commit fb91a10918576d6b1f4395960e2bbe2c6c11dcc4 Author: Allen Webster Date: Mon Sep 28 19:34:55 2015 -0400 initial diff --git a/4coder_custom.cpp b/4coder_custom.cpp new file mode 100644 index 00000000..dfbdea9a --- /dev/null +++ b/4coder_custom.cpp @@ -0,0 +1,221 @@ +/* + * Example use of customization API + */ + +#include "4coder_custom.h" + +// NOTE(allen): All this helper stuff is here to make the new API +// work a lot like the old API +struct Bind_Helper{ + Binding_Unit *cursor, *start, *end; + Binding_Unit *header, *map; + int write_total; + int error; +}; + +#define BH_ERR_NONE 0 +#define BH_ERR_MISSING_END_MAP 1 +#define BH_ERR_MISSING_BEGIN_MAP 2 + +inline Binding_Unit* +write_unit(Bind_Helper *helper, Binding_Unit unit){ + Binding_Unit *p = 0; + helper->write_total += sizeof(Binding_Unit); + if (helper->error == 0 && helper->cursor != helper->end){ + p = helper->cursor++; + *p = unit; + } + return p; +} + +inline Bind_Helper +begin_bind_helper(void *data, int size){ + Bind_Helper result; + + result.header = 0; + result.map = 0; + result.write_total = 0; + result.error = 0; + + result.cursor = (Binding_Unit*)data; + result.start = result.cursor; + result.end = result.start + size / sizeof(Binding_Unit); + + Binding_Unit unit; + unit.type = UNIT_HEADER; + unit.header.total_size = sizeof(Binding_Unit); + result.header = write_unit(&result, unit); + + return result; +} + +inline void +begin_map(Bind_Helper *helper, int mapid){ + if (helper->map != 0 && helper->error == 0) helper->error = BH_ERR_MISSING_END_MAP; + + Binding_Unit unit; + unit.type = UNIT_MAP_BEGIN; + unit.map_begin.mapid = mapid; + helper->map = write_unit(helper, unit); +} + +inline void +end_map(Bind_Helper *helper){ + if (helper->map == 0 && helper->error == 0) helper->error = BH_ERR_MISSING_BEGIN_MAP; + + helper->map = 0; +} + +inline void +bind(Bind_Helper *helper, short code, unsigned char modifiers, int cmdid){ + if (helper->map == 0 && helper->error == 0) helper->error = BH_ERR_MISSING_BEGIN_MAP; + + Binding_Unit unit; + unit.type = UNIT_BINDING; + unit.binding.command_id = cmdid; + unit.binding.code = code; + unit.binding.modifiers = modifiers; + + write_unit(helper, unit); +} + +inline void +bind_me(Bind_Helper *helper, short code, unsigned char modifiers, Custom_Command_Function *func){ + if (helper->map == 0 && helper->error == 0) helper->error = BH_ERR_MISSING_BEGIN_MAP; + + Binding_Unit unit; + unit.type = UNIT_CALLBACK; + unit.callback.func = func; + unit.callback.code = code; + unit.callback.modifiers = modifiers; + + write_unit(helper, unit); +} + +inline void +bind_vanilla_keys(Bind_Helper *helper, int cmdid){ + bind(helper, 0, 0, cmdid); +} + +inline void +bind_me_vanilla_keys(Bind_Helper *helper, Custom_Command_Function *func){ + bind_me(helper, 0, 0, func); +} + +inline void +end_bind_helper(Bind_Helper *helper){ + if (helper->header){ + helper->header->header.total_size = (int)(helper->cursor - helper->start); + helper->header->header.error = helper->error; + } +} + +#define exec_command app.exec_command +#define fulfill_interaction app.fulfill_interaction + +extern "C" START_HOOK_SIG(start_hook){ + exec_command(cmd_context, cmdid_open_panel_vsplit); + exec_command(cmd_context, cmdid_change_active_panel); +} + +CUSTOM_COMMAND_SIG(open_in_other){ + exec_command(cmd_context, cmdid_change_active_panel); + exec_command(cmd_context, cmdid_interactive_open); +} + +extern "C" GET_BINDING_DATA(get_bindings){ + Bind_Helper context_actual = begin_bind_helper(data, size); + Bind_Helper *context = &context_actual; + + begin_map(context, MAPID_GLOBAL); + + // NOTE(allen): Here put the contents of your set_global_bindings + bind(context, 'p', MDFR_CTRL, cmdid_open_panel_vsplit); + bind(context, '-', MDFR_CTRL, cmdid_open_panel_hsplit); + bind(context, 'P', MDFR_CTRL, cmdid_close_panel); + bind(context, 'n', MDFR_CTRL, cmdid_interactive_new); + bind(context, 'o', MDFR_CTRL, cmdid_interactive_open); + bind(context, ',', MDFR_CTRL, cmdid_change_active_panel); + bind(context, 'k', MDFR_CTRL, cmdid_interactive_kill_file); + bind(context, 'i', MDFR_CTRL, cmdid_interactive_switch_file); + bind(context, 'c', MDFR_ALT, cmdid_open_color_tweaker); + bind(context, 'x', MDFR_ALT, cmdid_open_menu); + bind_me(context, 'o', MDFR_ALT, open_in_other); + + end_map(context); + + + begin_map(context, MAPID_FILE); + + // NOTE(allen): This is a new concept in the API. Binding this can be thought of as binding + // all combos which have an ascii code (shifted or not) and unmodified by CTRL or ALT. + // As of now, if this is used it cannot be overriden for particular combos; this overrides + // normal bindings. + bind_vanilla_keys(context, cmdid_write_character); + + // NOTE(allen): Here put the contents of your set_file_bindings + bind(context, codes->left, MDFR_NONE, cmdid_move_left); + bind(context, codes->right, MDFR_NONE, cmdid_move_right); + bind(context, codes->del, MDFR_NONE, cmdid_delete); + bind(context, codes->back, MDFR_NONE, cmdid_backspace); + bind(context, codes->up, MDFR_NONE, cmdid_move_up); + bind(context, codes->down, MDFR_NONE, cmdid_move_down); + bind(context, codes->end, MDFR_NONE, cmdid_seek_end_of_line); + bind(context, codes->home, MDFR_NONE, cmdid_seek_beginning_of_line); + bind(context, codes->page_up, MDFR_NONE, cmdid_page_up); + bind(context, codes->page_down, MDFR_NONE, cmdid_page_down); + + bind(context, codes->right, MDFR_CTRL, cmdid_seek_alphanumeric_or_camel_right); + bind(context, codes->left, MDFR_CTRL, cmdid_seek_alphanumeric_or_camel_left); + bind(context, codes->up, MDFR_CTRL, cmdid_seek_whitespace_up); + bind(context, codes->down, MDFR_CTRL, cmdid_seek_whitespace_down); + + bind(context, ' ', MDFR_CTRL, cmdid_set_mark); + bind(context, 'm', MDFR_CTRL, cmdid_cursor_mark_swap); + bind(context, 'c', MDFR_CTRL, cmdid_copy); + bind(context, 'x', MDFR_CTRL, cmdid_cut); + bind(context, 'v', MDFR_CTRL, cmdid_paste); + bind(context, 'V', MDFR_CTRL, cmdid_paste_next); + bind(context, 'd', MDFR_CTRL, cmdid_delete_chunk); + bind(context, 'l', MDFR_CTRL, cmdid_toggle_line_wrap); + bind(context, 'L', MDFR_CTRL, cmdid_toggle_endline_mode); + bind(context, 'u', MDFR_CTRL, cmdid_to_uppercase); + bind(context, 'j', MDFR_CTRL, cmdid_to_lowercase); + bind(context, '?', MDFR_CTRL, cmdid_toggle_show_whitespace); + bind(context, '`', MDFR_CTRL, cmdid_clean_line); + bind(context, '~', MDFR_CTRL, cmdid_clean_all_lines); + bind(context, '1', MDFR_CTRL, cmdid_eol_dosify); + bind(context, '!', MDFR_CTRL, cmdid_eol_nixify); + bind(context, 'f', MDFR_CTRL, cmdid_search); + bind(context, 'r', MDFR_CTRL, cmdid_rsearch); + bind(context, 'g', MDFR_CTRL, cmdid_goto_line); + + bind(context, '\t', MDFR_CTRL, cmdid_auto_tab); + + bind(context, 'K', MDFR_CTRL, cmdid_kill_file); + bind(context, 'O', MDFR_CTRL, cmdid_reopen); + bind(context, 'w', MDFR_CTRL, cmdid_interactive_save_as); + bind(context, 's', MDFR_CTRL, cmdid_save); + + end_map(context); + end_bind_helper(context); + + return context->write_total; +} + +inline void +strset_(char *dst, char *src){ + do{ + *dst++ = *src++; + }while (*src); +} + +#define strset(d,s) if (sizeof(s) <= sizeof(d)) strset_(d,s) + +extern "C" SET_EXTRA_FONT_SIG(set_extra_font){ + strset(font_out->file_name, "liberation-mono.ttf"); + strset(font_out->font_name, "BIG"); + font_out->size = 25; +} + + diff --git a/4coder_custom.h b/4coder_custom.h new file mode 100644 index 00000000..94aa4e2f --- /dev/null +++ b/4coder_custom.h @@ -0,0 +1,176 @@ + +#define MDFR_NONE 0 +#define MDFR_CTRL 1 +#define MDFR_ALT 2 +#define MDFR_SHIFT 4 + +typedef unsigned short Code; + +struct Key_Codes{ + Code back; + Code up; + Code down; + Code left; + Code right; + Code del; + Code insert; + Code home; + Code end; + Code page_up; + Code page_down; + Code esc; + +#if 0 // TODO(allen): Get these working sometime + union{ + struct{ + Code f1; + Code f2; + Code f3; + Code f4; + Code f5; + Code f6; + Code f7; + Code f8; + + Code f9; + Code f10; + Code f11; + Code f12; + Code f13; + Code f14; + Code f15; + Code f16; + }; + Code f[16]; + }; +#endif +}; + +enum Command_ID{ + cmdid_null, + cmdid_write_character, + cmdid_seek_whitespace_right, + cmdid_seek_whitespace_left, + cmdid_seek_whitespace_up, + cmdid_seek_whitespace_down, + cmdid_seek_token_left, + cmdid_seek_token_right, + cmdid_seek_white_or_token_left, + cmdid_seek_white_or_token_right, + cmdid_seek_alphanumeric_left, + cmdid_seek_alphanumeric_right, + cmdid_seek_alphanumeric_or_camel_left, + cmdid_seek_alphanumeric_or_camel_right, + cmdid_search, + cmdid_rsearch, + cmdid_goto_line, + cmdid_set_mark, + cmdid_copy, + cmdid_cut, + cmdid_paste, + cmdid_paste_next, + cmdid_delete_chunk, + cmdid_interactive_new, + cmdid_interactive_open, + cmdid_reopen, + cmdid_save, + cmdid_interactive_save_as, + cmdid_change_active_panel, + cmdid_interactive_switch_file, + cmdid_interactive_kill_file, + cmdid_kill_file, + cmdid_toggle_line_wrap, + cmdid_toggle_endline_mode, + cmdid_to_uppercase, + cmdid_to_lowercase, + cmdid_toggle_show_whitespace, + cmdid_clean_line, + cmdid_clean_all_lines, + cmdid_eol_dosify, + cmdid_eol_nixify, + cmdid_auto_tab, + cmdid_open_panel_vsplit, + cmdid_open_panel_hsplit, + cmdid_close_panel, + cmdid_move_left, + cmdid_move_right, + cmdid_delete, + cmdid_backspace, + cmdid_move_up, + cmdid_move_down, + cmdid_seek_end_of_line, + cmdid_seek_beginning_of_line, + cmdid_page_up, + cmdid_page_down, + cmdid_open_color_tweaker, + cmdid_close_minor_view, + cmdid_cursor_mark_swap, + cmdid_open_menu, + // + cmdid_count +}; + +struct Extra_Font{ + char file_name[256]; + char font_name[24]; + int size; +}; + +#define GET_BINDING_DATA(name) int name(void *data, int size, Key_Codes *codes) +#define SET_EXTRA_FONT_SIG(name) void name(Extra_Font *font_out) +#define CUSTOM_COMMAND_SIG(name) void name(void *cmd_context, struct Application_Links app) +#define START_HOOK_SIG(name) void name(void *cmd_context, struct Application_Links app) + +extern "C"{ + typedef CUSTOM_COMMAND_SIG(Custom_Command_Function); + typedef GET_BINDING_DATA(Get_Binding_Data_Function); + typedef SET_EXTRA_FONT_SIG(Set_Extra_Font_Function); + typedef START_HOOK_SIG(Start_Hook_Function); +} + +#define EXECUTE_COMMAND_SIG(name) void name(void *cmd_context, int command_id) +#define FULFILL_INTERACTION_SIG(name) void name(void *cmd_context, char *data, bool full_set) + +extern "C"{ + typedef EXECUTE_COMMAND_SIG(Exec_Command_Function); + typedef FULFILL_INTERACTION_SIG(Fulfill_Interaction_Function); +} + +struct Application_Links{ + Exec_Command_Function *exec_command; + Fulfill_Interaction_Function *fulfill_interaction; +}; + +enum Binding_Unit_Type{ + UNIT_HEADER, + UNIT_MAP_BEGIN, + UNIT_BINDING, + UNIT_CALLBACK +}; + +enum Map_ID{ + MAPID_GLOBAL, + MAPID_FILE +}; + +struct Binding_Unit{ + Binding_Unit_Type type; + union{ + struct{ int total_size; int error; } header; + + struct{ int mapid; } map_begin; + + struct{ + int command_id; + short code; + unsigned char modifiers; + } binding; + + struct{ + Custom_Command_Function *func; + short code; + unsigned char modifiers; + } callback; + }; +}; + diff --git a/4cpp_clear_config.h b/4cpp_clear_config.h new file mode 100644 index 00000000..248652eb --- /dev/null +++ b/4cpp_clear_config.h @@ -0,0 +1,54 @@ +/* "4cpp" Open C++ Parser v0.1: Clear Config + no warranty implied; use at your own risk + +NOTES ON USE: + This file is used to clear options. The main use for this is for cases when you want + it include different portions of the library with different settings. So that the compiler + does not complain about redifintion, and so that you do not have to undef everything yourself + this is provided to undef everything at once. +*/ + +#ifdef FCPP_NO_CRT +#undef FCPP_NO_CRT +#endif + +#ifdef FCPP_NO_MALLOC +#undef FCPP_NO_MALLOC +#endif + +#ifdef FCPP_NO_ASSERT +#undef FCPP_NO_ASSERT +#endif + +#ifdef FCPP_NO_STRING +#undef FCPP_NO_STRING +#endif + +#ifdef FCPP_GET_MEMORY +#undef FCPP_GET_MEMORY +#endif + +#ifdef FCPP_FREE_MEMORY +#undef FCPP_FREE_MEMORY +#endif + +#ifdef FCPP_ASSERT +#undef FCPP_ASSERT +#endif + +#ifdef FCPP_MEM_COPY +#undef FCPP_MEM_COPY +#endif + +#ifdef FCPP_MEM_MOVE +#undef FCPP_MEM_MOVE +#endif + +#ifdef FCPP_LINK +#undef FCPP_LINK +#endif + +#ifdef FCPP_EXTERN +#undef FCPP_EXTERN +#endif + diff --git a/4cpp_config.h b/4cpp_config.h new file mode 100644 index 00000000..3bf91fcc --- /dev/null +++ b/4cpp_config.h @@ -0,0 +1,81 @@ +/* "4cpp" Open C++ Parser v0.1: Config + no warranty implied; use at your own risk + +NOTES ON USE: + This file is used to configure 4cpp options at the begining of 4cpp files. + It is not meant to be used directly. +*/ + +#ifdef FCPP_NO_CRT +# ifndef FCPP_NO_MALLOC +# define FCPP_NO_MALLOC +# endif +# ifndef FCPP_NO_ASSERT +# define FCPP_NO_ASSERT +# endif +# ifndef FCPP_NO_STRING +# define FCPP_NO_STRING +# endif +#endif + +#ifdef FCPP_FORBID_MALLOC +# define FCPP_NO_MALLOC +#endif + +#ifndef FCPP_NO_MALLOC +# include +#endif + +#ifndef FCPP_NO_ASSERT +# include +#endif + +#ifndef FCPP_NO_STRING +# include +#endif + +#ifndef FCPP_NO_MALLOC +# ifndef FCPP_GET_MEMORY +# define FCPP_GET_MEMORY malloc +# endif +# ifndef FCPP_FREE_MEMORY +# define FCPP_FREE_MEMORY free +# endif +#else +# ifndef FCPP_FORBID_MALLOC +# ifndef FCPP_GET_MEMORY +# error Missing definition for FCPP_GET_MEMORY +# endif +# ifndef FCPP_FREE_MEMORY +# error Missing definition for FCPP_FREE_MEMORY +# endif +# endif +#endif + +#ifndef FCPP_NO_ASSERT +# ifndef FCPP_ASSERT +# define FCPP_ASSERT assert +# endif +#else +# ifndef FCPP_ASSERT +# define FCPP_ASSERT(x) +# endif +#endif + +#ifndef FCPP_NO_STRING +# ifndef FCPP_MEM_COPY +# define FCPP_MEM_COPY memcpy +# endif +# ifndef FCPP_MEM_MOVE +# define FCPP_MEM_MOVE memmove +# endif +#endif + +#ifndef FCPP_LINK +# ifdef FCPP_EXTERN +# define FCPP_LINK extern +# else +# define FCPP_LINK static +# endif +#endif + diff --git a/4cpp_lexer.h b/4cpp_lexer.h new file mode 100644 index 00000000..11607b41 --- /dev/null +++ b/4cpp_lexer.h @@ -0,0 +1,1912 @@ +/* "4cpp" Open C++ Parser v0.1: Lexer + no warranty implied; use at your own risk + +NOTES ON USE: + OPTIONS: + Set options by defining macros before including this file. + + FCPP_LEXER_IMPLEMENTATION - causes this file to output function implementations + - this option is unset after use so that future includes of this file + in the same unit do not continue to output implementations + + FCPP_NO_MALLOC - prevent including + FCPP_NO_ASSERT - prevent including + FCPP_NO_STRING - prevent including + FCPP_NO_CRT - FCPP_NO_MALLOC & FCPP_NO_ASSERT & FCPP_NO_STRING + + FCPP_FORBID_MALLOC - one step above *NO_MALLOC with this set 4cpp functions that do allocations + are not allowed to be declared or defined at all, forcing the user to handle + allocation themselves + - implies FCPP_NO_MALLOC + + FCPP_GET_MEMORY - defines how to make allocations, interface of malloc, defaults to malloc + FCPP_FREE_MEMORY - defines how to free memory, interface of ree, defaults to free + (The above must be defined if FCPP_NO_MALLOC is set, unless FCPP_FORBID_MALLOC is set) + + FCPP_ASSERT - defines how to make assertions, interface of assert, defaults to assert + + FCPP_MEM_COPY - defines how to copy blocks of memory, interface of memcpy, defaults to memcpy + FCPP_MEM_MOVE - defines how to move blocks of memory, interface of memmove, defaults to memmove + (The above must be defined if FCPP_NO_STRING is set) + + FCPP_LINK - defines linkage of non-inline functions, defaults to static + FCPP_EXTERN - changes FCPP_LINK default to extern, this option is ignored if FCPP_LINK is defined + + include the file "4cpp_clear_config.h" if you want to undefine all options for some reason + + HIDDDEN DEPENDENCIES: + 4cpp is not a single file include library, there are dependencies between the files. + Be sure to include these dependencies before 4cpp_lexer.h: + + 4cpp_types.h + 4cpp_string.h +*/ + +// TOP +// TODO(allen): +// +// EASE OF USE AND DEPLOYMENT +// - make it easier to locate the list of function declarations +// - more C compatibility +// +// POTENTIAL +// - Experiment with optimizations. Sean's State machine? +// - Reserve 0th token for null? Put a EOF token at the end? +// - Pass Cpp_File and Cpp_Token_Stack by value instead of by pointer? +// +// CURRENT +// - lex in chunks +// + +#include "4cpp_config.h" + +#ifndef FCPP_LEXER_INC +#define FCPP_LEXER_INC + +enum Cpp_Token_Type{ + CPP_TOKEN_JUNK, + CPP_TOKEN_COMMENT, + + CPP_TOKEN_KEY_TYPE, + CPP_TOKEN_KEY_MODIFIER, + CPP_TOKEN_KEY_QUALIFIER, + CPP_TOKEN_KEY_OPERATOR, // NOTE(allen): This type is not actually stored in tokens + CPP_TOKEN_KEY_CONTROL_FLOW, + CPP_TOKEN_KEY_CAST, + CPP_TOKEN_KEY_TYPE_DECLARATION, + CPP_TOKEN_KEY_ACCESS, + CPP_TOKEN_KEY_LINKAGE, + CPP_TOKEN_KEY_OTHER, + + CPP_TOKEN_IDENTIFIER, + CPP_TOKEN_INTEGER_CONSTANT, + CPP_TOKEN_CHARACTER_CONSTANT, + CPP_TOKEN_FLOATING_CONSTANT, + CPP_TOKEN_STRING_CONSTANT, + CPP_TOKEN_BOOLEAN_CONSTANT, + + CPP_TOKEN_STATIC_ASSERT, + + CPP_TOKEN_BRACKET_OPEN, + CPP_TOKEN_BRACKET_CLOSE, + CPP_TOKEN_PARENTHESE_OPEN, + CPP_TOKEN_PARENTHESE_CLOSE, + CPP_TOKEN_BRACE_OPEN, + CPP_TOKEN_BRACE_CLOSE, + CPP_TOKEN_SEMICOLON, + CPP_TOKEN_ELLIPSIS, + + // NOTE(allen): Ambiguous tokens, lexer only, + // parser figures out the real meaning + CPP_TOKEN_STAR, + CPP_TOKEN_AMPERSAND, + CPP_TOKEN_TILDE, + CPP_TOKEN_PLUS, + CPP_TOKEN_MINUS, + CPP_TOKEN_INCREMENT, + CPP_TOKEN_DECREMENT, + + // NOTE(allen): Precedence 1, LtoR + CPP_TOKEN_SCOPE, + + // NOTE(allen): Precedence 2, LtoR + CPP_TOKEN_POSTINC, // from increment, parser only + CPP_TOKEN_POSTDEC, // from decrement, parser only + CPP_TOKEN_FUNC_STYLE_CAST, // parser only + CPP_TOKEN_CPP_STYLE_CAST, + CPP_TOKEN_CALL, // from open paren, parser only + CPP_TOKEN_INDEX, // from bracket open, parser only + CPP_TOKEN_DOT, + CPP_TOKEN_ARROW, + + // NOTE(allen): Precedence 3, RtoL + CPP_TOKEN_PREINC, // from increment, parser only + CPP_TOKEN_PREDEC, // from decrement, parser only + CPP_TOKEN_POSITIVE, // from plus, parser only + CPP_TOKEN_NEGAITVE, // from minus, parser only + CPP_TOKEN_NOT, + CPP_TOKEN_BIT_NOT, // from tilde, direct from 'compl' + CPP_TOKEN_CAST, // from open paren, parser only + CPP_TOKEN_DEREF, // from star, parser only + CPP_TOKEN_TYPE_PTR, // from star, parser only + CPP_TOKEN_ADDRESS, // from ampersand, parser only + CPP_TOKEN_TYPE_REF, // from ampersand, parser only + CPP_TOKEN_SIZEOF, + CPP_TOKEN_ALIGNOF, + CPP_TOKEN_DECLTYPE, + CPP_TOKEN_TYPEID, + CPP_TOKEN_NEW, + CPP_TOKEN_DELETE, + CPP_TOKEN_NEW_ARRAY, // from new and bracket open, parser only + CPP_TOKEN_DELETE_ARRAY, // from delete and bracket open, parser only + + // NOTE(allen): Precedence 4, LtoR + CPP_TOKEN_PTRDOT, + CPP_TOKEN_PTRARROW, + + // NOTE(allen): Precedence 5, LtoR + CPP_TOKEN_MUL, // from start, parser only + CPP_TOKEN_DIV, + CPP_TOKEN_MOD, + + // NOTE(allen): Precedence 6, LtoR + CPP_TOKEN_ADD, // from plus, parser only + CPP_TOKEN_SUB, // from minus, parser only + + // NOTE(allen): Precedence 7, LtoR + CPP_TOKEN_LSHIFT, + CPP_TOKEN_RSHIFT, + + // NOTE(allen): Precedence 8, LtoR + CPP_TOKEN_LESS, + CPP_TOKEN_GRTR, + CPP_TOKEN_GRTREQ, + CPP_TOKEN_LESSEQ, + + // NOTE(allen): Precedence 9, LtoR + CPP_TOKEN_EQEQ, + CPP_TOKEN_NOTEQ, + + // NOTE(allen): Precedence 10, LtoR + CPP_TOKEN_BIT_AND, // from ampersand, direct from 'bitand' + + // NOTE(allen): Precedence 11, LtoR + CPP_TOKEN_BIT_XOR, + + // NOTE(allen): Precedence 12, LtoR + CPP_TOKEN_BIT_OR, + + // NOTE(allen): Precedence 13, LtoR + CPP_TOKEN_AND, + + // NOTE(allen): Precedence 14, LtoR + CPP_TOKEN_OR, + + // NOTE(allen): Precedence 15, RtoL + CPP_TOKEN_TERNARY_QMARK, + CPP_TOKEN_COLON, + CPP_TOKEN_THROW, + CPP_TOKEN_EQ, + CPP_TOKEN_ADDEQ, + CPP_TOKEN_SUBEQ, + CPP_TOKEN_MULEQ, + CPP_TOKEN_DIVEQ, + CPP_TOKEN_MODEQ, + CPP_TOKEN_LSHIFTEQ, + CPP_TOKEN_RSHIFTEQ, + CPP_TOKEN_ANDEQ, + CPP_TOKEN_OREQ, + CPP_TOKEN_XOREQ, + + // NOTE(allen): Precedence 16, LtoR + CPP_TOKEN_COMMA, + + CPP_PP_INCLUDE, + CPP_PP_DEFINE, + CPP_PP_UNDEF, + CPP_PP_IF, + CPP_PP_IFDEF, + CPP_PP_IFNDEF, + CPP_PP_ELSE, + CPP_PP_ELIF, + CPP_PP_ENDIF, + CPP_PP_ERROR, + CPP_PP_IMPORT, + CPP_PP_USING, + CPP_PP_LINE, + CPP_PP_PRAGMA, + CPP_PP_STRINGIFY, + CPP_PP_CONCAT, + CPP_PP_UNKNOWN, + CPP_TOKEN_DEFINED, + CPP_TOKEN_INCLUDE_FILE, + + // NOTE(allen): used in the parser + CPP_TOKEN_EOF +}; + +struct Cpp_File{ + char *data; + int size; +}; + +struct Cpp_Token{ + Cpp_Token_Type type; + fcpp_i32 start, size; + fcpp_u16 state_flags; + fcpp_u16 flags; +}; + +enum Cpp_Token_Flag{ + CPP_TFLAG_IGNORE = 1 << 0, + CPP_TFLAG_PP_DIRECTIVE = 1 << 1, + CPP_TFLAG_PP_BODY = 1 << 2, + CPP_TFLAG_BAD_ENDING = 1 << 3, + CPP_TFLAG_MULTILINE = 1 << 4, + CPP_TFLAG_PARAMETERIZED = 1 << 5, + CPP_TFLAG_IS_OPERATOR = 1 << 6, + CPP_TFLAG_IS_KEYWORD = 1 << 7 +}; + +enum Cpp_Preprocessor_State{ + CPP_LEX_PP_DEFAULT, + CPP_LEX_PP_IDENTIFIER, + CPP_LEX_PP_MACRO_IDENTIFIER, + CPP_LEX_PP_INCLUDE, + CPP_LEX_PP_BODY, + CPP_LEX_PP_BODY_IF, + CPP_LEX_PP_NUMBER, + CPP_LEX_PP_JUNK, + // NEVER ADD BELOW THIS + CPP_LEX_PP_COUNT +}; + +struct Cpp_Lex_Data{ + Cpp_Preprocessor_State pp_state; + fcpp_i32 pos; + fcpp_bool32 complete; +}; + +struct Cpp_Read_Result{ + Cpp_Token token; + fcpp_i32 pos; + fcpp_bool8 newline; + fcpp_bool8 has_result; +}; + +struct Cpp_Token_Stack{ + Cpp_Token *tokens; + int count, max_count; +}; + +struct Cpp_Token_Merge{ + Cpp_Token new_token; + fcpp_bool32 did_merge; +}; + +struct Seek_Result{ + fcpp_i32 pos; + fcpp_bool32 new_line; +}; + +struct Cpp_Get_Token_Result{ + fcpp_i32 token_index; + fcpp_bool32 in_whitespace; +}; + +// TODO(allen): revisit this keyword data declaration system +struct String_And_Flag{ + char *str; + fcpp_u32 flags; +}; + +struct String_List{ + String_And_Flag *data; + int count; +}; + +struct Sub_Match_List_Result{ + int index; + fcpp_i32 new_pos; +}; + +inline fcpp_u16 +cpp_token_set_pp_state(fcpp_u16 bitfield, Cpp_Preprocessor_State state_value){ + return (fcpp_u16)state_value; +} + +inline Cpp_Preprocessor_State +cpp_token_get_pp_state(fcpp_u16 bitfield){ + return (Cpp_Preprocessor_State)(bitfield); +} + +inline String +cpp_get_lexeme(char *str, Cpp_Token *token){ + String result; + result.str = str + token->start; + result.size = token->size; + return result; +} + +inline bool +is_keyword(Cpp_Token_Type type){ + return (type >= CPP_TOKEN_KEY_TYPE && type <= CPP_TOKEN_KEY_OTHER); +} + +FCPP_LINK Sub_Match_List_Result sub_match_list(Cpp_File file, int pos, String_List list, int sub_size); + +FCPP_LINK Seek_Result seek_unescaped_eol(char *data, int size, int pos); +FCPP_LINK Seek_Result seek_unescaped_delim(char *data, int size, int pos, char delim); +FCPP_LINK Seek_Result seek_block_comment_end(char *data, int size, int pos); + +FCPP_LINK Cpp_Read_Result cpp_read_whitespace(Cpp_File file, int pos); +FCPP_LINK Cpp_Read_Result cpp_read_junk_line(Cpp_File file, int pos); +FCPP_LINK Cpp_Read_Result cpp_read_operator(Cpp_File file, int pos); +FCPP_LINK Cpp_Read_Result cpp_read_pp_operator(Cpp_File file, int pos); +FCPP_LINK Cpp_Read_Result cpp_read_alpha_numeric(Cpp_File file, int pos, bool in_if_body); +inline Cpp_Read_Result cpp_read_alpha_numeric(Cpp_File file, int pos) { return cpp_read_alpha_numeric(file, pos, 0); } +FCPP_LINK Cpp_Read_Result cpp_read_number(Cpp_File file, int pos); +FCPP_LINK Cpp_Read_Result cpp_read_string_litteral(Cpp_File file, int pos); +FCPP_LINK Cpp_Read_Result cpp_read_character_litteral(Cpp_File file, int pos); +FCPP_LINK Cpp_Read_Result cpp_read_line_comment(Cpp_File file, int pos); +FCPP_LINK Cpp_Read_Result cpp_read_block_comment(Cpp_File file, int pos); +FCPP_LINK Cpp_Read_Result cpp_read_preprocessor(Cpp_File file, int pos); +FCPP_LINK Cpp_Read_Result cpp_read_pp_include_file(Cpp_File file, int pos); +FCPP_LINK Cpp_Read_Result cpp_read_pp_default_mode(Cpp_File file, int pos, bool in_if_body); +inline Cpp_Read_Result cpp_read_pp_default_mode(Cpp_File file, int pos) { return cpp_read_pp_default_mode(file, pos, 0); } + +FCPP_LINK Cpp_Token_Merge cpp_attempt_token_merge(Cpp_Token prev, Cpp_Token next); + +FCPP_LINK bool cpp_push_token_no_merge(Cpp_Token_Stack *stack, Cpp_Token token); +FCPP_LINK bool cpp_push_token_nonalloc(Cpp_Token_Stack *stack, Cpp_Token token); + +FCPP_LINK Cpp_Read_Result cpp_lex_step(Cpp_File file, Cpp_Lex_Data *lex); + +FCPP_LINK int cpp_lex_file_token_count(Cpp_File file); +FCPP_LINK Cpp_Lex_Data cpp_lex_file_nonalloc(Cpp_File file, Cpp_Token_Stack *stack, Cpp_Lex_Data data); +inline Cpp_Lex_Data cpp_lex_file_nonalloc(Cpp_File file, Cpp_Token_Stack *stack) { return cpp_lex_file_nonalloc(file, stack, {}); } + +FCPP_LINK Cpp_Get_Token_Result cpp_get_token(Cpp_Token_Stack *stack, int pos); + +FCPP_LINK int cpp_get_end_token(Cpp_Token_Stack *stack, int end); +FCPP_LINK void cpp_shift_token_starts(Cpp_Token_Stack *stack, int from_token, int amount); + +struct Cpp_Relex_State{ + Cpp_File file; + Cpp_Token_Stack *stack; + int start, end, amount; + int start_token_i; + int end_token_i; + int relex_start; + int tolerance; + int space_request; +}; + +FCPP_LINK Cpp_Relex_State cpp_relex_nonalloc_start(Cpp_File file, Cpp_Token_Stack *stack, int start, int end, int amount, int tolerance); +FCPP_LINK bool cpp_relex_nonalloc_main(Cpp_Relex_State state, Cpp_Token_Stack *stack); + +#ifndef FCPP_FORBID_MALLOC +FCPP_LINK Cpp_Token_Stack cpp_make_token_stack(int max); +FCPP_LINK void cpp_free_token_stack(Cpp_Token_Stack stack); +FCPP_LINK void cpp_resize_token_stack(Cpp_Token_Stack *stack, int new_max); + +FCPP_LINK void cpp_push_token(Cpp_Token_Stack *stack, Cpp_Token token); +FCPP_LINK void cpp_lex_file(Cpp_File file, Cpp_Token_Stack *stack); +FCPP_LINK bool cpp_relex_file_limited(Cpp_File file, Cpp_Token_Stack *stack, int start_i, int end_i, int amount, int extra_tolerance); +inline void cpp_relex_file(Cpp_File file, Cpp_Token_Stack *stack, int start_i, int end_i, int amount) +{ cpp_relex_file_limited(file, stack, start_i, end_i, amount, -1); } +#endif + +#define FCPP_STRING_LIST(x) {x, FCPP_COUNT(x)} + +// TODO(allen): shift towards storing in a context +FCPP_GLOBAL String_And_Flag int_suf_strings[] = { + {"ull"}, {"ULL"}, + {"llu"}, {"LLU"}, + {"ll"}, {"LL"}, + {"l"}, {"L"}, + {"u"}, {"U"} +}; + +FCPP_GLOBAL String_List int_sufs = FCPP_STRING_LIST(int_suf_strings); + +FCPP_GLOBAL String_And_Flag float_suf_strings[] = { + {"f"}, {"F"}, + {"l"}, {"L"} +}; +FCPP_GLOBAL String_List float_sufs = FCPP_STRING_LIST(float_suf_strings); + +FCPP_GLOBAL String_And_Flag bool_lit_strings[] = { + {"true"}, {"false"} +}; +FCPP_GLOBAL String_List bool_lits = FCPP_STRING_LIST(bool_lit_strings); + +FCPP_GLOBAL String_And_Flag keyword_strings[] = { + {"and", CPP_TOKEN_AND}, + {"and_eq", CPP_TOKEN_ANDEQ}, + {"bitand", CPP_TOKEN_BIT_AND}, + {"bitor", CPP_TOKEN_BIT_OR}, + {"or", CPP_TOKEN_OR}, + {"or_eq", CPP_TOKEN_OREQ}, + {"sizeof", CPP_TOKEN_SIZEOF}, + {"alignof", CPP_TOKEN_ALIGNOF}, + {"decltype", CPP_TOKEN_DECLTYPE}, + {"throw", CPP_TOKEN_THROW}, + {"new", CPP_TOKEN_NEW}, + {"delete", CPP_TOKEN_DELETE}, + {"xor", CPP_TOKEN_BIT_XOR}, + {"xor_eq", CPP_TOKEN_XOREQ}, + {"not", CPP_TOKEN_NOT}, + {"not_eq", CPP_TOKEN_NOTEQ}, + {"typeid", CPP_TOKEN_TYPEID}, + {"compl", CPP_TOKEN_BIT_NOT}, + + {"void", CPP_TOKEN_KEY_TYPE}, + {"bool", CPP_TOKEN_KEY_TYPE}, + {"char", CPP_TOKEN_KEY_TYPE}, + {"int", CPP_TOKEN_KEY_TYPE}, + {"float", CPP_TOKEN_KEY_TYPE}, + {"double", CPP_TOKEN_KEY_TYPE}, + + {"long", CPP_TOKEN_KEY_MODIFIER}, + {"short", CPP_TOKEN_KEY_MODIFIER}, + + {"const", CPP_TOKEN_KEY_QUALIFIER}, + {"volatile", CPP_TOKEN_KEY_QUALIFIER}, + + {"asm", CPP_TOKEN_KEY_CONTROL_FLOW}, + {"break", CPP_TOKEN_KEY_CONTROL_FLOW}, + {"case", CPP_TOKEN_KEY_CONTROL_FLOW}, + {"catch", CPP_TOKEN_KEY_CONTROL_FLOW}, + {"continue", CPP_TOKEN_KEY_CONTROL_FLOW}, + {"default", CPP_TOKEN_KEY_CONTROL_FLOW}, + {"do", CPP_TOKEN_KEY_CONTROL_FLOW}, + {"else", CPP_TOKEN_KEY_CONTROL_FLOW}, + {"for", CPP_TOKEN_KEY_CONTROL_FLOW}, + {"goto", CPP_TOKEN_KEY_CONTROL_FLOW}, + {"if", CPP_TOKEN_KEY_CONTROL_FLOW}, + {"return", CPP_TOKEN_KEY_CONTROL_FLOW}, + {"switch", CPP_TOKEN_KEY_CONTROL_FLOW}, + {"try", CPP_TOKEN_KEY_CONTROL_FLOW}, + {"while", CPP_TOKEN_KEY_CONTROL_FLOW}, + {"static_assert", CPP_TOKEN_KEY_CONTROL_FLOW}, + + {"const_cast", CPP_TOKEN_KEY_CAST}, + {"dynamic_cast", CPP_TOKEN_KEY_CAST}, + {"reinterpret_cast", CPP_TOKEN_KEY_CAST}, + {"static_cast", CPP_TOKEN_KEY_CAST}, + + {"class", CPP_TOKEN_KEY_TYPE_DECLARATION}, + {"enum", CPP_TOKEN_KEY_TYPE_DECLARATION}, + {"struct", CPP_TOKEN_KEY_TYPE_DECLARATION}, + {"typedef", CPP_TOKEN_KEY_TYPE_DECLARATION}, + {"union", CPP_TOKEN_KEY_TYPE_DECLARATION}, + {"template", CPP_TOKEN_KEY_TYPE_DECLARATION}, + {"typename", CPP_TOKEN_KEY_TYPE_DECLARATION}, + + {"friend", CPP_TOKEN_KEY_ACCESS}, + {"namespace", CPP_TOKEN_KEY_ACCESS}, + {"private", CPP_TOKEN_KEY_ACCESS}, + {"protected", CPP_TOKEN_KEY_ACCESS}, + {"public", CPP_TOKEN_KEY_ACCESS}, + {"using", CPP_TOKEN_KEY_ACCESS}, + + {"extern", CPP_TOKEN_KEY_LINKAGE}, + {"export", CPP_TOKEN_KEY_LINKAGE}, + {"inline", CPP_TOKEN_KEY_LINKAGE}, + {"static", CPP_TOKEN_KEY_LINKAGE}, + {"virtual", CPP_TOKEN_KEY_LINKAGE}, + + {"alignas", CPP_TOKEN_KEY_OTHER}, + {"explicit", CPP_TOKEN_KEY_OTHER}, + {"noexcept", CPP_TOKEN_KEY_OTHER}, + {"nullptr", CPP_TOKEN_KEY_OTHER}, + {"operator", CPP_TOKEN_KEY_OTHER}, + {"register", CPP_TOKEN_KEY_OTHER}, + {"this", CPP_TOKEN_KEY_OTHER}, + {"thread_local", CPP_TOKEN_KEY_OTHER}, +}; +FCPP_GLOBAL String_List keywords = FCPP_STRING_LIST(keyword_strings); + +FCPP_GLOBAL String_And_Flag op_strings[] = { + {"...", CPP_TOKEN_ELLIPSIS}, + {"<<=", CPP_TOKEN_LSHIFTEQ}, + {">>=", CPP_TOKEN_RSHIFTEQ}, + {"->*", CPP_TOKEN_PTRARROW}, + {"<<", CPP_TOKEN_LSHIFT}, + {">>", CPP_TOKEN_RSHIFT}, + {"&&", CPP_TOKEN_AND}, + {"||", CPP_TOKEN_OR}, + {"->", CPP_TOKEN_ARROW}, + {"++", CPP_TOKEN_INCREMENT}, + {"--", CPP_TOKEN_DECREMENT}, + {"::", CPP_TOKEN_SCOPE}, + {"+=", CPP_TOKEN_ADDEQ}, + {"-=", CPP_TOKEN_SUBEQ}, + {"*=", CPP_TOKEN_MULEQ}, + {"/=", CPP_TOKEN_DIVEQ}, + {"%=", CPP_TOKEN_MODEQ}, + {"&=", CPP_TOKEN_ANDEQ}, + {"|=", CPP_TOKEN_OREQ}, + {"^=", CPP_TOKEN_XOREQ}, + {"==", CPP_TOKEN_EQEQ}, + {">=", CPP_TOKEN_GRTREQ}, + {"<=", CPP_TOKEN_LESSEQ}, + {"!=", CPP_TOKEN_NOTEQ}, + {".*", CPP_TOKEN_PTRDOT}, + {"{", CPP_TOKEN_BRACE_OPEN}, + {"}", CPP_TOKEN_BRACE_CLOSE}, + {"[", CPP_TOKEN_BRACKET_OPEN}, + {"]", CPP_TOKEN_BRACKET_CLOSE}, + {"(", CPP_TOKEN_PARENTHESE_OPEN}, + {")", CPP_TOKEN_PARENTHESE_CLOSE}, + {"<", CPP_TOKEN_LESS}, + {">", CPP_TOKEN_GRTR}, + {"+", CPP_TOKEN_PLUS}, + {"-", CPP_TOKEN_MINUS}, + {"!", CPP_TOKEN_NOT}, + {"~", CPP_TOKEN_TILDE}, + {"*", CPP_TOKEN_STAR}, + {"&", CPP_TOKEN_AMPERSAND}, + {"|", CPP_TOKEN_BIT_OR}, + {"^", CPP_TOKEN_BIT_XOR}, + {"=", CPP_TOKEN_EQ}, + {",", CPP_TOKEN_COMMA}, + {":", CPP_TOKEN_COLON}, + {";", CPP_TOKEN_SEMICOLON}, + {"/", CPP_TOKEN_DIV}, + {"?", CPP_TOKEN_TERNARY_QMARK}, + {"%", CPP_TOKEN_MOD}, + {".", CPP_TOKEN_DOT}, +}; +FCPP_GLOBAL String_List ops = FCPP_STRING_LIST(op_strings); + +FCPP_GLOBAL String_And_Flag pp_op_strings[] = { + {"##", CPP_PP_CONCAT}, + {"#", CPP_PP_STRINGIFY}, +}; +FCPP_GLOBAL String_List pp_ops = FCPP_STRING_LIST(pp_op_strings); + +FCPP_GLOBAL String_And_Flag preprop_strings[] = { + {"include", CPP_PP_INCLUDE}, + {"INCLUDE", CPP_PP_INCLUDE}, + {"ifndef", CPP_PP_IFNDEF}, + {"IFNDEF", CPP_PP_IFNDEF}, + {"define", CPP_PP_DEFINE}, + {"DEFINE", CPP_PP_DEFINE}, + {"import", CPP_PP_IMPORT}, + {"IMPORT", CPP_PP_IMPORT}, + {"pragma", CPP_PP_PRAGMA}, + {"PRAGMA", CPP_PP_PRAGMA}, + {"undef", CPP_PP_UNDEF}, + {"UNDEF", CPP_PP_UNDEF}, + {"endif", CPP_PP_ENDIF}, + {"ENDIF", CPP_PP_ENDIF}, + {"error", CPP_PP_ERROR}, + {"ERROR", CPP_PP_ERROR}, + {"ifdef", CPP_PP_IFDEF}, + {"IFDEF", CPP_PP_IFDEF}, + {"using", CPP_PP_USING}, + {"USING", CPP_PP_USING}, + {"else", CPP_PP_ELSE}, + {"ELSE", CPP_PP_ELSE}, + {"elif", CPP_PP_ELIF}, + {"ELIF", CPP_PP_ELIF}, + {"line", CPP_PP_LINE}, + {"LINE", CPP_PP_LINE}, + {"if", CPP_PP_IF}, + {"IF", CPP_PP_IF}, +}; +FCPP_GLOBAL String_List preprops = FCPP_STRING_LIST(preprop_strings); + +#undef FCPP_STRING_LIST + +#endif // #ifndef FCPP_CPP_LEXER + +#ifdef FCPP_LEXER_IMPLEMENTATION + +#define _Assert FCPP_ASSERT +#define _TentativeAssert FCPP_ASSERT + +FCPP_LINK Sub_Match_List_Result +sub_match_list(Cpp_File file, int pos, String_List list, int sub_size){ + Sub_Match_List_Result result; + String str_main; + char *str_check; + int i,l; + + result.index = -1; + result.new_pos = pos; + str_main = make_string(file.data + pos, file.size - pos); + if (sub_size > 0){ + str_main = substr(str_main, 0, sub_size); + for (i = 0; i < list.count; ++i){ + str_check = list.data[i].str; + if (match(str_main, str_check)){ + result.index = i; + result.new_pos = pos + sub_size; + break; + } + } + } + else{ + for (i = 0; i < list.count; ++i){ + str_check = list.data[i].str; + if (match_part(str_main, str_check, &l)){ + result.index = i; + result.new_pos = pos + l; + break; + } + } + } + return result; +} + +FCPP_LINK Seek_Result +seek_unescaped_eol(char *data, int size, int pos){ + Seek_Result result = {}; + ++pos; + while (pos < size){ + if (data[pos] == '\\'){ + if (pos + 1 < size && + data[pos+1] == '\n'){ + result.new_line = 1; + ++pos; + } + else if (pos + 1 < size && + data[pos+1] == '\r' && + pos + 2 < size && + data[pos+2] == '\n'){ + result.new_line = 1; + pos += 2; + } + } + else if (data[pos] == '\n'){ + break; + } + ++pos; + } + ++pos; + + result.pos = pos; + return result; +} + +FCPP_LINK Seek_Result +seek_unescaped_delim(char *data, int size, int pos, char delim){ + Seek_Result result = {}; + bool escape = 0; + ++pos; + while (pos < size){ + if (data[pos] == '\n'){ + result.new_line = 1; + } + if (escape){ + escape = 0; + } + else{ + if (data[pos] == '\\'){ + escape = 1; + } + else if (data[pos] == delim){ + break; + } + } + ++pos; + } + ++pos; + + result.pos = pos; + return result; +} + +FCPP_LINK Seek_Result +seek_block_comment_end(char *data, int size, int pos){ + Seek_Result result = {}; + pos += 2; + while (pos < size){ + if (data[pos] == '*' && + pos + 1 < size && + data[pos+1] == '/'){ + break; + } + if (data[pos] == '\n'){ + result.new_line = 1; + } + ++pos; + } + pos += 2; + result.pos = pos; + return result; +} + +FCPP_LINK Cpp_Read_Result +cpp_read_whitespace(Cpp_File file, int pos){ + Cpp_Read_Result result = {}; + + while (pos < file.size && char_is_whitespace(file.data[pos])){ + if (file.data[pos] == '\n'){ + result.newline = 1; + } + ++pos; + } + + result.pos = pos; + + return result; +} + +FCPP_LINK Cpp_Read_Result +cpp_read_junk_line(Cpp_File file, int pos){ + Cpp_Read_Result result = {}; + result.token.start = pos; + result.token.type = CPP_TOKEN_JUNK; + + bool comment_end = 0; + while (pos < file.size && file.data[pos] != '\n'){ + if (file.data[pos] == '/' && pos + 1 < file.size){ + if (file.data[pos + 1] == '/' || + file.data[pos + 1] == '*'){ + comment_end = 1; + break; + } + } + ++pos; + } + + if (comment_end){ + result.pos = pos; + result.token.size = pos - result.token.start; + } + else{ + while (pos > 0 && file.data[pos - 1] == '\r'){ + --pos; + } + if (pos > 0 && file.data[pos - 1] == '\\'){ + --pos; + } + result.pos = pos; + result.token.size = pos - result.token.start - 1; + } + + return result; +} + +FCPP_LINK Cpp_Read_Result +cpp_read_operator(Cpp_File file, int pos){ + Cpp_Read_Result result = {}; + result.pos = pos; + result.token.start = pos; + + Sub_Match_List_Result match; + match = sub_match_list(file, result.token.start, ops, -1); + + if (match.index != -1){ + result.pos = match.new_pos; + result.token.size = result.pos - result.token.start; + result.token.type = (Cpp_Token_Type)ops.data[match.index].flags; + result.token.flags |= CPP_TFLAG_IS_OPERATOR; + } + else{ + result.token.size = 1; + result.token.type = CPP_TOKEN_JUNK; + result.pos = pos + 1; + } + + return result; +} + +FCPP_LINK Cpp_Read_Result +cpp_read_pp_operator(Cpp_File file, int pos){ + Cpp_Read_Result result = {}; + result.pos = pos; + result.token.start = pos; + + Sub_Match_List_Result match; + match = sub_match_list(file, result.token.start, pp_ops, -1); + + _Assert(match.index != -1); + result.pos = match.new_pos; + result.token.size = result.pos - result.token.start; + result.token.type = (Cpp_Token_Type)pp_ops.data[match.index].flags; + + return result; +} + +FCPP_LINK Cpp_Read_Result +cpp_read_alpha_numeric(Cpp_File file, int pos, bool in_if_body){ + Cpp_Read_Result result = {}; + result.pos = pos; + result.token.start = pos; + + while (result.pos < file.size && + char_is_alpha_numeric(file.data[result.pos])){ + ++result.pos; + } + + result.token.size = result.pos - result.token.start; + + // TODO(allen): do better + if (in_if_body){ + String word; + word.size = result.token.size; + word.str = file.data + result.token.start; + if (match(word, "defined")){ + result.token.type = CPP_TOKEN_DEFINED; + result.token.flags |= CPP_TFLAG_IS_OPERATOR; + result.token.flags |= CPP_TFLAG_IS_KEYWORD; + } + } + + if (result.token.type == CPP_TOKEN_JUNK){ + Sub_Match_List_Result match; + match = sub_match_list(file, result.token.start, bool_lits, result.token.size); + + if (match.index != -1){ + result.token.type = CPP_TOKEN_BOOLEAN_CONSTANT; + result.token.flags |= CPP_TFLAG_IS_KEYWORD; + } + else{ + match = sub_match_list(file, result.token.start, keywords, result.token.size); + + if (match.index != -1){ + String_And_Flag data = keywords.data[match.index]; + result.token.type = (Cpp_Token_Type)data.flags; + result.token.flags |= CPP_TFLAG_IS_KEYWORD; + } + else{ + result.token.type = CPP_TOKEN_IDENTIFIER; + } + } + } + + return result; +} + +FCPP_LINK Cpp_Read_Result +cpp_read_number(Cpp_File file, int pos){ + Cpp_Read_Result result = {}; + result.pos = pos; + result.token.start = pos; + + bool is_float = 0; + bool is_integer = 0; + bool is_oct = 0; + bool is_hex = 0; + bool is_zero = 0; + + if (file.data[pos] == '0'){ + if (pos+1 < file.size){ + char next = file.data[pos+1]; + if (next == 'x'){ + is_hex = 1; + is_integer = 1; + } + else if (next == '.'){ + is_float = 1; + ++result.pos; + } + else if (next >= '0' && next <= '9'){ + is_oct = 1; + is_integer = 1; + } + else{ + is_zero = 1; + is_integer = 1; + } + } + else{ + is_zero = 1; + is_integer = 1; + } + } + else if (file.data[pos] == '.'){ + is_float = 1; + } + + if (is_zero){ + ++result.pos; + } + else if (is_hex){ + ++result.pos; + char character; + do{ + ++result.pos; + if (result.pos >= file.size){ + break; + } + character = file.data[result.pos]; + } while(char_is_hex(character)); + } + else if (is_oct){ + char character; + do{ + ++result.pos; + if (result.pos >= file.size){ + break; + } + character = file.data[result.pos]; + }while(char_is_numeric(character)); + } + else{ + if (!is_float){ + is_integer = 1; + while (1){ + ++result.pos; + + if (result.pos >= file.size){ + break; + } + bool is_good = 0; + char character = file.data[result.pos]; + if (character >= '0' && character <= '9'){ + is_good = 1; + } + else if (character == '.'){ + is_integer = 0; + is_float = 1; + } + if (!is_good){ + break; + } + } + } + + if (is_float){ + bool e_mode = 0; + bool e_minus = 0; + bool is_good = 0; + char character; + + while (1){ + ++result.pos; + if (result.pos >= file.size){ + break; + } + is_good = 0; + character = file.data[result.pos]; + if (character >= '0' && character <= '9'){ + is_good = 1; + } + else{ + if (character == 'e' && !e_mode){ + e_mode = 1; + is_good = 1; + } + else if (character == '-' && e_mode && !e_minus){ + e_minus = 1; + is_good = 1; + } + } + if (!is_good){ + break; + } + } + } + } + + if (is_integer){ + Sub_Match_List_Result match = + sub_match_list(file, result.pos, int_sufs, -1); + if (match.index != -1){ + result.pos = match.new_pos; + } + result.token.type = CPP_TOKEN_INTEGER_CONSTANT; + result.token.size = result.pos - result.token.start; + } + else if (is_float){ + Sub_Match_List_Result match = + sub_match_list(file, result.pos, float_sufs, -1); + if (match.index != -1){ + result.pos = match.new_pos; + } + result.token.type = CPP_TOKEN_FLOATING_CONSTANT; + result.token.size = result.pos - result.token.start; + } + else{ + _Assert(!"This shouldn't happen!"); + } + + return result; +} + +FCPP_LINK Cpp_Read_Result +cpp_read_string_litteral(Cpp_File file, int pos){ + Cpp_Read_Result result = {}; + result.token.start = pos; + + _Assert(file.data[pos] == '"'); + Seek_Result seek = seek_unescaped_delim(file.data, file.size, pos, '"'); + pos = seek.pos; + if (seek.new_line){ + result.token.flags |= CPP_TFLAG_MULTILINE; + } + + result.token.size = pos - result.token.start; + result.token.type = CPP_TOKEN_STRING_CONSTANT; + result.pos = pos; + + return result; +} + +FCPP_LINK Cpp_Read_Result +cpp_read_character_litteral(Cpp_File file, int pos){ + Cpp_Read_Result result = {}; + result.token.start = pos; + + _Assert(file.data[pos] == '\''); + Seek_Result seek = seek_unescaped_delim(file.data, file.size, pos, '\''); + pos = seek.pos; + if (seek.new_line){ + result.token.flags |= CPP_TFLAG_MULTILINE; + } + + result.token.size = pos - result.token.start; + result.token.type = CPP_TOKEN_CHARACTER_CONSTANT; + result.pos = pos; + + return result; +} + +FCPP_LINK Cpp_Read_Result +cpp_read_line_comment(Cpp_File file, int pos){ + Cpp_Read_Result result = {}; + result.token.start = pos; + + _Assert(file.data[pos] == '/' && file.data[pos + 1] == '/'); + + pos += 2; + while (pos < file.size){ + if (file.data[pos] == '\n'){ + break; + } + if (file.data[pos] == '\\'){ + if (pos + 1 < file.size && + file.data[pos + 1] == '\n'){ + ++pos; + } + else if (pos + 2 < file.size && + file.data[pos + 1] == '\r' && + file.data[pos + 2] == '\n'){ + pos += 2; + } + } + ++pos; + } + if (pos > 0 && file.data[pos-1] == '\r'){ + --pos; + } + result.token.size = pos - result.token.start; + result.token.type = CPP_TOKEN_COMMENT; + result.pos = pos; + return result; +} + +FCPP_LINK Cpp_Read_Result +cpp_read_block_comment(Cpp_File file, int pos){ + Cpp_Read_Result result = {}; + result.token.start = pos; + + _Assert(file.data[pos] == '/' && file.data[pos + 1] == '*'); + pos += 2; + while (pos < file.size){ + if (file.data[pos] == '*' && + pos + 1 < file.size && + file.data[pos+1] == '/'){ + break; + } + ++pos; + } + pos += 2; + result.token.size = pos - result.token.start; + result.token.type = CPP_TOKEN_COMMENT; + result.pos = pos; + return result; +} + +FCPP_LINK Cpp_Read_Result +cpp_read_preprocessor(Cpp_File file, int pos){ + _Assert(file.data[pos] == '#'); + Cpp_Read_Result result = {}; + result.token.start = pos; + result.token.type = CPP_PP_UNKNOWN; + result.token.flags |= CPP_TFLAG_PP_DIRECTIVE; + + ++pos; + while (pos < file.size && + (file.data[pos] == ' ' || + file.data[pos] == '\t')){ + ++pos; + } + + Sub_Match_List_Result match + = sub_match_list(file, pos, preprops, -1); + + if (match.index != -1){ + result.token.size = match.new_pos - result.token.start; + result.token.type = (Cpp_Token_Type)preprops.data[match.index].flags; + result.pos = match.new_pos; + } + else{ + while (pos < file.size && + !char_is_whitespace(file.data[pos])){ + ++pos; + } + result.token.size = pos - result.token.start; + result.pos = pos; + } + + return result; +} + +FCPP_LINK Cpp_Read_Result +cpp_read_pp_include_file(Cpp_File file, int pos){ + char start = file.data[pos]; + _Assert(start == '<' || start == '"'); + + Cpp_Read_Result result = {}; + result.token.start = pos; + result.token.type = CPP_TOKEN_INCLUDE_FILE; + result.token.flags |= CPP_TFLAG_PP_BODY; + + char end; + if (start == '<'){ + end = '>'; + } + else{ + end = '"'; + } + + ++pos; + while (pos < file.size && file.data[pos] != end){ + if (file.data[pos] == '\n'){ + result.token.type = CPP_TOKEN_JUNK; + result.token.flags |= CPP_TFLAG_BAD_ENDING; + break; + } + if (file.data[pos] == '\\'){ + // TODO(allen): Not sure that this is 100% correct. + if (pos + 1 < file.size && + file.data[pos + 1] == '\n'){ + ++pos; + result.token.flags |= CPP_TFLAG_MULTILINE; + } + else if (pos + 2 < file.size && + file.data[pos + 1] == '\r' && + file.data[pos + 2] == '\n'){ + pos += 2; + result.token.flags |= CPP_TFLAG_MULTILINE; + } + } + ++pos; + } + + if (result.token.type != CPP_TOKEN_JUNK){ + if (pos < file.size){ + ++pos; + } + } + + result.token.size = pos - result.token.start; + result.pos = pos; + + return result; +} + +FCPP_LINK Cpp_Read_Result +cpp_read_pp_default_mode(Cpp_File file, int pos, bool in_if_body){ + char current = file.data[pos]; + Cpp_Read_Result result; + if (char_is_numeric(current)){ + result = cpp_read_number(file, pos); + } + else if (char_is_alpha(current)){ + result = cpp_read_alpha_numeric(file, pos, in_if_body); + } + else if (current == '.'){ + if (pos + 1 < file.size){ + char next = file.data[pos + 1]; + if (char_is_numeric(next)){ + result = cpp_read_number(file, pos); + } + else{ + result = cpp_read_operator(file, pos); + } + } + else{ + result = cpp_read_operator(file, pos); + } + } + + else if (current == '/'){ + if (pos + 1 < file.size){ + char next = file.data[pos + 1]; + if (next == '/'){ + result = cpp_read_line_comment(file, pos); + } + else if (next == '*'){ + result = cpp_read_block_comment(file, pos); + } + else{ + result = cpp_read_operator(file, pos); + } + } + else{ + result = cpp_read_operator(file, pos); + } + } + else if (current == '"'){ + result = cpp_read_string_litteral(file, pos); + } + else if (current == '\''){ + result = cpp_read_character_litteral(file, pos); + } + else{ + result = cpp_read_operator(file, pos); + } + + return result; +} + +FCPP_LINK Cpp_Token_Merge +cpp_attempt_token_merge(Cpp_Token prev_token, Cpp_Token next_token){ + Cpp_Token_Merge result = {}; + if (next_token.type == CPP_TOKEN_COMMENT && prev_token.type == CPP_TOKEN_COMMENT && + next_token.flags == prev_token.flags && next_token.state_flags == prev_token.state_flags){ + result.did_merge = 1; + prev_token.size = next_token.start + next_token.size - prev_token.start; + result.new_token = prev_token; + } + else if (next_token.type == CPP_TOKEN_JUNK && prev_token.type == CPP_TOKEN_JUNK && + next_token.flags == prev_token.flags && next_token.state_flags == prev_token.state_flags){ + result.did_merge = 1; + prev_token.size = next_token.start + next_token.size - prev_token.start; + result.new_token = prev_token; + } + return result; +} + +FCPP_LINK bool +cpp_push_token_no_merge(Cpp_Token_Stack *token_stack, Cpp_Token token){ + if (token_stack->count >= token_stack->max_count){ + return 0; + } + + token_stack->tokens[token_stack->count++] = token; + return 1; +} + +FCPP_LINK bool +cpp_push_token_nonalloc(Cpp_Token_Stack *token_stack, Cpp_Token token){ + Cpp_Token_Merge merge = {}; + + if (token_stack->count > 0){ + Cpp_Token prev_token = token_stack->tokens[token_stack->count - 1]; + merge = cpp_attempt_token_merge(prev_token, token); + if (merge.did_merge){ + token_stack->tokens[token_stack->count - 1] = merge.new_token; + } + } + + if (!merge.did_merge){ + if (token_stack->count >= token_stack->max_count){ + return 0; + } + + token_stack->tokens[token_stack->count++] = token; + } + + return 1; +} + +FCPP_LINK Cpp_Read_Result +cpp_lex_step(Cpp_File file, Cpp_Lex_Data *lex_data){ + Cpp_Lex_Data lex = *lex_data; + Cpp_Read_Result result = {}; + bool has_result = 1; + + fcpp_u16 state_flags = cpp_token_set_pp_state(0, lex.pp_state); + + char current = file.data[lex.pos]; + if (char_is_whitespace(current)){ + result = cpp_read_whitespace(file, lex.pos); + lex.pos = result.pos; + if (result.newline && lex.pp_state != CPP_LEX_PP_DEFAULT){ + lex.pp_state = CPP_LEX_PP_DEFAULT; + } + has_result = 0; + } + + else{ + if (lex.pp_state == CPP_LEX_PP_DEFAULT){ + // TODO(allen): Not first hard of the line? Then it's junk. + if (current == '#'){ + result = cpp_read_preprocessor(file, lex.pos); + lex.pos = result.pos; + switch (result.token.type){ + case CPP_PP_INCLUDE: + case CPP_PP_IMPORT: + case CPP_PP_USING: + lex.pp_state = CPP_LEX_PP_INCLUDE; + break; + case CPP_PP_DEFINE: + lex.pp_state = CPP_LEX_PP_MACRO_IDENTIFIER; + break; + case CPP_PP_UNDEF: + case CPP_PP_IFDEF: + case CPP_PP_IFNDEF: + lex.pp_state = CPP_LEX_PP_IDENTIFIER; + break; + case CPP_PP_IF: + case CPP_PP_ELIF: + lex.pp_state = CPP_LEX_PP_BODY_IF; + break; + case CPP_PP_PRAGMA: + lex.pp_state = CPP_LEX_PP_BODY; + break; + case CPP_PP_LINE: + lex.pp_state = CPP_LEX_PP_NUMBER; + break; + case CPP_PP_ERROR: + case CPP_PP_UNKNOWN: + case CPP_PP_ELSE: + case CPP_PP_ENDIF: + lex.pp_state = CPP_LEX_PP_JUNK; + break; + } + } + else{ + result = cpp_read_pp_default_mode(file, lex.pos); + lex.pos = result.pos; + } + } + + else{ + if (current == '\\'){ + fcpp_i32 seek = lex.pos; + ++seek; + while (seek < file.size && file.data[seek] == '\r'){ + ++seek; + } + if ((seek < file.size && file.data[seek] == '\n') || seek >= file.size){ + lex.pos = seek + 1; + has_result = 0; + } + else{ + lex.pp_state = CPP_LEX_PP_JUNK; + result.token.type = CPP_TOKEN_JUNK; + result.token.start = lex.pos; + result.token.size = 1; + result.token.flags |= CPP_TFLAG_PP_BODY; + lex.pos = seek; + } + } + + else{ + if (lex.pp_state == CPP_LEX_PP_IDENTIFIER){ + if (!char_is_alpha_numeric(current)){ + has_result = 0; + lex.pp_state = CPP_LEX_PP_JUNK; + } + else{ + result = cpp_read_alpha_numeric(file, lex.pos); + result.token.flags |= CPP_TFLAG_PP_BODY; + lex.pos = result.pos; + lex.pp_state = CPP_LEX_PP_JUNK; + } + } + else if (lex.pp_state == CPP_LEX_PP_MACRO_IDENTIFIER){ + if (!char_is_alpha_numeric(current)){ + has_result = 0; + lex.pp_state = CPP_LEX_PP_JUNK; + } + else{ + result = cpp_read_alpha_numeric(file, lex.pos); + result.token.flags |= CPP_TFLAG_PP_BODY; + lex.pos = result.pos; + lex.pp_state = CPP_LEX_PP_BODY; + } + } + + else if (lex.pp_state == CPP_LEX_PP_INCLUDE){ + if (current != '"' && current != '<'){ + has_result = 0; + lex.pp_state = CPP_LEX_PP_JUNK; + } + else{ + result = cpp_read_pp_include_file(file, lex.pos); + lex.pos = result.pos; + lex.pp_state = CPP_LEX_PP_JUNK; + } + } + else if (lex.pp_state == CPP_LEX_PP_BODY){ + if (current == '#'){ + result = cpp_read_pp_operator(file, lex.pos); + } + else{ + result = cpp_read_pp_default_mode(file, lex.pos); + } + lex.pos = result.pos; + result.token.flags |= CPP_TFLAG_PP_BODY; + } + else if (lex.pp_state == CPP_LEX_PP_BODY_IF){ + if (current == '#'){ + result = cpp_read_pp_operator(file, lex.pos); + } + else{ + result = cpp_read_pp_default_mode(file, lex.pos, 1); + } + lex.pos = result.pos; + result.token.flags |= CPP_TFLAG_PP_BODY; + } + + else if (lex.pp_state == CPP_LEX_PP_NUMBER){ + if (!char_is_numeric(current)){ + has_result = 0; + lex.pp_state = CPP_LEX_PP_JUNK; + } + else{ + result = cpp_read_number(file, lex.pos); + lex.pos = result.pos; + result.token.flags |= CPP_TFLAG_PP_BODY; + lex.pp_state = CPP_LEX_PP_INCLUDE; + } + } + else{ + bool took_comment = 0; + if (current == '/' && lex.pos + 1 < file.size){ + if (file.data[lex.pos + 1] == '/'){ + result = cpp_read_line_comment(file, lex.pos); + lex.pp_state = CPP_LEX_PP_DEFAULT; + lex.pos = result.pos; + took_comment = 1; + }else if (file.data[lex.pos + 1] == '*'){ + result = cpp_read_block_comment(file, lex.pos); + lex.pos = result.pos; + took_comment = 1; + } + } + + if (!took_comment){ + result = cpp_read_junk_line(file, lex.pos); + lex.pos = result.pos; + result.token.flags |= CPP_TFLAG_PP_BODY; + } + } + } + } + } + + result.token.state_flags = state_flags; + result.has_result = has_result; + + *lex_data = lex; + return result; +} + +FCPP_LINK int +cpp_lex_file_token_count(Cpp_File file){ + int count = 0; + Cpp_Lex_Data lex = {}; + Cpp_Token token = {}; + while (lex.pos < file.size){ + Cpp_Read_Result step_result = cpp_lex_step(file, &lex); + + if (step_result.has_result){ + if (count > 0){ + Cpp_Token_Merge merge = cpp_attempt_token_merge(token, step_result.token); + if (merge.did_merge){ + token = merge.new_token; + } + else{ + token = step_result.token; + ++count; + } + } + else{ + token = step_result.token; + ++count; + } + } + } + return count; +} + +FCPP_LINK Cpp_Lex_Data +cpp_lex_file_nonalloc(Cpp_File file, Cpp_Token_Stack *token_stack_out, Cpp_Lex_Data data){ + while (data.pos < file.size){ + Cpp_Lex_Data prev_lex = data; + Cpp_Read_Result step_result = cpp_lex_step(file, &data); + + if (step_result.has_result){ + if (!cpp_push_token_nonalloc(token_stack_out, step_result.token)){ + data = prev_lex; + return data; + } + } + } + + data.complete = 1; + return data; +} + +FCPP_LINK Cpp_Get_Token_Result +cpp_get_token(Cpp_Token_Stack *token_stack, int pos){ + int first, last; + first = 0; + last = token_stack->count; + + Cpp_Get_Token_Result result = {}; + while (1){ + result.token_index = (first + last)/2; + + int this_start = token_stack->tokens[result.token_index].start; + int next_start; + if (result.token_index + 1 < token_stack->count){ + next_start = token_stack->tokens[result.token_index+1].start; + } + else{ + next_start = this_start + token_stack->tokens[result.token_index].size; + } + if (this_start <= pos && pos < next_start){ + break; + } + else if (pos < this_start){ + last = result.token_index; + } + else{ + first = result.token_index + 1; + } + if (first == last){ + result.token_index = first; + break; + } + } + + if (result.token_index == token_stack->count){ + --result.token_index; + result.in_whitespace = 1; + } + else{ + Cpp_Token *token = token_stack->tokens + result.token_index; + if (token->start + token->size <= pos){ + result.in_whitespace = 1; + } + } + + return result; +} + +FCPP_LINK int +cpp_get_end_token(Cpp_Token_Stack *stack, int end){ + Cpp_Get_Token_Result result = cpp_get_token(stack, end); + if (result.token_index < 0) result.token_index = 0; + else if (end > stack->tokens[result.token_index].start) ++result.token_index; + return result.token_index; +} + +FCPP_LINK void +cpp_shift_token_starts(Cpp_Token_Stack *stack, int from_token_i, int amount){ + int count = stack->count; + Cpp_Token *token = stack->tokens + from_token_i; + for (int i = from_token_i; i < count; ++i, ++token){ + token->start += amount; + } +} + +FCPP_LINK Cpp_Relex_State +cpp_relex_nonalloc_start(Cpp_File file, Cpp_Token_Stack *stack, + int start, int end, int amount, int tolerance){ + Cpp_Relex_State state; + state.file = file; + state.stack = stack; + state.start = start; + state.end = end; + state.amount = amount; + state.tolerance = tolerance; + + Cpp_Get_Token_Result result = cpp_get_token(stack, start); + if (result.token_index <= 0){ + state.start_token_i = 0; + } + else{ + state.start_token_i = result.token_index-1; + } + + result = cpp_get_token(stack, end); + if (result.token_index < 0) result.token_index = 0; + else if (end > stack->tokens[result.token_index].start) ++result.token_index; + state.end_token_i = result.token_index; + + state.relex_start = stack->tokens[state.start_token_i].start; + if (start < state.relex_start) state.relex_start = start; + + state.space_request = state.end_token_i - state.start_token_i + tolerance + 1; + + return state; +} + +inline Cpp_Token +cpp__get_token(Cpp_Token_Stack *stack, Cpp_Token *tokens, int size, int index){ + Cpp_Token result; + if (index < stack->count){ + result = tokens[index]; + } + else{ + result.start = size; + result.size = 0; + result.type = CPP_TOKEN_EOF; + result.flags = 0; + result.state_flags = 0; + } + return result; +} + +FCPP_LINK bool +cpp_relex_nonalloc_main(Cpp_Relex_State *state, Cpp_Token_Stack *relex_stack, int *relex_end){ + Cpp_Token_Stack *stack = state->stack; + Cpp_Token *tokens = stack->tokens; + + cpp_shift_token_starts(stack, state->end_token_i, state->amount); + + Cpp_Lex_Data lex = {}; + lex.pp_state = cpp_token_get_pp_state(tokens[state->start_token_i].state_flags); + lex.pos = state->relex_start; + + int relex_end_i = state->end_token_i; + Cpp_Token match_token = cpp__get_token(stack, tokens, state->file.size, relex_end_i); + Cpp_Token end_token = match_token; + bool went_too_far = 0; + + for (;;){ + Cpp_Read_Result read = cpp_lex_step(state->file, &lex); + if (read.has_result){ + if (read.token.start == match_token.start && + read.token.size == match_token.size && + read.token.flags == match_token.flags && + read.token.state_flags == match_token.state_flags){ + break; + } + cpp_push_token_nonalloc(relex_stack, read.token); + + while (lex.pos > end_token.start && relex_end_i < stack->count){ + ++relex_end_i; + end_token = cpp__get_token(stack, tokens, state->file.size, relex_end_i); + } + if (relex_stack->count == relex_stack->max_count){ + went_too_far = 1; + break; + } + } + if (lex.pos >= state->file.size) break; + } + + if (!went_too_far){ + if (relex_stack->count > 0){ + if (state->start_token_i > 0){ + Cpp_Token_Merge merge = + cpp_attempt_token_merge(tokens[state->start_token_i - 1], + relex_stack->tokens[0]); + if (merge.did_merge){ + --state->start_token_i; + relex_stack->tokens[0] = merge.new_token; + } + } + + if (relex_end_i < state->stack->count){ + Cpp_Token_Merge merge = + cpp_attempt_token_merge(relex_stack->tokens[relex_stack->count-1], + tokens[relex_end_i]); + if (merge.did_merge){ + ++relex_end_i; + relex_stack->tokens[relex_stack->count-1] = merge.new_token; + } + } + } + + *relex_end = relex_end_i; + } + else{ + cpp_shift_token_starts(stack, state->end_token_i, -state->amount); + } + + return went_too_far; +} + +#ifndef FCPP_FORBID_MALLOC +FCPP_LINK Cpp_Token_Stack +cpp_make_token_stack(int starting_max){ + Cpp_Token_Stack token_stack; + token_stack.count = 0; + token_stack.max_count = starting_max; + token_stack.tokens = (Cpp_Token*)FCPP_GET_MEMORY(sizeof(Cpp_Token)*starting_max); + return token_stack; +} + +FCPP_LINK void +cpp_free_token_stack(Cpp_Token_Stack token_stack){ + FCPP_FREE_MEMORY(token_stack.tokens); +} + +FCPP_LINK void +cpp_resize_token_stack(Cpp_Token_Stack *token_stack, int new_max){ + Cpp_Token *new_tokens = (Cpp_Token*)FCPP_GET_MEMORY(sizeof(Cpp_Token)*new_max); + + if (new_tokens){ + FCPP_MEM_COPY(new_tokens, token_stack->tokens, sizeof(Cpp_Token)*token_stack->count); + FCPP_FREE_MEMORY(token_stack->tokens); + token_stack->tokens = new_tokens; + token_stack->max_count = new_max; + } +} + +FCPP_LINK void +cpp_push_token(Cpp_Token_Stack *token_stack, Cpp_Token token){ + if (!cpp_push_token_nonalloc(token_stack, token)){ + int new_max = 2*token_stack->max_count + 1; + cpp_resize_token_stack(token_stack, new_max); + bool result = cpp_push_token_nonalloc(token_stack, token); + _Assert(result); + } +} + +FCPP_LINK void +cpp_lex_file(Cpp_File file, Cpp_Token_Stack *token_stack_out){ + Cpp_Lex_Data lex = {}; + while (lex.pos < file.size){ + Cpp_Read_Result step_result = cpp_lex_step(file, &lex); + if (step_result.has_result){ + cpp_push_token(token_stack_out, step_result.token); + } + } +} + +FCPP_LINK bool +cpp_relex_file_limited(Cpp_File file, Cpp_Token_Stack *stack, + int start, int end, int amount, int tolerance){ +#if 0 + int start_token_i, end_token_i; + Cpp_Get_Token_Result get_result = cpp_get_token(token_stack, start_i); + start_token_i = get_result.token_index; + get_result = cpp_get_token(token_stack, end_i); + end_token_i = get_result.token_index; + if (end_token_i == -1){ + end_token_i = 0; + } + else if (end > token_stack->tokens[end_token_i].start){ + ++end_token_i; + } + cpp_shift_token_starts(token_stack, end_token_i, amount); + + int relex_start_i = start_token_i - 1; + if (relex_start_i < 0){ + relex_start_i = 0; + } + + int end_guess_i = end_token_i + 1; + if (end_guess_i > token_stack->count){ + --end_guess_i; + } +#endif + + int relex_start_i; + int end_token_i, end_guess_i; + { + Cpp_Get_Token_Result result = cpp_get_token(stack, start); + if (result.token_index <= 0){ + relex_start_i = 0; + } + else{ + relex_start_i = result.token_index-1; + } + + result = cpp_get_token(stack, end); + if (result.token_index < 0) result.token_index = 0; + else if (end > stack->tokens[result.token_index].start) ++result.token_index; + end_token_i = result.token_index; + end_guess_i = result.token_index+1; + } + + int relex_start = stack->tokens[relex_start_i].start; + if (start < relex_start) relex_start = start; + + cpp_shift_token_starts(stack, end_token_i, amount); + Cpp_Token_Stack relex_stack = cpp_make_token_stack((end_guess_i - relex_start_i + 1) * 3 / 2); + Cpp_Lex_Data lex = {}; + lex.pp_state = cpp_token_get_pp_state(stack->tokens[relex_start_i].state_flags); + lex.pos = relex_start; + bool went_too_far = 0; + + while (1){ + Cpp_Read_Result result = cpp_lex_step(file, &lex); + if (result.has_result){ + if (end_guess_i < stack->count && + result.token.start == stack->tokens[end_guess_i].start && + result.token.size == stack->tokens[end_guess_i].size && + result.token.flags == stack->tokens[end_guess_i].flags && + result.token.state_flags == stack->tokens[end_guess_i].state_flags){ + break; + } + else{ + cpp_push_token(&relex_stack, result.token); + while (lex.pos > stack->tokens[end_guess_i].start && + end_guess_i < stack->count){ + ++end_guess_i; + } + } + } + + if (lex.pos >= file.size){ + break; + } + + if (tolerance >= 0 && relex_stack.count + relex_start_i >= end_guess_i + tolerance){ + went_too_far = 1; + break; + } + } + + if (!went_too_far){ + int relex_end_i = end_guess_i; + + if (relex_stack.count > 0){ + if (relex_start_i > 0){ + Cpp_Token_Merge merge = cpp_attempt_token_merge(stack->tokens[relex_start_i - 1], + relex_stack.tokens[0]); + if (merge.did_merge){ + --relex_start_i; + relex_stack.tokens[0] = merge.new_token; + } + } + + if (relex_end_i < stack->count){ + Cpp_Token_Merge merge = cpp_attempt_token_merge(relex_stack.tokens[relex_stack.count - 1], + stack->tokens[relex_end_i]); + if (merge.did_merge){ + ++relex_end_i; + relex_stack.tokens[relex_stack.count - 1] = merge.new_token; + } + } + } + + int token_delete_amount = relex_end_i - relex_start_i; + int token_shift_amount = relex_stack.count - token_delete_amount; + + if (token_shift_amount != 0){ + int new_token_count = stack->count + token_shift_amount; + if (new_token_count > stack->max_count){ + int new_max = 2*stack->max_count + 1; + while (new_token_count > new_max){ + new_max = 2*new_max + 1; + } + cpp_resize_token_stack(stack, new_max); + } + + if (relex_end_i < stack->count){ + FCPP_MEM_MOVE(stack->tokens + relex_end_i + token_shift_amount, + stack->tokens + relex_end_i, sizeof(Cpp_Token)*(stack->count - relex_end_i)); + } + + stack->count += token_shift_amount; + } + + FCPP_MEM_COPY(stack->tokens + relex_start_i, relex_stack.tokens, sizeof(Cpp_Token)*relex_stack.count); + cpp_free_token_stack(relex_stack); + } + + else{ + cpp_shift_token_starts(stack, end_token_i, -amount); + cpp_free_token_stack(relex_stack); + } + + return went_too_far; +} +#endif + +#undef _Assert +#undef _TentativeAssert + +#undef FCPP_LEXER_IMPLEMENTATION +#endif // #ifdef FCPP_LEXER_IMPLEMENTATION + +// BOTTOM diff --git a/4cpp_preprocessor.cpp b/4cpp_preprocessor.cpp new file mode 100644 index 00000000..b5376846 --- /dev/null +++ b/4cpp_preprocessor.cpp @@ -0,0 +1,2058 @@ + +// TOP + +// TODO(allen): +// error check the body of macros as they are first read in +// (as in, check the thing after # is a parameter, and that ## is not on one of the ends) + +#define _Assert Assert +#define _TentativeAssert TentativeAssert + +enum Cpp_Def_Type{ + CPP_DEFTYPE_ERROR, + CPP_DEFTYPE_FILE, + CPP_DEFTYPE_MACRO, + CPP_DEFTYPE_COUNT +}; + +struct Table_Entry{ + String name; + Cpp_Def_Type type; + fcpp_u32 hash; + int index; +}; + +struct Table{ + Table_Entry *table; + int size, max_size; +}; + +internal fcpp_u32 +get_hash(String name, Cpp_Def_Type type){ + fcpp_u32 x = 5381; + int i = 0; + char c; + while (i < name.size){ + c = name.str[i++]; + x = ((x << 5) + x) + c; + } + x += (fcpp_u32)(type)*13; + return x; +} + +internal bool +table_insert(Table *table, Table_Entry info){ + Table_Entry entry; + int index; + + info.hash = get_hash(info.name, info.type); + index = info.hash % table->max_size; + while ((entry = table->table[index]).name.str && entry.index != -1){ + if (entry.hash == info.hash && entry.type == info.type && match(entry.name, info.name)){ + return 1; + } + index = (index + 1) % table->max_size; + } + table->table[index] = info; + ++table->size; + return 0; +} + +internal bool +table_find_entry(Table *table, String name, Cpp_Def_Type type, int *index_out){ + fcpp_u32 hash = get_hash(name, type); + int index = hash % table->max_size; + Table_Entry entry; + while ((entry = table->table[index]).name.str){ + if (entry.index != -1 && entry.hash == hash && entry.type == type && match(entry.name, name)){ + *index_out = index; + return 1; + } + index = (index + 1) % table->max_size; + } + return 0; +} + +internal bool +table_find(Table *table, String name, Cpp_Def_Type type, int *index_out){ + bool result; + int entry_index; + result = table_find_entry(table, name, type, &entry_index); + if (result){ + *index_out = table->table[entry_index].index; + } + return result; +} + +internal bool +table_drop(Table *table, String name, Cpp_Def_Type type){ + bool result; + int entry_index; + result = table_find_entry(table, name, type, &entry_index); + if (result){ + table->table[entry_index].index = -1; + } + return result; +} + +internal void +table_copy(Table *table_src, Table *table_dst){ + Table_Entry entry; + int i; + for (i = 0; i < table_src->max_size; ++i){ + entry = table_src->table[i]; + if (entry.name.str){ + table_insert(table_dst, entry); + } + } +} + +// TODO(allen): File_Data not Parse_File +struct Cpp_Parse_File{ + Cpp_File file; + Cpp_Token_Stack tokens; + String filename; +}; + +struct Cpp_Macro_Data{ + int file_index; + int token_index; + int param_count; + int first_param_index; + int body_start_index; + int body_end_index; +}; + +union Cpp_Def_Slot{ + Cpp_Parse_File file; + Cpp_Macro_Data macro; +}; + +struct Cpp_Loose_Token{ + int file_index; + int token_index; + int blocked; +}; + +struct Cpp_Loose_Token_Stack{ + Cpp_Loose_Token *tokens; + int count, max; +}; + +struct Cpp_Parse_Context{ + int preserve_chunk_size; +}; + +struct Cpp_Parse_Definitions{ + Table table; + Cpp_Def_Slot *slots; + int count, max; + + int string_file_index; + int string_write_pos; + + Cpp_Loose_Token eof_token; + Cpp_Loose_Token va_args_token; +}; + +struct Cpp_Visit{ + int file_index; + int token_index; + int blocked; +}; + +struct Cpp_Expansion{ + int position; + int end_position; + int file_index; + int macro_index; + int stack_base; + int stack_pop_pos; + int out_rule; + int out_type; + int param_info_base; + int invoking_file_index; + int invoking_token_index; + + bool pop_and_drop; + bool may_have_pp; + bool is_file_level; +}; + +struct Cpp_Macro_Reading_Vars{ + int name_index; + int first_param_index; + int param_count; + int body_start_index; +}; + +struct Cpp_Macro_Invoking_Vars{ + int file_index; + int token_index; + int invoking_file_index; + int invoking_token_index; + int macro_index; + int param_count; + int paren_level; + int stack_base; + int param_info_base; + int variadic; +}; + +struct Cpp_Token_Range{ + int start, end; +}; + +struct Cpp_Preproc_State{ + Cpp_Expansion expansions[256]; + Cpp_Token_Range param_info[192]; + int expansion_level; + int param_info_used; + + Cpp_Loose_Token_Stack tokens; + + int state; + + Cpp_Macro_Reading_Vars mac_read; + Cpp_Macro_Invoking_Vars mac_inv; + + char *spare_string; + int spare_string_write_pos; + int spare_string_size; + + bool resizing_slots; + bool finished; +}; + +enum Memory_Request_Purpse{ + MEMPURP_NONE, + MEMPURP_SPARE_STRING, + MEMPURP_PRESERVE_FILE, + MEMPURP_TOKEN_STACK, + MEMPURP_DEFINITION_SLOTS +}; + +struct Cpp_Preproc_Result{ + int file_index; + int token_index; + int blocked; + + int invoking_file_index; + int invoking_token_index; + + int error_code; + int memory_request; + Memory_Request_Purpse memory_purpose; + bool emit; + bool from_macro; + bool file_request; +}; + +struct Cpp_Memory_Request{ + Cpp_Preproc_State *state; + Cpp_Parse_Definitions *definitions; + int size; + Memory_Request_Purpse purpose; +}; + +struct Cpp_File_Request{ + String filename; +}; + +internal int +cpp_defs_add(Cpp_Parse_Definitions *defs, String name, Cpp_Def_Type type){ + int result; + _Assert(defs->count < defs->max); + result = defs->count++; + defs->slots[result] = {}; + + if (name.str != 0){ + _Assert(defs->table.size * 7 < defs->table.max_size * 8); + Table_Entry entry; + entry.name = name; + entry.type = type; + entry.index = result; + table_insert(&defs->table, entry); + } + + return result; +} + +internal Cpp_Memory_Request +cpp_get_memory_request(Cpp_Preproc_State *state, Cpp_Parse_Definitions *definitions, + Cpp_Preproc_Result result){ + Cpp_Memory_Request request = {}; + request.state = state; + request.definitions = definitions; + request.size = result.memory_request; + request.purpose = result.memory_purpose; + return request; +} + +internal Cpp_Parse_File* +cpp_get_parse_file(Cpp_Parse_Definitions *definitions, int file_index){ + return &definitions->slots[file_index].file; +} + +internal void +cpp_set_parse_file(Cpp_Parse_Definitions *definitions, int file_index, Cpp_Parse_File file){ + definitions->slots[file_index].file = file; +} + +internal Cpp_Macro_Data* +cpp_get_macro_data(Cpp_Parse_Definitions *definitions, int macro_index){ + return &definitions->slots[macro_index].macro; +} + +internal void +cpp_set_macro_data(Cpp_Parse_Definitions *definitions, int macro_index, Cpp_Macro_Data macro){ + definitions->slots[macro_index].macro = macro; +} + +internal void* +cpp_provide_memory(Cpp_Memory_Request request, void *memory){ + void *result = 0; + switch (request.purpose){ + case MEMPURP_SPARE_STRING: + { + Cpp_Preproc_State *state = request.state; + result = state->spare_string; + memcpy(memory, result, state->spare_string_size); + state->spare_string_size = request.size; + state->spare_string = (char*)memory; + }break; + + case MEMPURP_PRESERVE_FILE: + { + persist String string_filename = make_lit_string("~ string space"); + + Cpp_Parse_Definitions *definitions = request.definitions; + int size = request.size >> 1; + Cpp_Parse_File new_file = {}; + new_file.tokens.tokens = (Cpp_Token*)memory; + new_file.tokens.max_count = size / sizeof(Cpp_Token); + new_file.file.data = ((char*)memory) + size; + new_file.file.size = size; + new_file.filename = string_filename; + + int string_index = cpp_defs_add(definitions, {}, CPP_DEFTYPE_FILE); + definitions->string_write_pos = 0; + definitions->string_file_index = string_index; + cpp_set_parse_file(definitions, string_index, new_file); + }break; + + case MEMPURP_TOKEN_STACK: + { + Cpp_Preproc_State *state = request.state; + result = state->tokens.tokens; + memcpy(memory, result, sizeof(Cpp_Loose_Token)*state->tokens.count); + state->tokens.max = request.size / sizeof(Cpp_Loose_Token); + state->tokens.tokens = (Cpp_Loose_Token*)memory; + }break; + + case MEMPURP_DEFINITION_SLOTS: + { + Cpp_Preproc_State *state = request.state; + Cpp_Parse_Definitions *definitions = request.definitions; + if (state->resizing_slots){ + result = definitions->slots; + memcpy(memory, result, sizeof(Cpp_Def_Slot)*(definitions->count)); + definitions->slots = (Cpp_Def_Slot*)memory; + definitions->max = request.size / sizeof(Cpp_Def_Slot); + } + else{ + result = definitions->table.table; + Table new_table; + new_table.table = (Table_Entry*)memory; + new_table.size = 0; + new_table.max_size = request.size / sizeof(Table_Entry); + memset(new_table.table, 0, request.size); + table_copy(&definitions->table, &new_table); + definitions->table = new_table; + } + }break; + + default: + { + _Assert(!"unrecognized memory request"); + }break; + } + return result; +} + +internal Cpp_File_Request +cpp_get_file_request(Cpp_Preproc_State *state, Cpp_Preproc_Result result){ + Cpp_File_Request request = {}; + return request; +} + +internal bool +cpp_has_more_files(Cpp_File_Request *request){ + return 0; +} + +internal void +cpp_get_next_file(Cpp_File_Request *request){ +} + +internal bool +cpp_try_reuse_file(Cpp_File_Request *request){ + return 0; +} + +internal void +cpp_provide_file(Cpp_File_Request *request, Cpp_File new_file, Cpp_Token_Stack new_tokens){ +} + +enum Preproc_Error_Code{ + PPERR_NO_ERROR, + PPERR_UNFINISHED_DEFINE, + PPERR_DEFINE_EXPECTED_IDENTIFIER, + PPERR_MACRO_NONIDENTIFIER_ARG, + PPERR_MACRO_INCOMPLETE_PARAMS, + PPERR_MACRO_EXPECTED_COMMA, + PPERR_MACRO_MORE_AFTER_ELLIPSIS, + PPERR_MACROINV_TOO_FEW_PARAMS, + PPERR_MACROINV_TOO_MANY_PARAMS, + PPERR_EXPANSION_OVERRUN, + PPERR_INCOMPLETE_MACROINV +}; + +internal String +cpp_get_error(int error_code){ + String error = {}; + switch (error_code){ + case PPERR_NO_ERROR: + error = make_lit_string("no error"); break; + + case PPERR_UNFINISHED_DEFINE: + error = make_lit_string("define directive is unfinished"); break; + + case PPERR_DEFINE_EXPECTED_IDENTIFIER: + error = make_lit_string("define expected identifier for name"); break; + + case PPERR_MACRO_NONIDENTIFIER_ARG: + error = make_lit_string("macro argument must be an identifier"); break; + + case PPERR_MACRO_INCOMPLETE_PARAMS: + error = make_lit_string("macro incomplete parameters"); break; + + case PPERR_MACRO_EXPECTED_COMMA: + error = make_lit_string("macro expected comma between parameters"); break; + + case PPERR_MACRO_MORE_AFTER_ELLIPSIS: + error = make_lit_string("should not have more after the ellipsis"); break; + + case PPERR_EXPANSION_OVERRUN: + error = make_lit_string("nested expansion of files and macros ran too deep"); break; + + case PPERR_MACROINV_TOO_FEW_PARAMS: + error = make_lit_string("not enough arguments to macro invokation"); break; + + case PPERR_MACROINV_TOO_MANY_PARAMS: + error = make_lit_string("too many arguments to macro invokation"); break; + + case PPERR_INCOMPLETE_MACROINV: + error = make_lit_string("unexpected end of file in macro expansion"); break; + } + return error; +} + +internal bool +cpp_recommend_termination(int error_code){ + bool result = 0; + switch (error_code){ + case PPERR_EXPANSION_OVERRUN: + result = 1; + } + return result; +} + +struct Spare_String_Checkpoint{ + int write_pos_start; + int memory_overrun; +}; + +internal Spare_String_Checkpoint +cpp__checkpoint_spare_string(Cpp_Preproc_State *state){ + Spare_String_Checkpoint result = {}; + result.write_pos_start = state->spare_string_write_pos; + return result; +} + +internal void +cpp__spare_write(Spare_String_Checkpoint *check, Cpp_Preproc_State *state, char c){ + if (state->spare_string_write_pos < state->spare_string_size){ + state->spare_string[state->spare_string_write_pos++] = c; + } + else{ + ++check->memory_overrun; + } +} + +internal void +cpp__spare_write(Spare_String_Checkpoint *check, Cpp_Preproc_State *state, String str){ + if (state->spare_string_write_pos + str.size <= state->spare_string_size){ + memcpy(state->spare_string + state->spare_string_write_pos, str.str, str.size); + state->spare_string_write_pos += str.size; + } + else{ + check->memory_overrun += str.size; + } +} + +internal void +cpp__restore_spare_string(Cpp_Preproc_State *state, Spare_String_Checkpoint check){ + state->spare_string_write_pos = check.write_pos_start; +} + +struct Preserve_Checkpoint{ + int start_write_pos; + int start_token_count; + bool out_of_memory; +}; + +internal Preserve_Checkpoint +cpp__checkpoint_preserve_write(Cpp_Parse_Definitions *definitions){ + Cpp_Parse_File *file = cpp_get_parse_file(definitions, definitions->string_file_index); + Preserve_Checkpoint check; + check.start_write_pos = definitions->string_write_pos; + check.start_token_count = file->tokens.count; + check.out_of_memory = 0; + return check; +} + +internal void +cpp__restore_preserve_write(Cpp_Parse_Definitions *definitions, Preserve_Checkpoint check){ + Cpp_Parse_File *file = cpp_get_parse_file(definitions, definitions->string_file_index); + definitions->string_write_pos = check.start_write_pos; + file->tokens.count = check.start_token_count; +} + +internal void +cpp__preserve_string(Cpp_Parse_Definitions *definitions, String string){ + Cpp_Parse_File *string_file = cpp_get_parse_file(definitions, definitions->string_file_index); + _Assert(string_file->file.size - definitions->string_write_pos >= string.size); + copy_fast_unsafe(string_file->file.data + definitions->string_write_pos, string); + definitions->string_write_pos += string.size; +} + +internal Cpp_Loose_Token +cpp__preserve_token(Cpp_Parse_Definitions *definitions, Cpp_Token token){ + Cpp_Parse_File *string_file = cpp_get_parse_file(definitions, definitions->string_file_index); + _Assert(string_file->tokens.count < string_file->tokens.max_count); + Cpp_Loose_Token loose; + loose.file_index = definitions->string_file_index; + loose.token_index = string_file->tokens.count; + loose.blocked = 0; + string_file->tokens.tokens[string_file->tokens.count++] = token; + return loose; +} + +internal void +cpp__preserve_string(Preserve_Checkpoint *check, Cpp_Parse_Definitions *definitions, String string){ + if (!check->out_of_memory){ + Cpp_Parse_File *string_file = cpp_get_parse_file(definitions, definitions->string_file_index); + if (string_file->file.size - definitions->string_write_pos >= string.size){ + copy_fast_unsafe(string_file->file.data + definitions->string_write_pos, string); + definitions->string_write_pos += string.size; + } + else{ + check->out_of_memory = 1; + } + } +} + +internal Cpp_Loose_Token +cpp__preserve_token(Preserve_Checkpoint *check, Cpp_Parse_Definitions *definitions, Cpp_Token token){ + Cpp_Loose_Token loose = {}; + if (!check->out_of_memory){ + Cpp_Parse_File *string_file = cpp_get_parse_file(definitions, definitions->string_file_index); + if (string_file->tokens.count < string_file->tokens.max_count){ + loose.file_index = definitions->string_file_index; + loose.token_index = string_file->tokens.count; + loose.blocked = 0; + string_file->tokens.tokens[string_file->tokens.count++] = token; + } + else{ + check->out_of_memory = 1; + } + } + return loose; +} + +internal bool +cpp__can_push_loose_token(Cpp_Preproc_State *state, int count){ + return (state->tokens.count + count <= state->tokens.max); +} + +internal void +cpp__push_loose_token(Cpp_Preproc_State *state, int file_index, int token_index, int blocked){ + _Assert(state->tokens.count < state->tokens.max); + state->tokens.tokens[state->tokens.count++] = {file_index, token_index, blocked}; +} + +struct Token_Stack_Checkpoint{ + int start_count; + int memory_overrun; +}; + +internal Token_Stack_Checkpoint +cpp__checkpoint_token_stack(Cpp_Preproc_State *state){ + Token_Stack_Checkpoint result = {}; + result.start_count = state->tokens.count; + return result; +} + +internal void +cpp__restore_token_stack(Cpp_Preproc_State *state, Token_Stack_Checkpoint checkpoint){ + state->tokens.count = checkpoint.start_count; +} + +internal void +cpp__push_loose_token(Token_Stack_Checkpoint *checkpoint, Cpp_Preproc_State *state, + int file_index, int token_index, int blocked){ + if (state->tokens.count < state->tokens.max){ + cpp__push_loose_token(state, file_index, token_index, blocked); + } + else{ + ++checkpoint->memory_overrun; + } +} + +internal void +cpp__finish_macro(Cpp_Parse_Definitions *definitions, String name, + int file_index, int token_index, + int param_count, int first_param_index, + int body_start_index, int body_end_index){ + Cpp_Macro_Data macro; + macro.file_index = file_index; + macro.token_index = token_index; + macro.param_count = param_count; + macro.first_param_index = first_param_index; + macro.body_start_index = body_start_index; + macro.body_end_index = body_end_index; + + int macro_index = cpp_defs_add(definitions, name, CPP_DEFTYPE_MACRO); + cpp_set_macro_data(definitions, macro_index, macro); +} + +enum Cpp_Expansion_Type{ + EXPAN_FILE, + EXPAN_MACRO, + EXPAN_ARG, + EXPAN_PMACRO_BODY +}; + +enum Cpp_Expansion_Out_Type{ + EXPAN_NORMAL, + EXPAN_BIG_PROCESS_ARGS, + EXPAN_BIG_PROCESS_BODY, + EXPAN_BIG_DIRECT_OUT, + EXPAN_EOF_FLUSH, + EXPAN_STRFY +}; + +internal Cpp_Expansion* +cpp__push_expansion_raw(Cpp_Preproc_State *state, Cpp_Expansion *expansion){ + if (state->expansion_level == ArrayCount(state->expansions)){ + return 0; + } + + ++state->expansion_level; + Cpp_Expansion *result = state->expansions + state->expansion_level; + *result = *expansion; + return result; +} + +internal void +cpp__set_expansion(Cpp_Preproc_State *state, Cpp_Expansion *expansion, Cpp_Expansion_Type type, + int file_index, int start, int end, int out_rule, + int invoking_file_index, int invoking_token_index){ + expansion->position = start; + expansion->end_position = end; + expansion->file_index = file_index; + expansion->macro_index = 0; + expansion->out_rule = out_rule; + expansion->out_type = EXPAN_NORMAL; + expansion->invoking_file_index = invoking_file_index; + expansion->invoking_token_index = invoking_token_index; + if (state->expansion_level >= 1){ + expansion->stack_base = state->expansions[state->expansion_level-1].stack_base; + } + else{ + expansion->stack_base = 0; + } + + switch (type){ + case EXPAN_FILE: + expansion->may_have_pp = 1; + expansion->is_file_level = 1; + expansion->pop_and_drop = 0; + break; + + case EXPAN_MACRO: + expansion->may_have_pp = 0; + expansion->is_file_level = 0; + expansion->pop_and_drop = 0; + break; + + case EXPAN_ARG: + expansion->may_have_pp = 0; + expansion->is_file_level = 1; + expansion->pop_and_drop = 0; + break; + + case EXPAN_PMACRO_BODY: + expansion->may_have_pp = 0; + expansion->is_file_level = 0; + expansion->pop_and_drop = 1; + expansion->stack_pop_pos = state->tokens.count; + break; + } +} + +internal Cpp_Expansion* +cpp__push_expansion(Cpp_Preproc_State *state, Cpp_Expansion_Type type, + int file_index, int start, int end, int out_rule, + int invoking_file_index, int invoking_token_index){ + _Assert(start < end); + if (state->expansion_level == ArrayCount(state->expansions)){ + return 0; + } + + ++state->expansion_level; + Cpp_Expansion *result = state->expansions + state->expansion_level; + cpp__set_expansion(state, result, type, file_index, start, end, out_rule, + invoking_file_index, invoking_token_index); + return result; +} + +internal void +cpp__set_strfy_expansion(Cpp_Preproc_State *state, Cpp_Expansion *expansion, + int start, int end, int out_rule, + int invoking_file_index, int invoking_token_index){ + expansion->position = start; + expansion->end_position = end; + expansion->file_index = -1; + expansion->macro_index = 0; + expansion->out_rule = out_rule; + expansion->out_type = EXPAN_STRFY; + expansion->invoking_file_index = invoking_file_index; + expansion->invoking_token_index = invoking_token_index; + + expansion->may_have_pp = 0; + expansion->is_file_level = 0; + expansion->pop_and_drop = 0; + + if (state->expansion_level >= 1){ + expansion->stack_base = state->expansions[state->expansion_level-1].stack_base; + } + else{ + expansion->stack_base = 0; + } +} + +internal Cpp_Expansion* +cpp__push_strfy_expansion(Cpp_Preproc_State *state, int start, int end, int out_rule, + int invoking_file_index, int invoking_token_index){ + _Assert(start < end); + if (state->expansion_level == ArrayCount(state->expansions)){ + return 0; + } + + ++state->expansion_level; + Cpp_Expansion *result = state->expansions + state->expansion_level; + cpp__set_strfy_expansion(state, result, start, end, out_rule, + invoking_file_index, invoking_token_index); + return result; +} + +internal void +cpp__set_big_expansion(Cpp_Expansion *expansion, int macro_index, int stack_base, + int param_info_base, int out_rule, + int invoking_file_index, int invoking_token_index){ + expansion->stack_base = stack_base; + expansion->file_index = macro_index; + expansion->macro_index = 0; + expansion->out_rule = out_rule; + expansion->out_type = EXPAN_BIG_PROCESS_ARGS; + expansion->param_info_base = param_info_base; + expansion->invoking_file_index = invoking_file_index; + expansion->invoking_token_index = invoking_token_index; + expansion->pop_and_drop = 0; + expansion->may_have_pp = 0; + expansion->is_file_level = 0; +} + +internal Cpp_Expansion* +cpp__push_big_expansion(Cpp_Preproc_State *state, int macro_index, int stack_base, int param_info_base, + int out_rule, int invoking_file_index, int invoking_token_index){ + if (state->expansion_level == ArrayCount(state->expansions)){ + return 0; + } + ++state->expansion_level; + Cpp_Expansion *result = state->expansions + state->expansion_level; + cpp__set_big_expansion(result, macro_index, stack_base, param_info_base, out_rule, + invoking_file_index, invoking_token_index); + return result; +} + +internal int +cpp__alloc_param_table(Cpp_Preproc_State *state, int param_count){ + _Assert(state->param_info_used + param_count * 3 <= ArrayCount(state->param_info)); + int result = state->param_info_used; + state->param_info_used += param_count * 3; + memset(state->param_info + result, 0, sizeof(Cpp_Token_Range)*param_count*3); + return result; +} + +internal void +cpp__free_param_table(Cpp_Preproc_State *state, int param_count){ + _Assert(state->param_info_used >= param_count * 3); + state->param_info_used -= param_count * 3; +} + +enum Param_Info_Position{ + RAW_START, + RAW_END, + STRFY_START, + STRFY_END, + EXPANDED_START, + EXPANDED_END +}; + +enum Param_Info_Range_Position{ + RAW, + STRFY, + EXPANDED +}; + +internal Cpp_Token_Range +cpp__param_info_get(Cpp_Preproc_State *state, int base, int arg_index, Param_Info_Range_Position pos){ + Cpp_Token_Range *range = state->param_info + (base + arg_index*3); + range += pos; + return *range; +} + +internal void +cpp__param_info_set(Cpp_Preproc_State *state, int base, int arg_index, Param_Info_Position pos, int val){ + Cpp_Token_Range *range = state->param_info + (base + arg_index*3); + range += (pos >> 1); + *( ((int*)range) + (pos & 1) ) = val; +} + +internal void +cpp_set_target(Cpp_Preproc_State *state, Cpp_Parse_Definitions *definitions, + Cpp_File file, Cpp_Token_Stack tokens, String filename){ + int target_index; + target_index = cpp_defs_add(definitions, filename, CPP_DEFTYPE_FILE); + cpp_set_parse_file(definitions, target_index, {file, tokens, filename}); + state->expansion_level = -1; + cpp__push_expansion(state, EXPAN_FILE, target_index, 0, tokens.count, 0, -1, -1); +} + +internal void +cpp__preproc_pop_expansion(Cpp_Preproc_State *state, Cpp_Expansion *expansion){ + if (expansion->is_file_level){ + _Assert(expansion->out_type != EXPAN_EOF_FLUSH); + expansion->out_type = EXPAN_EOF_FLUSH; + } + + else{ + if (expansion->pop_and_drop){ + Cpp_Loose_Token *tokens = state->tokens.tokens; + int count = state->tokens.count - expansion->stack_pop_pos; + memmove(tokens + expansion->stack_base, tokens + expansion->stack_pop_pos, + sizeof(Cpp_Loose_Token)*count); + state->tokens.count = expansion->stack_base + count; + state->mac_inv.stack_base -= (expansion->stack_pop_pos - expansion->stack_base); + } + + if (state->expansion_level == 0){ + state->finished = 1; + } + else{ + --state->expansion_level; + } + } +} + +enum Preproc_State{ + PPS_DEFAULT, + PPS_MACRO_NAME, + PPS_MACRO_PARAM_OR_CLOSE, + PPS_MACRO_BODY, + PPS_MACRO_COMMA_OR_CLOSE, + PPS_MACRO_PARAM, + PPS_MACRO_CLOSE, + PPS_MACRO_BODY_OR_PARAM_OPEN, + PPS_MACRO_ERROR, + PPS_MACROINV_OPEN, + PPS_MACROINV_PARAMS +}; + +internal Cpp_Preproc_Result +cpp__preproc_normal_step_nonalloc(Cpp_Preproc_State *state, Cpp_Parse_Definitions *definitions, + Cpp_Parse_Context *context){ + Cpp_Preproc_Result result = {}; + Cpp_Expansion to_push_later = {}; + int do_push_later = 0; + Cpp_Expansion *expansion = state->expansions + state->expansion_level; + + if (expansion->position == expansion->end_position && expansion->out_type == EXPAN_NORMAL){ + cpp__preproc_pop_expansion(state, expansion); + result = {}; + return result; + } + + if (!cpp__can_push_loose_token(state, 1)){ + result.memory_request = sizeof(Cpp_Loose_Token)*((state->tokens.count*2) + 1); + result.memory_purpose = MEMPURP_TOKEN_STACK; + return result; + } + + Cpp_Visit visit = {}; + Cpp_Parse_File visit_file; + Cpp_Token visit_token; + + if (expansion->out_type == EXPAN_NORMAL){ + if (expansion->file_index >= 0){ + visit.file_index = expansion->file_index; + visit.token_index = expansion->position; + visit.blocked = 0; + } + else{ + Cpp_Loose_Token loose_token = state->tokens.tokens[expansion->position]; + visit.file_index = loose_token.file_index; + visit.token_index = loose_token.token_index; + visit.blocked = loose_token.blocked; + } + } + else if (expansion->out_type == EXPAN_EOF_FLUSH){ + visit.file_index = definitions->eof_token.file_index; + visit.token_index = definitions->eof_token.token_index; + visit.blocked = 0; + } + + visit_file = *cpp_get_parse_file(definitions, visit.file_index); + visit_token = visit_file.tokens.tokens[visit.token_index]; + + result.file_index = visit.file_index; + result.token_index = visit.token_index; + result.blocked = visit.blocked; + result.invoking_file_index = expansion->invoking_file_index; + result.invoking_token_index = expansion->invoking_token_index; + result.from_macro = !expansion->is_file_level; + + bool step_forward = 1; + switch (state->state){ + case PPS_MACRO_NAME: + { + if (visit_token.flags & CPP_TFLAG_PP_BODY){ + switch (visit_token.type){ + case CPP_TOKEN_COMMENT: + case CPP_TOKEN_JUNK:break; + + case CPP_TOKEN_IDENTIFIER: + { + state->mac_read.name_index = visit.token_index; + state->state = PPS_MACRO_BODY_OR_PARAM_OPEN; + }break; + + default: + { + result.error_code = PPERR_DEFINE_EXPECTED_IDENTIFIER; + state->state = PPS_MACRO_ERROR; + }break; + } + } + else{ + result.error_code = PPERR_UNFINISHED_DEFINE; + state->state = PPS_DEFAULT; + step_forward = 0; + } + }break; + + case PPS_MACRO_BODY_OR_PARAM_OPEN: + { + if (visit_token.flags & CPP_TFLAG_PP_BODY){ + switch (visit_token.type){ + case CPP_TOKEN_COMMENT: + case CPP_TOKEN_JUNK:break; + + case CPP_TOKEN_PARENTHESE_OPEN: + { + state->state = PPS_MACRO_PARAM_OR_CLOSE; + }break; + + default: + { + state->mac_read.param_count = -1; + state->mac_read.body_start_index = visit.token_index; + state->state = PPS_MACRO_BODY; + }break; + } + } + else{ + Cpp_Token token = visit_file.tokens.tokens[state->mac_read.name_index]; + String name = make_string(visit_file.file.data + token.start, token.size); + cpp__finish_macro(definitions, name, visit.file_index, state->mac_read.name_index, -1, 0, 0, 0); + state->state = PPS_DEFAULT; + step_forward = 0; + } + }break; + + case PPS_MACRO_BODY: + { + if (!(visit_token.flags & CPP_TFLAG_PP_BODY)){ + Cpp_Token token = visit_file.tokens.tokens[state->mac_read.name_index]; + String name = make_string(visit_file.file.data + token.start, token.size); + cpp__finish_macro(definitions, name, + visit.file_index, state->mac_read.name_index, + state->mac_read.param_count, state->mac_read.first_param_index, + state->mac_read.body_start_index, visit.token_index); + state->state = PPS_DEFAULT; + step_forward = 0; + } + }break; + + case PPS_MACRO_PARAM_OR_CLOSE: + { + if (visit_token.flags & CPP_TFLAG_PP_BODY){ + switch (visit_token.type){ + case CPP_TOKEN_COMMENT: + case CPP_TOKEN_JUNK:break; + + case CPP_TOKEN_PARENTHESE_CLOSE: + { + state->mac_read.body_start_index = visit.token_index + 1; + state->state = PPS_MACRO_BODY; + }break; + + case CPP_TOKEN_IDENTIFIER: + { + state->mac_read.first_param_index = visit.token_index; + ++state->mac_read.param_count; + state->state = PPS_MACRO_COMMA_OR_CLOSE; + }break; + + default: + { + result.error_code = PPERR_MACRO_NONIDENTIFIER_ARG; + state->state = PPS_MACRO_ERROR; + }break; + } + } + else{ + result.error_code = PPERR_MACRO_INCOMPLETE_PARAMS; + state->state = PPS_DEFAULT; + step_forward = 0; + } + }break; + + case PPS_MACRO_COMMA_OR_CLOSE: + { + if (visit_token.flags & CPP_TFLAG_PP_BODY){ + switch (visit_token.type){ + case CPP_TOKEN_COMMENT: + case CPP_TOKEN_JUNK:break; + + case CPP_TOKEN_PARENTHESE_CLOSE: + { + state->mac_read.body_start_index = visit.token_index + 1; + state->state = PPS_MACRO_BODY; + }break; + + case CPP_TOKEN_COMMA: + { + state->state = PPS_MACRO_PARAM; + }break; + + default: + { + result.error_code = PPERR_MACRO_EXPECTED_COMMA; + state->state = PPS_MACRO_ERROR; + }break; + } + } + else{ + result.error_code = PPERR_MACRO_INCOMPLETE_PARAMS; + state->state = PPS_DEFAULT; + step_forward = 0; + } + }break; + + case PPS_MACRO_PARAM: + { + if (visit_token.flags & CPP_TFLAG_PP_BODY){ + switch (visit_token.type){ + case CPP_TOKEN_COMMENT: + case CPP_TOKEN_JUNK:break; + + case CPP_TOKEN_IDENTIFIER: + { + ++state->mac_read.param_count; + state->state = PPS_MACRO_COMMA_OR_CLOSE; + }break; + + case CPP_TOKEN_ELLIPSIS: + { + ++state->mac_read.param_count; + state->state = PPS_MACRO_CLOSE; + }break; + + default: + { + result.error_code = PPERR_MACRO_NONIDENTIFIER_ARG; + state->state = PPS_MACRO_ERROR; + }break; + } + } + else{ + result.error_code = PPERR_MACRO_INCOMPLETE_PARAMS; + state->state = PPS_DEFAULT; + step_forward = 0; + } + }break; + + case PPS_MACRO_CLOSE: + { + if (visit_token.flags & CPP_TFLAG_PP_BODY){ + switch (visit_token.type){ + case CPP_TOKEN_COMMENT: + case CPP_TOKEN_JUNK:break; + + case CPP_TOKEN_PARENTHESE_CLOSE: + { + state->mac_read.body_start_index = visit.token_index + 1; + state->state = PPS_MACRO_BODY; + }break; + + default: + { + result.error_code = PPERR_MACRO_MORE_AFTER_ELLIPSIS; + state->state = PPS_MACRO_ERROR; + }break; + } + } + else{ + result.error_code = PPERR_MACRO_INCOMPLETE_PARAMS; + state->state = PPS_DEFAULT; + step_forward = 0; + } + }break; + + case PPS_MACRO_ERROR: + { + if (!(visit_token.flags & CPP_TFLAG_PP_BODY)){ + state->state = PPS_DEFAULT; + step_forward = 0; + } + }break; + + case PPS_MACROINV_OPEN: + { + switch (visit_token.type){ + case CPP_TOKEN_COMMENT: + case CPP_TOKEN_JUNK:break; + + case CPP_TOKEN_PARENTHESE_OPEN: + { + Cpp_Macro_Data *macro = cpp_get_macro_data(definitions, state->mac_inv.macro_index); + state->mac_inv.param_info_base = cpp__alloc_param_table(state, macro->param_count); + state->state = PPS_MACROINV_PARAMS; + }break; + + default: + { + state->tokens.count = state->mac_inv.stack_base; + + result.file_index = state->mac_inv.file_index; + result.token_index = state->mac_inv.token_index; + result.blocked = 0; + result.invoking_file_index = state->mac_inv.invoking_file_index; + result.invoking_token_index = state->mac_inv.invoking_token_index; + result.from_macro = (result.invoking_file_index != -1); + result.emit = 1; + + step_forward = 0; + + state->state = PPS_DEFAULT; + }break; + } + }break; + + case PPS_MACROINV_PARAMS: + { + enum Macroinv_Params_Action{ + MIA_NONE, + MIA_EXTEND_PARAM, + MIA_NEXT_PARAM, + MIA_FINISH, + MIA_CLEANUP + }; + + Macroinv_Params_Action action = MIA_NONE; + Cpp_Macro_Data macro = *cpp_get_macro_data(definitions, state->mac_inv.macro_index); + switch (visit_token.type){ + case CPP_TOKEN_COMMENT: + case CPP_TOKEN_JUNK:break; + + case CPP_TOKEN_PARENTHESE_OPEN: + { + action = MIA_EXTEND_PARAM; + ++state->mac_inv.paren_level; + }break; + + case CPP_TOKEN_PARENTHESE_CLOSE: + { + if (state->mac_inv.paren_level == 0){ + action = MIA_FINISH; + } + else{ + --state->mac_inv.paren_level; + action = MIA_EXTEND_PARAM; + } + }break; + + case CPP_TOKEN_COMMA: + { + if (state->mac_inv.paren_level != 0 || + (state->mac_inv.variadic && + (state->mac_inv.param_count == macro.param_count || + macro.param_count == 1))){ + action = MIA_EXTEND_PARAM; + } + else{ + action = MIA_NEXT_PARAM; + } + }break; + + case CPP_TOKEN_EOF: + { + action = MIA_CLEANUP; + result.error_code = PPERR_INCOMPLETE_MACROINV; + }break; + + default: + { + action = MIA_EXTEND_PARAM; + }break; + } + + switch (action){ + case MIA_EXTEND_PARAM: + { + if (state->mac_inv.param_count == 0){ + state->mac_inv.param_count = 1; + cpp__param_info_set(state, state->mac_inv.param_info_base, + 0, RAW_START, state->tokens.count); + } + // NOTE(allen): this function gaurantees at the head end that there is + // room for at least one token + cpp__push_loose_token(state, visit.file_index, visit.token_index, 0); + }break; + + case MIA_NEXT_PARAM: + { + if (state->mac_inv.param_count == 0){ + state->mac_inv.param_count = 1; + cpp__param_info_set(state, state->mac_inv.param_info_base, + 0, RAW_START, state->tokens.count); + } + + if (state->mac_inv.param_count <= macro.param_count){ + cpp__param_info_set(state, state->mac_inv.param_info_base, + state->mac_inv.param_count - 1, RAW_END, state->tokens.count); + ++state->mac_inv.param_count; + cpp__param_info_set(state, state->mac_inv.param_info_base, + state->mac_inv.param_count - 1, RAW_START, state->tokens.count); + } + else{ + ++state->mac_inv.param_count; + } + }break; + + case MIA_FINISH: + { + if (state->mac_inv.param_count != 0 && state->mac_inv.param_count <= macro.param_count){ + cpp__param_info_set(state, state->mac_inv.param_info_base, + state->mac_inv.param_count - 1, RAW_END, state->tokens.count); + } + if (state->mac_inv.param_count != macro.param_count){ + if (state->mac_inv.param_count < macro.param_count){ + result.error_code = PPERR_MACROINV_TOO_FEW_PARAMS; + } + else{ + result.error_code = PPERR_MACROINV_TOO_MANY_PARAMS; + } + } + state->state = PPS_DEFAULT; + + do_push_later = 2; + cpp__set_big_expansion(&to_push_later, state->mac_inv.macro_index, + state->mac_inv.stack_base, state->mac_inv.param_info_base, + expansion->out_rule, visit.file_index, visit.token_index); + }break; + + case MIA_CLEANUP: + { + state->tokens.count = state->mac_inv.stack_base; + cpp__free_param_table(state, state->mac_inv.param_count); + }break; + } + }break; + + case PPS_DEFAULT: + { + switch (visit_token.type){ + case CPP_TOKEN_COMMENT: + case CPP_TOKEN_JUNK: + case CPP_TOKEN_EOF:break; + + case CPP_PP_DEFINE: + { + state->mac_read = {}; + state->state = PPS_MACRO_NAME; + }break; + + case CPP_TOKEN_IDENTIFIER: + { + String name = make_string(visit_file.file.data + visit_token.start, visit_token.size); + int macro_index; + if (table_find(&definitions->table, name, CPP_DEFTYPE_MACRO, ¯o_index)){ + bool blocked = (visit.blocked != 0); + if (!blocked){ + for (int i = state->expansion_level; i >= 0; --i){ + if (macro_index == state->expansions[i].macro_index){ + blocked = 1; + break; + } + } + } + + if (!blocked){ + Cpp_Macro_Data *macro = cpp_get_macro_data(definitions, macro_index); + if (macro->param_count == -1){ + if (macro->body_start_index < macro->body_end_index){ + do_push_later = 1; + cpp__set_expansion(state, &to_push_later, EXPAN_MACRO, macro->file_index, + macro->body_start_index, macro->body_end_index, + expansion->out_rule, visit.file_index, visit.token_index); + to_push_later.macro_index = macro_index; + } + } + + else{ + int stack_start = state->tokens.count; + // NOTE(allen): luckily the number of tokens about to be pushed is known + // up front in this case. No states have been changed yet so it's okay + // allowing a memory request to be issued from here. + if (cpp__can_push_loose_token(state, macro->param_count)){ + bool variadic = 0; + if (macro->param_count != 0){ + int macro_file_index = macro->file_index; + Cpp_Parse_File file = *cpp_get_parse_file(definitions, macro_file_index); + int i = macro->first_param_index; + Cpp_Token token = file.tokens.tokens[i]; + for (;;){ + if (token.type == CPP_TOKEN_IDENTIFIER){ + cpp__push_loose_token(state, macro_file_index, i, 0); + } + if (token.type == CPP_TOKEN_ELLIPSIS){ + Cpp_Loose_Token va_args = definitions->va_args_token; + cpp__push_loose_token(state, va_args.file_index, + va_args.token_index, 0); + variadic = 1; + } + + ++i; + if (i >= file.tokens.count){ + break; + } + token = file.tokens.tokens[i]; + if (token.type == CPP_TOKEN_PARENTHESE_CLOSE){ + break; + } + } + _Assert(state->tokens.count - stack_start == macro->param_count); + } + + state->mac_inv.file_index = visit.file_index; + state->mac_inv.token_index = visit.token_index; + + state->mac_inv.invoking_file_index = result.invoking_file_index; + state->mac_inv.invoking_token_index = result.invoking_token_index; + + state->mac_inv.macro_index = macro_index; + state->mac_inv.param_count = 0; + state->mac_inv.paren_level = 0; + state->mac_inv.stack_base = stack_start; + state->mac_inv.variadic = variadic; + + state->state = PPS_MACROINV_OPEN; + } + else{ + result.memory_request = sizeof(Cpp_Loose_Token)* + ((state->tokens.max*2) + macro->param_count); + result.memory_purpose = MEMPURP_TOKEN_STACK; + step_forward = 0; + } + } + } + else{ + result.emit = 1; + result.blocked = 1; + } + } + else{ + result.emit = 1; + } + }break; + + default: + { + result.emit = 1; + }break; + } + }break; + + default: + { + _Assert(!"bad state->state"); + }break; + } + + if (expansion->out_rule != 0 && result.emit){ + cpp__push_loose_token(state, result.file_index, result.token_index, result.blocked); + result.emit = 0; + if (expansion->out_rule > 0){ + if (state->param_info[expansion->out_rule].start == 0){ + state->param_info[expansion->out_rule].start = state->tokens.count - 1; + } + state->param_info[expansion->out_rule].end = state->tokens.count; + } + } + + if (expansion->out_type == EXPAN_NORMAL){ + if (step_forward){ + ++expansion->position; + if (expansion->position == expansion->end_position && do_push_later == 0){ + cpp__preproc_pop_expansion(state, expansion); + } + } + } + else{ + if (state->expansion_level == 0){ + state->finished = 1; + } + else{ + --state->expansion_level; + } + } + + if (do_push_later){ + cpp__push_expansion_raw(state, &to_push_later); + } + + return result; +} + +internal int +cpp__get_parameter_i(Cpp_Preproc_State *state, Cpp_Parse_Definitions * definitions, + Cpp_Parse_File *macro_file, Cpp_Token token, int param_start, int param_count){ + int param_i = -1; + if (token.type == CPP_TOKEN_IDENTIFIER){ + String token_str = make_string(macro_file->file.data + token.start, token.size); + for (int j = 0; j < param_count; ++j){ + Cpp_Loose_Token param_loose = state->tokens.tokens[j + param_start]; + Cpp_Parse_File *file = cpp_get_parse_file(definitions, param_loose.file_index); + Cpp_Token param_token = file->tokens.tokens[param_loose.token_index]; + String param_str = make_string(file->file.data + param_token.start, param_token.size); + if (match(token_str, param_str)){ + param_i = j; + break; + } + } + } + return param_i; +} + +internal Cpp_Preproc_Result +cpp__preproc_big_step_nonalloc(Cpp_Preproc_State *state, Cpp_Parse_Definitions *definitions, + Cpp_Parse_Context *context){ + Cpp_Preproc_Result result = {}; + Cpp_Expansion *expansion = state->expansions + state->expansion_level; + + Cpp_Macro_Data macro = *cpp_get_macro_data(definitions, expansion->file_index); + Cpp_Parse_File *macro_file = cpp_get_parse_file(definitions, macro.file_index); + switch (expansion->out_type){ + case EXPAN_BIG_PROCESS_ARGS: + { + bool expand_arg[64]; + bool strfy_arg[64]; + _Assert(macro.param_count < 64); + memset(expand_arg, 0, macro.param_count); + memset(strfy_arg, 0, macro.param_count); + + bool prev_was_paste = 0; + bool next_is_paste = 0; + bool prev_was_strfy = 0; + Cpp_Token token; + int param_i; + for (int i = macro.body_start_index; i < macro.body_end_index; ++i){ + token = macro_file->tokens.tokens[i]; + param_i = cpp__get_parameter_i(state, definitions, macro_file, token, + expansion->stack_base, macro.param_count); + + next_is_paste = (i+1 < macro.body_end_index && + macro_file->tokens.tokens[i+1].type == CPP_PP_CONCAT); + + if (param_i != -1){ + if (prev_was_strfy){ + strfy_arg[param_i] = 1; + } + else if (!prev_was_paste && !next_is_paste){ + expand_arg[param_i] = 1; + } + } + + prev_was_strfy = (token.type == CPP_PP_STRINGIFY); + prev_was_paste = (token.type == CPP_PP_CONCAT && i != macro.body_start_index); + } + + int param_info_base = expansion->param_info_base; + for (int i = 0; i < macro.param_count; ++i){ + Cpp_Token_Range range = cpp__param_info_get(state, param_info_base, i, RAW); + + if (strfy_arg[i]){ + Cpp_Expansion *new_expansion = + cpp__push_strfy_expansion(state, range.start, range.end, + param_info_base + i*3 + 1, + expansion->invoking_file_index, + expansion->invoking_token_index); + if (new_expansion == 0){ + result.error_code = PPERR_EXPANSION_OVERRUN; + break; + } + } + + if (expand_arg[i]){ + if (range.start < range.end){ + Cpp_Expansion *new_expansion = + cpp__push_expansion(state, EXPAN_ARG, -1, range.start, range.end, + param_info_base + i*3 + 2, + expansion->invoking_file_index, + expansion->invoking_token_index); + if (new_expansion == 0){ + result.error_code = PPERR_EXPANSION_OVERRUN; + break; + } + } + } + } + expansion->out_type = EXPAN_BIG_PROCESS_BODY; + }break; + + case EXPAN_BIG_PROCESS_BODY: + { + int start_pos = state->tokens.count; + + // TODO(allen): because it is possible that this be used more than once, it is possible that + // all of this code happens any number of times before it finally runs through with enough + // memory. Perhaps loft this checkpoint out and try the whole thing in one big checkpoint? + // Some sort of way of determining the max size needed in spare string? + Spare_String_Checkpoint str_checkpoint = {}; + Preserve_Checkpoint pres_checkpoint = cpp__checkpoint_preserve_write(definitions); + Token_Stack_Checkpoint checkpoint = cpp__checkpoint_token_stack(state); + bool next_is_paste = 0; + for (int i = macro.body_start_index; i < macro.body_end_index; ++i){ + Cpp_Token token = macro_file->tokens.tokens[i]; + + next_is_paste = (i+1 < macro.body_end_index && + macro_file->tokens.tokens[i+1].type == CPP_PP_CONCAT); + + enum Body_Builder_Action{ + BBA_NONE, + BBA_NORMAL, + BBA_STRFY, + BBA_PASTE + }; + + int action = BBA_NONE; + int param_i = -1; + int i2 = -1; + + if (token.type == CPP_PP_STRINGIFY){ + i += 1; + if (i < macro.body_end_index){ + action = BBA_STRFY; + } + else{ + i -= 1; + action = BBA_NONE; + } + } + else if (next_is_paste){ + i2 = i + 2; + if (i2 < macro.body_end_index){ + action = BBA_PASTE; + } + else{ + action = BBA_NORMAL; + } + } + else if (token.type == CPP_PP_CONCAT){ + action = BBA_NONE; + } + else{ + action = BBA_NORMAL; + } + + // NOTE(allen): loose token pushes here are managed by checkpoint + switch (action){ + case BBA_NONE:break; + + case BBA_NORMAL: + { + param_i = cpp__get_parameter_i(state, definitions, macro_file, token, + expansion->stack_base, macro.param_count); + if (param_i != -1){ + Cpp_Token_Range range = cpp__param_info_get(state, expansion->param_info_base, param_i, EXPANDED); + for (int j = range.start; j < range.end; ++j){ + Cpp_Loose_Token loose = state->tokens.tokens[j]; + cpp__push_loose_token(&checkpoint, state, loose.file_index, loose.token_index, loose.blocked); + } + } + else{ + cpp__push_loose_token(&checkpoint, state, macro.file_index, i, 0); + } + }break; + + case BBA_STRFY: + { + token = macro_file->tokens.tokens[i]; + param_i = cpp__get_parameter_i(state, definitions, macro_file, token, + expansion->stack_base, macro.param_count); + if (param_i != -1){ + Cpp_Token_Range range = cpp__param_info_get(state, expansion->param_info_base, param_i, STRFY); + Cpp_Loose_Token loose = state->tokens.tokens[range.start]; + cpp__push_loose_token(&checkpoint, state, loose.file_index, loose.token_index, loose.blocked); + } + else{ + cpp__push_loose_token(&checkpoint, state, macro.file_index, i, 0); + } + }break; + + case BBA_PASTE: + { + Cpp_Token token2; + int param_i2; + + param_i = cpp__get_parameter_i(state, definitions, macro_file, token, + expansion->stack_base, macro.param_count); + token2 = macro_file->tokens.tokens[i2]; + param_i2 = cpp__get_parameter_i(state, definitions, macro_file, token2, + expansion->stack_base, macro.param_count); + + str_checkpoint = cpp__checkpoint_spare_string(state); + if (param_i != -1){ + Cpp_Token_Range range = cpp__param_info_get(state, expansion->param_info_base, param_i, RAW); + range.end -= 1; + if (range.start <= range.end){ + int j; + for (j = range.start; j < range.end; ++j){ + Cpp_Loose_Token loose = state->tokens.tokens[j]; + cpp__push_loose_token(&checkpoint, state, loose.file_index, loose.token_index, loose.blocked); + } + Cpp_Loose_Token loose = state->tokens.tokens[j]; + Cpp_Parse_File *end_file = cpp_get_parse_file(definitions, loose.file_index); + Cpp_Token end_token = end_file->tokens.tokens[loose.token_index]; + String end_string = make_string(end_file->file.data + end_token.start, end_token.size); + cpp__spare_write(&str_checkpoint, state, end_string); + } + } + else{ + String end_string = make_string(macro_file->file.data + token.start, token.size); + cpp__spare_write(&str_checkpoint, state, end_string); + } + + if (param_i2 != -1){ + Cpp_Token_Range range = cpp__param_info_get(state, expansion->param_info_base, param_i2, RAW); + if (range.start < range.end){ + int j = range.start; + Cpp_Loose_Token loose = state->tokens.tokens[j]; + Cpp_Parse_File *start_file = cpp_get_parse_file(definitions, loose.file_index); + Cpp_Token start_token = start_file->tokens.tokens[loose.token_index]; + String start_string = make_string(start_file->file.data + start_token.start, start_token.size); + cpp__spare_write(&str_checkpoint, state, start_string); + } + } + else{ + String start_string = make_string(macro_file->file.data + token2.start, token2.size); + cpp__spare_write(&str_checkpoint, state, start_string); + } + + if (!str_checkpoint.memory_overrun){ + Cpp_Token _tokens[3]; + Cpp_Token_Stack tokens; + tokens.tokens = _tokens; + tokens.max_count = ArrayCount(_tokens); + tokens.count = 0; + + Cpp_File paste_file; + paste_file.data = state->spare_string; + paste_file.size = state->spare_string_write_pos; + + cpp_lex_file_nonalloc(paste_file, &tokens); + _Assert(tokens.count <= 2); + + // TODO(allen): protect these preserves + String paste_string = make_string(state->spare_string, state->spare_string_write_pos); + cpp__preserve_string(&pres_checkpoint, definitions, paste_string); + int start_pos = definitions->string_write_pos - paste_string.size; + for (int k = 0; k < tokens.count; ++k){ + tokens.tokens[k].start += start_pos; + } + for (int k = 0; k < tokens.count; ++k){ + Cpp_Loose_Token loose = cpp__preserve_token(&pres_checkpoint, definitions, tokens.tokens[k]); + cpp__push_loose_token(&checkpoint, state, loose.file_index, loose.token_index, loose.blocked); + } + + i = i2; + } + else{ + i = macro.body_end_index; + } + + state->spare_string_write_pos = 0; + + if (param_i2 != -1){ + Cpp_Token_Range range = cpp__param_info_get(state, expansion->param_info_base, param_i2, RAW); + range.start += 1; + for (int j = range.start; j < range.end; ++j){ + Cpp_Loose_Token loose = state->tokens.tokens[j]; + cpp__push_loose_token(&checkpoint, state, loose.file_index, loose.token_index, loose.blocked); + } + } + }break; + + default: + { + _Assert(!"unknown action"); + }break; + } + } + + if (pres_checkpoint.out_of_memory){ + cpp__restore_token_stack(state, checkpoint); + cpp__restore_preserve_write(definitions, pres_checkpoint); + result.memory_request = context->preserve_chunk_size; + result.memory_purpose = MEMPURP_PRESERVE_FILE; + } + else if (str_checkpoint.memory_overrun){ + cpp__restore_token_stack(state, checkpoint); + cpp__restore_preserve_write(definitions, pres_checkpoint); + result.memory_request = (state->spare_string_size * 2) + str_checkpoint.memory_overrun; + result.memory_purpose = MEMPURP_SPARE_STRING; + } + else if (checkpoint.memory_overrun){ + cpp__restore_token_stack(state, checkpoint); + cpp__restore_preserve_write(definitions, pres_checkpoint); + result.memory_request = sizeof(Cpp_Loose_Token)*((state->tokens.max*2) + checkpoint.memory_overrun); + result.memory_purpose = MEMPURP_TOKEN_STACK; + } + else{ + expansion->position = expansion->stack_base; + expansion->out_type = EXPAN_BIG_DIRECT_OUT; + + if (start_pos < state->tokens.count){ + Cpp_Expansion *new_expansion = + cpp__push_expansion(state, EXPAN_PMACRO_BODY, -1, start_pos, state->tokens.count, + -1, expansion->invoking_file_index, + expansion->invoking_token_index); + new_expansion->macro_index = expansion->file_index; + if (new_expansion == 0){ + result.error_code = PPERR_EXPANSION_OVERRUN; + } + } + } + }break; + + case EXPAN_BIG_DIRECT_OUT: + { + if (expansion->out_rule == -1){ + --state->expansion_level; + _Assert(state->expansion_level != -1); + cpp__free_param_table(state, macro.param_count); + } + else if (expansion->out_rule != 0){ + if (state->param_info[expansion->out_rule].start == 0){ + state->param_info[expansion->out_rule].start = expansion->position; + } + state->param_info[expansion->out_rule].end = state->tokens.count; + --state->expansion_level; + _Assert(state->expansion_level != -1); + cpp__free_param_table(state, macro.param_count); + } + else{ + Cpp_Visit visit; + Cpp_Loose_Token loose_token = state->tokens.tokens[expansion->position]; + visit.file_index = loose_token.file_index; + visit.token_index = loose_token.token_index; + + result.file_index = visit.file_index; + result.token_index = visit.token_index; + result.invoking_file_index = expansion->invoking_file_index; + result.invoking_token_index = expansion->invoking_token_index; + result.from_macro = 1; + result.emit = 1; + + ++expansion->position; + if (expansion->position == state->tokens.count){ + state->mac_inv.stack_base -= (state->tokens.count - expansion->stack_base); + state->tokens.count = expansion->stack_base; + --state->expansion_level; + _Assert(state->expansion_level != -1); + cpp__free_param_table(state, macro.param_count); + } + } + }break; + + default: + _Assert(!"only meant to be used with one of the big expan types"); + } + + return result; +} + +internal Cpp_Preproc_Result +cpp__preproc_strfy_step_nonalloc(Cpp_Preproc_State *state, Cpp_Parse_Definitions *definitions, + Cpp_Parse_Context *context){ + Cpp_Preproc_Result result = {}; + Cpp_Expansion *expansion = state->expansions + state->expansion_level; + + if (!cpp__can_push_loose_token(state, 1)){ + result.memory_request = sizeof(Cpp_Loose_Token)*((state->tokens.max*2) + 1); + result.memory_purpose = MEMPURP_TOKEN_STACK; + return result; + } + + Cpp_Visit visit = {}; + + bool do_body = 1; + if (expansion->position == expansion->end_position + 1){ + do_body = 0; + --expansion->position; + } + else if (expansion->position == expansion->end_position){ + visit.file_index = definitions->eof_token.file_index; + visit.token_index = definitions->eof_token.token_index; + } + else{ + _Assert(expansion->file_index == -1); + Cpp_Loose_Token loose = state->tokens.tokens[expansion->position]; + visit.file_index = loose.file_index; + visit.token_index = loose.token_index; + } + + Spare_String_Checkpoint checkpoint = cpp__checkpoint_spare_string(state); + + if (do_body){ + Cpp_Parse_File visit_file; + Cpp_Token visit_token; + + visit_file = *cpp_get_parse_file(definitions, visit.file_index); + visit_token = visit_file.tokens.tokens[visit.token_index]; + + result.file_index = visit.file_index; + result.token_index = visit.token_index; + result.invoking_file_index = expansion->invoking_file_index; + result.invoking_token_index = expansion->invoking_token_index; + result.from_macro = 1; + result.emit = 0; + + if (state->spare_string_write_pos == 0){ + cpp__spare_write(&checkpoint, state, '"'); + expansion->param_info_base = visit_token.start + visit_token.size; + } + + switch (visit_token.type){ + case CPP_TOKEN_COMMENT: + case CPP_TOKEN_JUNK:break; + + case CPP_TOKEN_EOF: + { + cpp__spare_write(&checkpoint, state, '"'); + }break; + + case CPP_TOKEN_STRING_CONSTANT: + { + if (visit_token.start > expansion->param_info_base){ + cpp__spare_write(&checkpoint, state, ' '); + } + + int i = visit_token.start; + char c = visit_file.file.data[i]; + while (c != '"'){ + cpp__spare_write(&checkpoint, state, c); + ++i; + c = visit_file.file.data[i]; + } + + cpp__spare_write(&checkpoint, state, '\\'); + cpp__spare_write(&checkpoint, state, '"'); + ++i; + + int end_pos = visit_token.start + visit_token.size - 1; + for (; i < end_pos; ++i){ + c = visit_file.file.data[i]; + switch (c){ + case '\\': + cpp__spare_write(&checkpoint, state, '\\'); + + default: + cpp__spare_write(&checkpoint, state, c); + } + } + + cpp__spare_write(&checkpoint, state, '\\'); + cpp__spare_write(&checkpoint, state, '"'); + + expansion->param_info_base = visit_token.start + visit_token.size; + }break; + + case CPP_TOKEN_CHARACTER_CONSTANT: + { + if (visit_token.start > expansion->param_info_base){ + cpp__spare_write(&checkpoint, state, ' '); + } + + int i = visit_token.start; + char c = visit_file.file.data[i]; + while (c != '\''){ + cpp__spare_write(&checkpoint, state, c); + ++i; + c = visit_file.file.data[i]; + } + + int end_pos = visit_token.start + visit_token.size; + for (; i < end_pos; ++i){ + c = visit_file.file.data[i]; + switch (c){ + case '\\': + cpp__spare_write(&checkpoint, state, '\\'); + + default: + cpp__spare_write(&checkpoint, state, c); + } + } + + expansion->param_info_base = visit_token.start + visit_token.size; + }break; + + default: + { + if (visit_token.start > expansion->param_info_base){ + cpp__spare_write(&checkpoint, state, ' '); + } + + int end_pos = visit_token.start + visit_token.size; + for (int i = visit_token.start; i < end_pos; ++i){ + char c = visit_file.file.data[i]; + cpp__spare_write(&checkpoint, state, c); + } + + expansion->param_info_base = visit_token.start + visit_token.size; + }break; + } + } + + if (checkpoint.memory_overrun){ + cpp__restore_spare_string(state, checkpoint); + result.memory_request = (state->spare_string_size*2) + checkpoint.memory_overrun; + result.memory_purpose = MEMPURP_SPARE_STRING; + } + else{ + if (expansion->position == expansion->end_position){ + Preserve_Checkpoint preserve_checkpoint = cpp__checkpoint_preserve_write(definitions); + + String string = make_string(state->spare_string, state->spare_string_write_pos); + Cpp_Token token = {}; + token.type = CPP_TOKEN_STRING_CONSTANT; + token.start = definitions->string_write_pos; + token.size = string.size; + + cpp__preserve_string(&preserve_checkpoint, definitions, string); + Cpp_Loose_Token loose = cpp__preserve_token(&preserve_checkpoint, definitions, token); + + if (preserve_checkpoint.out_of_memory){ + cpp__restore_preserve_write(definitions, preserve_checkpoint); + result.memory_request = context->preserve_chunk_size; + result.memory_purpose = MEMPURP_PRESERVE_FILE; + } + else{ + _Assert(expansion->out_rule > 0); + // NOTE(allen): this function gaurantees there is at least room + // for one token at the head end + cpp__push_loose_token(state, loose.file_index, loose.token_index, loose.blocked); + state->param_info[expansion->out_rule].start = state->tokens.count - 1; + state->param_info[expansion->out_rule].end = state->tokens.count; + --state->expansion_level; + state->spare_string_write_pos = 0; + } + } + ++expansion->position; + } + + return result; +} + +internal Cpp_Preproc_Result +cpp_preproc_step_nonalloc(Cpp_Preproc_State *state, Cpp_Parse_Definitions *definitions, + Cpp_Parse_Context *context){ + Cpp_Preproc_Result result; + + if (state->resizing_slots){ + result = {}; + result.memory_request = sizeof(Table_Entry)*(definitions->table.max_size*2); + result.memory_purpose = MEMPURP_DEFINITION_SLOTS; + state->resizing_slots = 0; + return result; + } + if (definitions->count * 8 >= definitions->max * 7){ + result = {}; + result.memory_request = sizeof(Cpp_Def_Slot)*(definitions->max*2); + result.memory_purpose = MEMPURP_DEFINITION_SLOTS; + state->resizing_slots = 1; + return result; + } + + Cpp_Expansion *expansion = state->expansions + state->expansion_level; + switch (expansion->out_type){ + case EXPAN_NORMAL: + case EXPAN_EOF_FLUSH: + result = cpp__preproc_normal_step_nonalloc(state, definitions, context); + break; + + case EXPAN_BIG_PROCESS_ARGS: + case EXPAN_BIG_PROCESS_BODY: + case EXPAN_BIG_DIRECT_OUT: + result = cpp__preproc_big_step_nonalloc(state, definitions, context); + break; + + case EXPAN_STRFY: + result = cpp__preproc_strfy_step_nonalloc(state, definitions, context); + break; + + default: + result = {}; + _Assert(!"unknown expansion->out_type"); + } + + return result; +} + +// BOTTOM + diff --git a/4cpp_string.h b/4cpp_string.h new file mode 100644 index 00000000..92f77d70 --- /dev/null +++ b/4cpp_string.h @@ -0,0 +1,997 @@ +/* "4cpp" Open C++ Parser v0.1: String + no warranty implied; use at your own risk + +NOTES ON USE: + OPTIONS: + Set options by defining macros before including this file + + FCPP_STRING_IMPLEMENTATION - causes this file to output function implementations + - this option is unset after use so that future includes of this file + in the same unit do not continue to output implementations + + FCPP_LINK - defines linkage of non-inline functions, defaults to static + FCPP_EXTERN changes FCPP_LINK default to extern, this option is ignored if FCPP_LINK is defined + + include the file "4cpp_clear_config.h" if yo want to undefine all options for some reason + + HIDDEN DEPENDENCIES: + none + */ + +// TOP +// TODO(allen): +// - comments +// - memcpy / memmove replacements (different file for optimization options?) +// + +#include "4cpp_config.h" + +#ifndef FCPP_STRING_INC +#define FCPP_STRING_INC + +struct String{ + char *str; + int size; + int memory_size; +}; + +inline bool char_not_slash(char c) { return (c != '\\' && c != '/'); } +inline bool char_is_slash(char c) { return (c == '\\' || c == '/'); } + +inline char char_to_upper(char c) { return (c >= 'a' && c <= 'z') ? c + (char)('A' - 'a') : c; } +inline char char_to_lower(char c) { return (c >= 'A' && c <= 'Z') ? c - (char)('A' - 'a') : c; } + +inline bool char_is_whitespace(char c) { return (c == ' ' || c == '\n' || c == '\r' || c == '\t'); } +inline bool char_is_white_not_r(char c) { return (c == ' ' || c == '\n' || c == '\t'); } +inline bool char_is_lower(char c) { return (c >= 'a' && c <= 'z'); } +inline bool char_is_upper(char c) { return (c >= 'A' && c <= 'Z'); } +inline bool char_is_alpha(char c) { return (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '_'); } +inline bool char_is_alpha_true(char c) { return (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'); } +inline bool char_is_numeric(char c) { return (c >= '0' && c <= '9'); } +inline bool char_is_alpha_numeric_true(char c) { return (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9'); } +inline bool char_is_alpha_numeric(char c) { return (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '_'); } +inline bool char_is_hex(char c) { return c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f'; } +inline bool char_is_basic(char c) { return c >= ' ' && c <= '~'; } + +inline String make_string(char *s, int size, int mem_size); +inline String make_string(char *s, int size); + +#define make_lit_string(str) (make_string((char*)(str), sizeof(str)-1, sizeof(str))) +#define make_fixed_width_string(str) (make_string((char*)(str), 0, sizeof(str))) + +inline String make_string_slowly(char *s); +inline char* make_c_str(String s); + +inline String substr(String str, int start); +inline String substr(String str, int start, int size); +inline String substr_slowly(char *s, int start); +inline String substr(char *s, int start, int size); +inline String tailstr(String s); + + +FCPP_LINK int str_size(char *s); + +FCPP_LINK bool match(char *a, char *b); +FCPP_LINK bool match(String a, char *b); +inline bool match(char *a, String b) { return match(b,a); } +FCPP_LINK bool match(String a, String b); + +FCPP_LINK bool match_part(char *a, char *b, int *len); +FCPP_LINK bool match_part(String a, char *b, int *len); +inline bool match_part(char *a, char *b) { int x; return match_part(a,b,&x); } +inline bool match_part(String a, char *b) { int x; return match_part(a,b,&x); } +FCPP_LINK bool match_part(char *a, String b); +FCPP_LINK bool match_part(String a, String b); + +FCPP_LINK int find(char *s, int start, char c); +FCPP_LINK int find(String s, int start, char c); +FCPP_LINK int find(char *s, int start, char *c); +FCPP_LINK int find(String s, int start, char *c); + +FCPP_LINK int find_substr(char *s, int start, String seek); +FCPP_LINK int find_substr(String s, int start, String seek); +FCPP_LINK int rfind_substr(String s, int start, String seek); + +FCPP_LINK int find_substr_unsensitive(char *s, int start, String seek); +FCPP_LINK int find_substr_unsensitive(String s, int start, String seek); + +inline bool has_substr(char *s, String seek) { return (s[find_substr(s, 0, seek)] != 0); } +inline bool has_substr(String s, String seek) { return (find_substr(s, 0, seek) < s.size); } + +inline bool has_substr_unsensitive(char *s, String seek) { return (s[find_substr_unsensitive(s, 0, seek)] != 0); } +inline bool has_substr_unsensitive(String s, String seek) { return (find_substr_unsensitive(s, 0, seek) < s.size); } + +FCPP_LINK int int_to_str_size(int x); +FCPP_LINK int int_to_str(int x, char *s_out); +FCPP_LINK bool int_to_str(int x, String *s_out); +FCPP_LINK bool append_int_to_str(int x, String *s_out); + +FCPP_LINK int str_to_int(String s); +FCPP_LINK int hexchar_to_int(char c); +FCPP_LINK int int_to_hexchar(char c); +FCPP_LINK int hexstr_to_int(String s); + +FCPP_LINK void copy_fast_unsafe(char *dest, char *src); +FCPP_LINK void copy_fast_unsafe(char *dest, String src); +FCPP_LINK bool copy_checked(String *dest, String src); +FCPP_LINK bool copy_partial(String *dest, char *src); +FCPP_LINK bool copy_partial(String *dest, String src); + +inline void copy(char *dest, char *src) { copy_fast_unsafe(dest, src); } +inline void copy(String *dest, String src) { copy_checked(dest, src); } +inline void copy(String *dest, char *src) { copy_partial(dest, src); } + +FCPP_LINK bool append_checked(String *dest, String src); +FCPP_LINK bool append_partial(String *dest, char *src); +FCPP_LINK bool append_partial(String *dest, String src); + +FCPP_LINK bool append(String *dest, char c); +inline bool append(String *dest, String src) { return append_partial(dest, src); } +inline bool append(String *dest, char *src) { return append_partial(dest, src); } +inline void terminate_with_null(String *str) +{ if (str->size < str->memory_size) str->str[str->size] = 0; else str->str[str->size-1] = 0; } + +FCPP_LINK int compare(char *a, char *b); +FCPP_LINK int compare(String a, char *b); +inline int compare(char *a, String b) { return -compare(b,a); } +FCPP_LINK int compare(String a, String b); + +FCPP_LINK int reverse_seek_slash(String str); +inline bool get_front_of_directory(String *dest, String dir) { return append_checked(dest, substr(dir, reverse_seek_slash(dir) + 1)); } +inline bool get_path_of_directory(String *dest, String dir) { return append_checked(dest, substr(dir, 0, reverse_seek_slash(dir) + 1)); } +FCPP_LINK bool set_last_folder(String *dir, char *folder_name); +FCPP_LINK bool set_last_folder(String *dir, String folder_name); +FCPP_LINK String file_extension(String str); +FCPP_LINK String file_extension_slowly(char *str); + +inline String make_string(char *str, int size, int mem_size){ + String result; + result.str = str; + result.size = size; + result.memory_size = mem_size; + return result; +} + +inline String +make_string(char *str, int size){ + String result; + result.str = str; + result.size = size; + result.memory_size = size; + return result; +} + +inline String +make_string_slowly(char *str){ + String result; + result.str = str; + result.size = str_size(str); + result.memory_size = result.size; + return result; +} + +inline char* +make_c_str(String str){ + if (str.size < str.memory_size){ + str.str[str.size] = 0; + } + else{ + str.str[str.memory_size-1] = 0; + } + return (char*)str.str; +} + +inline String +substr(String str, int start){ + String result; + result.str = str.str + start; + result.size = str.size - start; + return result; +} + +inline String +substr(String str, int start, int size){ + String result; + result.str = str.str + start; + if (start + size > str.size){ + result.size = str.size - start; + } + else{ + result.size = size; + } + return result; +} + +inline String +substr_slowly(char *str, int start){ + String result; + result.str = str + start; + result.size = str_size(result.str); + return result; +} + +inline String +substr(char *str, int start, int size){ + String result; + result.str = str + start; + result.size = size; + for (int i = 0; i < size; ++i){ + if (result.str[i] == 0){ + result.size = i; + break; + } + } + return result; +} + +inline String +tailstr(String str){ + String result; + result.str = str.str + str.size; + result.memory_size = str.memory_size - str.size; + result.size = 0; + return result; +} + +#endif // #ifndef FCPP_STRING_INC + +#ifdef FCPP_STRING_IMPLEMENTATION + +FCPP_LINK int +str_size(char *str){ + int i = 0; + while (str[i]) ++i; + return i; +} + +FCPP_LINK bool +match(char *a, char *b){ + for (int i = 0;; ++i){ + if (a[i] != b[i]){ + return 0; + } + if (a[i] == 0){ + return 1; + } + } +} + +FCPP_LINK bool +match(String a, char *b){ + int i = 0; + for (; i < a.size; ++i){ + if (a.str[i] != b[i]){ + return 0; + } + } + if (b[i] != 0){ + return 0; + } + return 1; +} + +FCPP_LINK bool +match(String a, String b){ + if (a.size != b.size){ + return 0; + } + for (int i = 0; i < b.size; ++i){ + if (a.str[i] != b.str[i]){ + return 0; + } + } + return 1; +} + +FCPP_LINK bool +match_part(char *a, char *b, int *len){ + int i; + for (i = 0; b[i] != 0; ++i){ + if (a[i] != b[i]){ + return 0; + } + } + *len = i; + return 1; +} + +FCPP_LINK bool +match_part(String a, char *b, int *len){ + int i; + for (i = 0; b[i] != 0; ++i){ + if (a.str[i] != b[i] || i == a.size){ + return 0; + } + } + *len = i; + return 1; +} + +FCPP_LINK bool +match_part(char *a, String b){ + for (int i = 0; i != b.size; ++i){ + if (a[i] != b.str[i]){ + return 0; + } + } + return 1; +} + +FCPP_LINK bool +match_part(String a, String b){ + if (a.size < b.size){ + return 0; + } + for (int i = 0; i < b.size; ++i){ + if (a.str[i] != b.str[i]){ + return 0; + } + } + return 1; +} + +FCPP_LINK int +find(char *str, int start, char character){ + int i = start; + while (str[i] != character && str[i] != 0) ++i; + return i; +} + +FCPP_LINK int +find(String str, int start, char character){ + int i = start; + while (i < str.size && str.str[i] != character) ++i; + return i; +} + +FCPP_LINK int +find(char *str, int start, char *characters){ + int i = start, j; + while (str[i] != 0){ + for (j = 0; characters[j]; ++j){ + if (str[i] == characters[j]){ + return i; + } + } + ++i; + } + return i; +} + +FCPP_LINK int +find(String str, int start, char *characters){ + int i = start, j; + while (i < str.size){ + for (j = 0; characters[j]; ++j){ + if (str.str[i] == characters[j]){ + return i; + } + } + ++i; + } + return i; +} + +FCPP_LINK int +find_substr(char *str, int start, String seek){ + int i, j, k; + bool hit; + + if (seek.size == 0){ + return str_size(str); + } + for (i = start; str[i]; ++i){ + if (str[i] == seek.str[0]){ + hit = 1; + for (j = 1, k = i+1; j < seek.size; ++j, ++k){ + if (str[k] != seek.str[j]){ + hit = 0; + break; + } + } + if (hit){ + return i; + } + } + } + return i; +} + +FCPP_LINK int +find_substr(String str, int start, String seek){ + int stop_at, i, j, k; + bool hit; + + if (seek.size == 0){ + return str.size; + } + stop_at = str.size - seek.size + 1; + for (i = start; i < stop_at; ++i){ + if (str.str[i] == seek.str[0]){ + hit = 1; + for (j = 1, k = i+1; j < seek.size; ++j, ++k){ + if (str.str[k] != seek.str[j]){ + hit = 0; + break; + } + } + if (hit){ + return i; + } + } + } + return str.size; +} + +FCPP_LINK int +rfind_substr(String str, int start, String seek){ + int i, j, k; + bool hit; + + if (seek.size == 0){ + return -1; + } + if (start + seek.size > str.size){ + start = str.size - seek.size; + } + for (i = start; i >= 0; --i){ + if (str.str[i] == seek.str[0]){ + hit = 1; + for (j = 1, k = i+1; j < seek.size; ++j, ++k){ + if (str.str[k] != seek.str[j]){ + hit = 0; + break; + } + } + if (hit){ + return i; + } + } + } + return -1; +} + +FCPP_LINK int +find_substr_unsensitive(char *str, int start, String seek){ + int i, j, k; + bool hit; + char a_upper, b_upper; + + if (seek.size == 0){ + return str_size(str); + } + for (i = start; str[i]; ++i){ + if (str[i] == seek.str[0]){ + hit = 1; + for (j = 1, k = i+1; j < seek.size; ++j, ++k){ + a_upper = char_to_upper(str[k]); + b_upper = char_to_upper(seek.str[j]); + if (a_upper != b_upper){ + hit = 0; + break; + } + } + if (hit){ + return i; + } + } + } + return i; +} + +FCPP_LINK int +find_substr_unsensitive(String str, int start, String seek){ + int i, j, k; + int stop_at; + bool hit; + char a_upper, b_upper; + + if (seek.size == 0){ + return str.size; + } + stop_at = str.size - seek.size + 1; + for (i = start; i < stop_at; ++i){ + if (str.str[i] == seek.str[0]){ + hit = 1; + for (j = 1, k = i+1; j < seek.size; ++j, ++k){ + a_upper = char_to_upper(str.str[k]); + b_upper = char_to_upper(seek.str[j]); + if (a_upper != b_upper){ + hit = 0; + break; + } + } + if (hit){ + return i; + } + } + } + return str.size; +} + +FCPP_LINK int +int_to_str_size(int x){ + int size; + if (x < 0){ + size = 0; + } + else{ + size = 1; + x /= 10; + while (x != 0){ + x /= 10; + ++size; + } + } + return size; +} + +FCPP_LINK int +int_to_str(int x, char *str){ + int size, i, j; + bool negative; + char temp; + + size = 0; + if (x == 0){ + str[0] = '0'; + str[1] = 0; + size = 1; + } + else{ + size = 0; + negative = 0; + if (x < 0){ + negative = 1; + x = -x; + str[size++] = '-'; + } + while (x != 0){ + i = x % 10; + x /= 10; + str[size++] = (char)('0' + i); + } + // NOTE(allen): Start i = 0 if not negative, start i = 1 if is negative + // because - should not be flipped if it is negative :) + for (i = negative, j = size-1; i < j; ++i, --j){ + temp = str[i]; + str[i] = str[j]; + str[j] = temp; + } + str[size] = 0; + } + return size; +} + +FCPP_LINK bool +int_to_str(int x, String *dest){ + bool result = 1; + char *str = dest->str; + int memory_size = dest->memory_size; + int size, i, j; + bool negative; + + if (x == 0){ + str[0] = '0'; + dest->size = 1; + } + else{ + size = 0; + negative = 0; + if (x < 0){ + negative = 1; + x = -x; + str[size++] = '-'; + } + while (x != 0){ + if (size == memory_size){ + result = 0; + break; + } + i = x % 10; + x /= 10; + str[size++] = (char)('0' + i); + } + if (result){ + // NOTE(allen): Start i = 0 if not negative, start i = 1 if is negative + // because - should not be flipped if it is negative :) + for (i = negative, j = size-1; i < j; ++i, --j){ + char temp = str[i]; + str[i] = str[j]; + str[j] = temp; + } + dest->size = size; + } + else{ + dest->size = 0; + } + } + return result; +} + +FCPP_LINK bool +append_int_to_str(int x, String *dest){ + String last_part = tailstr(*dest); + bool result = int_to_str(x, &last_part); + if (result){ + dest->size += last_part.size; + } + return result; +} + +FCPP_LINK int +str_to_int(String str){ + int x, i; + if (str.size == 0){ + x = 0; + } + else{ + x = str.str[0] - '0'; + for (i = 1; i < str.size; ++i){ + x *= 10; + x += str.str[i] - '0'; + } + } + return x; +} + +FCPP_LINK int +hexchar_to_int(char c){ + int x; + if (c >= '0' && c <= '9'){ + x = c-'0'; + } + else if (c > 'F'){ + x = c+(10-'a'); + } + else{ + x = c+(10-'A'); + } + return x; +} + +FCPP_LINK char +int_to_hexchar(int x){ + return (x<10)?((char)x+'0'):((char)x+'a'-10); +} + +FCPP_LINK int +hexstr_to_int(String str){ + int x, i; + if (str.size == 0){ + x = 0; + } + else{ + x = hexchar_to_int(str.str[0]); + for (i = 1; i < str.size; ++i){ + x *= 0x10; + x += hexchar_to_int(str.str[i]); + } + } + return x; +} + +FCPP_LINK void +copy_fast_unsafe(char *dest, char *src){ + int i = 0; + while (src[i] != 0){ + dest[i] = src[i]; + ++i; + } + dest[i] = 0; +} + +FCPP_LINK void +copy_fast_unsafe(char *dest, String src){ + int i = 0; + while (i != src.size){ + dest[i] = src.str[i]; + ++i; + } + dest[i] = 0; +} + +FCPP_LINK bool +copy_checked(String *dest, String src){ + char *dest_str; + int i; + if (dest->memory_size < src.size){ + return 0; + } + dest_str = dest->str; + for (i = 0; i < src.size; ++i){ + dest_str[i] = src.str[i]; + } + dest->size = src.size; + return 1; +} + +FCPP_LINK bool +copy_partial(String *dest, char *src){ + int i = 0; + int memory_size = dest->memory_size; + char *dest_str = dest->str; + while (src[i] != 0){ + if (i >= memory_size){ + return 0; + } + dest_str[i] = src[i]; + ++i; + } + dest->size = i; + return 1; +} + +FCPP_LINK bool +copy_partial(String *dest, String src){ + bool result; + int memory_size = dest->memory_size; + char *dest_str = dest->str; + if (memory_size < src.size){ + result = 0; + for (int i = 0; i < memory_size; ++i){ + dest_str[i] = src.str[i]; + } + dest->size = memory_size; + } + else{ + result = 1; + for (int i = 0; i < src.size; ++i){ + dest_str[i] = src.str[i]; + } + dest->size = src.size; + } + return result; +} + +FCPP_LINK bool +append_checked(String *dest, String src){ + String end; + end = tailstr(*dest); + bool result = copy_checked(&end, src); + // NOTE(allen): This depends on end.size still being 0 if + // the check failed and no coppy occurred. + dest->size += end.size; + return result; +} + +FCPP_LINK bool +append_partial(String *dest, char *src){ + String end = tailstr(*dest); + bool result = copy_partial(&end, src); + dest->size += end.size; + return result; +} + +FCPP_LINK bool +append_partial(String *dest, String src){ + String end = tailstr(*dest); + bool result = copy_partial(&end, src); + dest->size += end.size; + return result; +} + +FCPP_LINK bool +append(String *dest, char c){ + bool result = 0; + if (dest->size < dest->memory_size){ + dest->str[dest->size++] = c; + result = 1; + } + return result; +} + +FCPP_LINK int +compare(char *a, char *b){ + int i = 0; + while (a[i] == b[i] && a[i] != 0){ + ++i; + } + return (a[i] > b[i]) - (a[i] < b[i]); +} + +FCPP_LINK int +compare(String a, char *b){ + int i = 0; + while (i < a.size && a.str[i] == b[i]){ + ++i; + } + if (i < a.size){ + return (a.str[i] > b[i]) - (a.str[i] < b[i]); + } + else{ + if (b[i] == 0){ + return 0; + } + else{ + return -1; + } + } +} + +FCPP_LINK int +compare(String a, String b){ + int i = 0; + while (i < a.size && i < b.size && a.str[i] == b.str[i]){ + ++i; + } + if (i < a.size && i < b.size){ + return (a.str[i] > b.str[i]) - (a.str[i] < b.str[i]); + } + else{ + return (a.size > b.size) - (a.size < b.size); + } +} + +FCPP_LINK int +reverse_seek_slash(String str){ + int i = str.size - 1; + while (i >= 0 && char_not_slash(str.str[i])){ + --i; + } + return i; +} + +FCPP_LINK bool +set_last_folder(String *dir, char *folder_name){ + bool result = 0; + int size = reverse_seek_slash(*dir) + 1; + dir->size = size; + if (append(dir, folder_name)){ + if (append(dir, (char*)"\\")){ + result = 1; + } + } + if (!result){ + dir->size = size; + } + return result; +} + +FCPP_LINK bool +set_last_folder(String *dir, String folder_name){ + bool result = 0; + int size = reverse_seek_slash(*dir) + 1; + dir->size = size; + if (append(dir, folder_name)){ + if (append(dir, (char*)"\\")){ + result = 1; + } + } + if (!result){ + dir->size = size; + } + return result; +} + +FCPP_LINK String +file_extension(String str){ + int i; + for (i = str.size - 1; i >= 0; --i){ + if (str.str[i] == '.') break; + } + ++i; + return make_string(str.str+i, str.size-i); +} + +FCPP_LINK String +file_extension_slowly(char *str){ + i32 s, i; + for (s = 0; str[s]; ++s); + for (i = s - 1; i >= 0; --i){ + if (str[i] == '.') break; + } + ++i; + return make_string(str+i, s-i); +} + +// NOTE(allen): experimental section, things below here are +// not promoted to public API level yet. + +struct Absolutes{ + String a[8]; + int count; +}; + +FCPP_LINK void +get_absolutes(String name, Absolutes *absolutes, bool implicit_first, bool implicit_last){ + int count = 0; + int max = ArrayCount(absolutes->a) - 1; + if (implicit_last) --max; + + String str; + str.str = name.str; + str.size = 0; + str.memory_size = 0; + bool prev_was_wild = 0; + + if (implicit_first){ + absolutes->a[count++] = str; + prev_was_wild = 1; + } + + int i; + for (i = 0; i < name.size; ++i){ + if (name.str[i] == '*' && count < max){ + if (!prev_was_wild){ + str.memory_size = str.size; + absolutes->a[count++] = str; + str.size = 0; + } + str.str = name.str + i + 1; + prev_was_wild = 1; + } + else{ + ++str.size; + prev_was_wild = 0; + } + } + + str.memory_size = str.size; + absolutes->a[count++] = str; + + if (implicit_last){ + str.size = 0; + str.memory_size = 0; + absolutes->a[count++] = str; + } + + absolutes->count = count; +} + +FCPP_LINK bool +wildcard_match(Absolutes *absolutes, char *x){ + bool r = 1; + String *a = absolutes->a; + if (absolutes->count == 1){ + r = match(x, *a); + } + else{ + if (!match_part(x, *a)){ + r = 0; + } + else{ + String *max = a + absolutes->count - 1; + x += a->size; + ++a; + while (a < max){ + if (*x == 0){ + r = 0; + break; + } + if (match_part(x, *a)){ + x += a->size; + ++a; + } + else{ + ++x; + } + } + if (r && a->size > 0){ + r = 0; + while (*x != 0){ + if (match_part(x, *a) && *(x + a->size) == 0){ + r = 1; + break; + } + else{ + ++x; + } + } + } + } + } + return r; +} + +FCPP_LINK bool +wildcard_match(Absolutes *absolutes, String x){ + terminate_with_null(&x); + return wildcard_match(absolutes, x.str); +} + +#undef FCPP_STRING_IMPLEMENTATION +#endif // #ifdef FCPP_STRING_IMPLEMENTATION + +// BOTTOM + diff --git a/4cpp_types.h b/4cpp_types.h new file mode 100644 index 00000000..b0f067f7 --- /dev/null +++ b/4cpp_types.h @@ -0,0 +1,39 @@ +/* "4cpp" Open C++ Parser v0.1: Types + no warranty implied; use at your own risk + +NOTES ON USE: + This file is used to declare 4cpp fixed width integer and float types. + It is not meant to be used directly. +*/ + +// TODO(allen): +// - create non stdint.h version in case someone wants to exclude that header + +#include "4cpp_config.h" + +#ifndef FCPP_TYPES +#define FCPP_TYPES + +#include + +typedef uint8_t fcpp_u8; +typedef uint64_t fcpp_u64; +typedef uint32_t fcpp_u32; +typedef uint16_t fcpp_u16; + +typedef int8_t fcpp_i8; +typedef int64_t fcpp_i64; +typedef int32_t fcpp_i32; +typedef int16_t fcpp_i16; + +typedef fcpp_i32 fcpp_bool32; +typedef fcpp_i8 fcpp_bool8; + +typedef float fcpp_real32; +typedef double fcpp_real64; + +#define FCPP_GLOBAL static + +#define FCPP_COUNT(a) (sizeof(a)/sizeof(*(a))) + +#endif diff --git a/4ed.cpp b/4ed.cpp new file mode 100644 index 00000000..632eeb6f --- /dev/null +++ b/4ed.cpp @@ -0,0 +1,3164 @@ +/* + * Mr. 4th Dimention - Allen Webster + * + * 12.12.2014 + * + * Application layer for project codename "4ed" + * + */ + +// TOP +// TODO(allen): +// +// BUGS & PROBLEMS +// +// - LOGGING SYSTEM FOR ALL BUILD CONFIGS +// +// - line_wrap_ys remeasurement optimization +// +// GENERAL +// +// - eol regularization +// +// - untabification +// +// - mode switching +// +// - auto mode switching +// +// - meta command stuff +// command use frequency +// +// - configuration / GUI for generating configuration +// +// - no scroll on line wrap/line mode change +// +// - travel packaging +// +// - multiple live name conflicts +// +// - nav links +// +// - undo / redo +// +// - on file reopen diff and try to find place for cursor +// +// TOOLS +// +// - calculator with: dec hex translation +// +// - solver +// +// TEXT MODE +// +// - replace +// +// - select word +// +// - match list +// +// - regular expression +// +// BASIC CODE MODE - general any language +// +// - select token +// +// - seek token or whitespace within token +// +// - reprogrammable lexing / auto indent +// +// - bracket match / mismatch highlighting +// +// - smart line wrapping (using tokens and white space as separation points) +// +// - auto casing rules? +// +// SUPER CODE MODE - C/C++ editing +// +// - identifier rename +// +// - boolean inverse polarity +// +// - compose/explode class/hierarchy +// - virtual to vtable sim/switch +// +// - explode class template? +// +// - enumerate template combinations? +// +// - generate header +// + +/* + * App Structs + */ + +enum App_State{ + APP_STATE_EDIT, + APP_STATE_RESIZING, + // never below this + APP_STATE_COUNT +}; + +struct App_State_Resizing{ + Panel_Divider *divider; + i32 min, max; +}; + +struct App_Vars{ + Mem_Options mem; + Command_Map map_top; + Command_Map map_file; + Command_Map map_ui; +#if FRED_INTERNAL + Command_Map map_debug; +#endif + + Font_Set fonts; + + Style style; + Style_Library styles; + u32 *palette; + i32 palette_size; + + Editing_Layout layout; + Live_Views live_set; + Working_Set working_set; + + char hot_dir_base_[256]; + String hot_dir_base; + Hot_Directory hot_directory; + + char query_[256]; + char dest_[256]; + + Delay delay; + + String mini_str; + u8 mini_buffer[512]; + + App_State state; + App_State_Resizing resizing; + Panel *prev_mouse_panel; +}; + +/* + * Commands + */ + +globalvar Application_Links app_links; + +#define USE_MEM(n) Mem_Options *n = command->mem +#define USE_PANEL(n) Panel *n = command->panel +#define USE_VIEW(n) View *n = command->view +#define USE_WORKING_SET(n) Working_Set *n = command->working_set +#define USE_LAYOUT(n) Editing_Layout *n = command->layout +#define USE_LIVE_SET(n) Live_Views *live_set = command->live_set +#define USE_STYLE(n) Style *n = command->style +#define USE_DELAY(n) Delay *n = command->delay +#define USE_VARS(n) App_Vars *n = command->vars + +#define REQ_VIEW(n) View *n = command->view; if (!n) return +#define REQ_FILE_VIEW(n) File_View *n = view_to_file_view(command->view); if (!n) return +#define REQ_FILE(n,v) Editing_File *n = (v)->file; if (!n || !n->data || n->is_dummy) return +#define REQ_COLOR_VIEW(n) Color_View *n = view_to_color_view(command->view); if (!n) return +#define REQ_DBG_VIEW(n) Debug_View *n = view_to_debug_view(command->view); if (!n) return + +#define COMMAND_DECL(n) internal void command_##n(Command_Data *command, Command_Binding binding) + +struct Command_Data{ + Mem_Options *mem; + Panel *panel; + View *view; + Working_Set *working_set; + Editing_Layout *layout; + Live_Views *live_set; + Style *style; + Delay *delay; + App_Vars *vars; + + i32 screen_width, screen_height; + Key_Single key; +}; + +COMMAND_DECL(null){ + AllowLocal(command); +} + +COMMAND_DECL(write_character){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + USE_MEM(mem); + + u8 character = (u8)command->key.key.character; + char str_space[2]; + String string = make_string(str_space, 2); + if (character == '\n' && file->endline_mode == ENDLINE_RN_COMBINED){ + str_space[0] = '\r'; + str_space[1] = '\n'; + string.size = 2; + } + else{ + str_space[0] = character; + string.size = 1; + } + + i32 pos = pos_adjust_to_left(view->cursor.pos, file->data); + buffer_replace_range(mem, file, pos, pos, (u8*)string.str, string.size); + +#if 0 + switch ((u8)command->key.key.character){ + case '{': case '}': + case '(': case ')': + case ';': case ':': + case '#': case '\n': + { + view_auto_tab(view, view->cursor.pos, view->cursor.pos); + }break; + } +#endif + + Editing_Layout *layout = command->layout; + Panel *current_panel = layout->panels; + i32 panel_count = layout->panel_count; + for (i32 i = 0; i < panel_count; ++i, ++current_panel){ + File_View *current_view = view_to_file_view(current_panel->view); + if (current_view && current_view->file == file){ + view_measure_wraps(&mem->general, current_view); + if (current_view->cursor.pos >= pos){ + view_cursor_move(current_view, current_view->cursor.pos+string.size); + } + if (current_view->mark >= pos){ + current_view->mark += string.size; + } + current_view->preferred_x = view_get_cursor_x(current_view); + } + } + file->cursor.pos = view->cursor.pos; +} + +internal i32 +seek_whitespace_right(u8 *data, i32 size, i32 pos){ + while (pos < size && char_is_whitespace(data[pos])){ + ++pos; + } + while (pos < size && !char_is_whitespace(data[pos])){ + ++pos; + } + return pos; +} + +internal i32 +seek_whitespace_left(u8 *data, i32 pos){ + --pos; + while (pos > 0 && char_is_whitespace(data[pos])){ + --pos; + } + while (pos >= 0 && !char_is_whitespace(data[pos])){ + --pos; + } + ++pos; + return pos; +} + +COMMAND_DECL(seek_whitespace_right){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + + i32 pos = seek_whitespace_right( + file->data, file->size, view->cursor.pos); + + view_cursor_move(view, pos); +} + +COMMAND_DECL(seek_whitespace_left){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + + i32 pos = seek_whitespace_left( + file->data, view->cursor.pos); + + view_cursor_move(view, pos); +} + +COMMAND_DECL(seek_whitespace_up){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + + u8* data = file->data; + u32 pos = view->cursor.pos; + while (pos > 0 && char_is_whitespace(data[pos])){ + --pos; + } + + bool32 no_hard_character = 0; + while (pos > 0){ + if (starts_new_line(data[pos], file->endline_mode)){ + if (no_hard_character){ + break; + } + else{ + no_hard_character = 1; + } + } + else{ + if (!char_is_whitespace(data[pos])){ + no_hard_character = 0; + } + } + --pos; + } + + if (pos != 0){ + ++pos; + } + + if (file->endline_mode == ENDLINE_RN_COMBINED){ + pos = pos_adjust_to_self(pos, data, file->size); + } + + view_cursor_move(view, pos); +} + +COMMAND_DECL(seek_whitespace_down){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + + i32 size = file->size; + u8* data = file->data; + i32 pos = view->cursor.pos; + while (pos < size && char_is_whitespace(data[pos])){ + ++pos; + } + + bool32 no_hard_character = 0; + i32 prev_endline = -1; + while (pos < size){ + if (starts_new_line(data[pos], file->endline_mode)){ + if (no_hard_character){ + break; + } + else{ + no_hard_character = 1; + prev_endline = pos; + } + } + else{ + if (!char_is_whitespace(data[pos])){ + no_hard_character = 0; + } + } + ++pos; + } + + if (prev_endline == -1 || prev_endline+1 >= size){ + pos = size-1; + } + else{ + pos = prev_endline+1; + } + + view_cursor_move(view, pos); +} + +internal i32 +seek_token_left(Cpp_Token_Stack *tokens, i32 pos){ + Cpp_Get_Token_Result get = cpp_get_token(tokens, pos); + if (get.token_index == -1){ + get.token_index = 0; + } + + Cpp_Token *token = tokens->tokens + get.token_index; + if (token->start == pos && get.token_index > 0){ + --token; + } + + return token->start; +} + +internal i32 +seek_token_right(Cpp_Token_Stack *tokens, i32 pos){ + Cpp_Get_Token_Result get = cpp_get_token(tokens, pos); + if (get.in_whitespace){ + ++get.token_index; + } + if (get.token_index >= tokens->count){ + get.token_index = tokens->count-1; + } + + Cpp_Token *token = tokens->tokens + get.token_index; + return token->start + token->size; +} + +COMMAND_DECL(seek_token_left){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + + if (file->tokens_complete){ + i32 pos = seek_token_left(&file->token_stack, view->cursor.pos); + view_cursor_move(view, pos); + } +} + +COMMAND_DECL(seek_token_right){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + + if (file->tokens_complete){ + i32 pos = seek_token_right(&file->token_stack, view->cursor.pos); + view_cursor_move(view, pos); + } +} + +COMMAND_DECL(seek_white_or_token_right){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + + i32 token_pos, white_pos; + token_pos = file->size; + if (file->tokens_complete){ + token_pos = seek_token_right(&file->token_stack, view->cursor.pos); + } + white_pos = seek_whitespace_right(file->data, file->size, view->cursor.pos); + view_cursor_move(view, Min(token_pos, white_pos)); +} + +COMMAND_DECL(seek_white_or_token_left){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + + i32 token_pos, white_pos; + token_pos = file->size; + if (file->tokens_complete){ + token_pos = seek_token_left(&file->token_stack, view->cursor.pos); + } + white_pos = seek_whitespace_left(file->data, view->cursor.pos); + view_cursor_move(view, Max(token_pos, white_pos)); +} + +internal i32 +seek_alphanumeric_right(u8 *data, i32 size, i32 pos){ + while (pos < size && !char_is_alpha_numeric_true(data[pos])){ + ++pos; + } + while (pos < size && char_is_alpha_numeric_true(data[pos])){ + ++pos; + } + return pos; +} + +internal i32 +seek_alphanumeric_left(u8 *data, i32 pos){ + --pos; + while (pos > 0 && !char_is_alpha_numeric_true(data[pos])){ + --pos; + } + while (pos >= 0 && char_is_alpha_numeric_true(data[pos])){ + --pos; + } + ++pos; + return pos; +} + +COMMAND_DECL(seek_alphanumeric_right){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + + i32 pos = seek_alphanumeric_right( + file->data, file->size, view->cursor.pos); + view_cursor_move(view, pos); +} + +COMMAND_DECL(seek_alphanumeric_left){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + + i32 pos = seek_alphanumeric_left( + file->data, view->cursor.pos); + view_cursor_move(view, pos); +} + +COMMAND_DECL(seek_alphanumeric_or_camel_right){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + + u8 *data = file->data; + + i32 an_pos, camel_pos; + an_pos = seek_alphanumeric_right( + file->data, file->size, view->cursor.pos); + + u8 curr_char; + u8 prev_char = data[view->cursor.pos + 1]; + for (camel_pos = view->cursor.pos + 2; camel_pos < an_pos; ++camel_pos){ + curr_char = data[camel_pos]; + if (char_is_upper(curr_char) && char_is_lower(prev_char)){ + break; + } + prev_char = curr_char; + } + + view_cursor_move(view, camel_pos); +} + +COMMAND_DECL(seek_alphanumeric_or_camel_left){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + + u8 *data = file->data; + + i32 an_pos, camel_pos; + an_pos = seek_alphanumeric_left( + data, view->cursor.pos); + + char curr_char; + char prev_char = data[view->cursor.pos]; + for (camel_pos = view->cursor.pos - 1; camel_pos > an_pos; --camel_pos){ + curr_char = data[camel_pos]; + if (char_is_upper(curr_char) && char_is_lower(prev_char)){ + break; + } + prev_char = curr_char; + } + + view_cursor_move(view, camel_pos); +} + +COMMAND_DECL(search){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + USE_VARS(vars); + + view->state = FVIEW_STATE_SEARCH; + view->isearch.str = vars->mini_str; + view->isearch.reverse = 0; + view->isearch.pos = view->cursor.pos; +} + +COMMAND_DECL(rsearch){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + USE_VARS(vars); + + view->state = FVIEW_STATE_SEARCH; + view->isearch.str = vars->mini_str; + view->isearch.reverse = 1; + view->isearch.pos = view->cursor.pos; +} + +COMMAND_DECL(goto_line){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + USE_VARS(vars); + + view->state = FVIEW_STATE_GOTO_LINE; + view->isearch.str = vars->mini_str; + view->isearch.reverse = 1; + view->isearch.pos = view->cursor.pos; +} + +COMMAND_DECL(set_mark){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + + view->mark = (i32)view->cursor.pos; +} + +COMMAND_DECL(copy){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + USE_WORKING_SET(working_set); + USE_MEM(mem); + + Range range = get_range(view->cursor.pos, view->mark); + if (range.smaller < range.larger){ + u8 *data = file->data; + if (file->endline_mode == ENDLINE_RN_COMBINED){ + range = range_adjust_to_left(range, data); + } + clipboard_copy(&mem->general, working_set, data, range); + } +} + +COMMAND_DECL(cut){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + USE_WORKING_SET(working_set); + USE_MEM(mem); + + Range range = get_range(view->cursor.pos, view->mark); + if (range.smaller < range.larger){ + u8 *data = file->data; + if (file->endline_mode == ENDLINE_RN_COMBINED){ + range = range_adjust_to_left(range, data); + } + clipboard_copy(&mem->general, working_set, data, range); + buffer_delete_range(mem, file, range); + + view->mark = pos_universal_fix(range.smaller, + file->data, file->size, + file->endline_mode); + view_measure_wraps(&mem->general, view); + view_cursor_move(view, view->mark); + + Editing_Layout *layout = command->layout; + Panel *panels = layout->panels; + i32 panel_count = layout->panel_count; + i32 shift_amount = range.end - range.start; + for (i32 i = 0; i < panel_count; ++i){ + Panel *current_panel = panels + i; + File_View *current_view = view_to_file_view(current_panel->view); + + if (current_view && current_view != view && current_view->file == view->file){ + if (current_view->mark >= range.end){ + current_view->mark -= shift_amount; + } + else if (current_view->mark > range.start){ + current_view->mark = range.start; + } + + view_measure_wraps(&mem->general, current_view); + + i32 cursor_pos = current_view->cursor.pos; + if (cursor_pos >= range.end){ + view_cursor_move(current_view, cursor_pos - shift_amount); + } + else if (cursor_pos > range.start){ + view_cursor_move(current_view, range.start); + } + } + } + } +} + +internal void +view_post_paste_effect(File_View *view, i32 ticks, i32 start, i32 size, u32 color){ + view->paste_effect.start = start; + view->paste_effect.end = start + size; + view->paste_effect.color = color; + view->paste_effect.tick_down = ticks; + view->paste_effect.tick_max = ticks; +} + +COMMAND_DECL(paste){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + USE_WORKING_SET(working_set); + USE_MEM(mem); + + if (working_set->clipboard_size > 0){ + view->next_mode.rewrite = 1; + + String *src = working_set_clipboard_head(working_set); + i32 pos_left = view->cursor.pos; + if (file->endline_mode == ENDLINE_RN_COMBINED){ + pos_left = pos_adjust_to_left(pos_left, file->data); + } + + buffer_replace_range(mem, file, pos_left, pos_left, (u8*)src->str, src->size); + + view_measure_wraps(&mem->general, view); + view_cursor_move(view, pos_left+src->size); + view->mark = pos_universal_fix(pos_left, + file->data, file->size, + file->endline_mode); + + Editing_Layout *layout = command->layout; + Panel *panels = layout->panels; + i32 panel_count = layout->panel_count; + for (i32 i = 0; i < panel_count; ++i){ + Panel *current_panel = panels + i; + File_View *current_view = view_to_file_view(current_panel->view); + + if (current_view && current_view->file == file){ + if (current_view != view){ + view_measure_wraps(&mem->general, current_view); + if (current_view->cursor.pos > pos_left){ + view_cursor_move(current_view, + current_view->cursor.pos+src->size); + } + + if (current_view->mark > pos_left){ + current_view->mark += src->size; + } + } + + view_post_paste_effect(current_view, 20, pos_left, src->size, + current_view->style->main.paste_color); + } + } + } +} + +COMMAND_DECL(paste_next){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + USE_WORKING_SET(working_set); + USE_MEM(mem); + + if (working_set->clipboard_size > 0 && view->mode.rewrite){ + view->next_mode.rewrite = 1; + + Range range = get_range(view->mark, view->cursor.pos); + + if (range.smaller < range.larger || 1){ + if (file->endline_mode == ENDLINE_RN_COMBINED){ + range = range_adjust_to_left(range, file->data); + } + + String *src = working_set_clipboard_roll_down(working_set); + buffer_replace_range(mem, file, range.smaller, range.larger, + (u8*)src->str, src->size); + + view_measure_wraps(&mem->general, view); + + view->cursor = view_compute_cursor_from_pos(view, range.smaller+src->size); + view->preferred_x = view_get_cursor_x(view); + view->file->cursor.pos = view->cursor.pos; + + view->mark = pos_universal_fix(range.smaller, + file->data, file->size, + file->endline_mode); + + Editing_Layout *layout = command->layout; + Panel *panels = layout->panels; + i32 panel_count = layout->panel_count; + i32 shift_amount = range.start - range.end + src->size; + for (i32 i = 0; i < panel_count; ++i){ + Panel *current_panel = panels + i; + File_View *current_view = view_to_file_view(current_panel->view); + + if (current_view && current_view->file == file){ + if (current_view != view){ + view_measure_wraps(&mem->general, current_view); + if (current_view->cursor.pos >= range.larger){ + view_cursor_move(current_view, + current_view->cursor.pos + shift_amount); + } + else if (current_view->cursor.pos > range.smaller){ + view_cursor_move(current_view, range.smaller); + } + + if (current_view->mark >= range.larger){ + current_view->mark += shift_amount; + } + else if (current_view->cursor.pos > range.smaller){ + current_view->mark = range.smaller; + } + } + + view_post_paste_effect(current_view, 20, range.smaller, src->size, + current_view->style->main.paste_color); + } + } + } + } + else{ + command_paste(command, binding); + } +} + +COMMAND_DECL(delete_chunk){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + USE_MEM(mem); + + Range range = get_range(view->cursor.pos, view->mark); + if (range.smaller < range.larger){ + if (file->endline_mode == ENDLINE_RN_COMBINED){ + range = range_adjust_to_left(range, file->data); + } + buffer_delete_range(mem, file, range); + view_measure_wraps(&mem->general, view); + view->cursor = view_compute_cursor_from_pos(view, range.smaller); + view->mark = pos_universal_fix(range.smaller, + file->data, file->size, + file->endline_mode); + view->file->cursor.pos = view->cursor.pos; + + Editing_Layout *layout = command->layout; + Panel *panels = layout->panels; + i32 panel_count = layout->panel_count; + i32 shift_amount = range.smaller - range.larger; + for (i32 i = 0; i < panel_count; ++i){ + Panel *current_panel = panels + i; + File_View *current_view = view_to_file_view(current_panel->view); + + if (current_view && current_view->file == file && current_view != view){ + view_measure_wraps(&mem->general, current_view); + + if (current_view->cursor.pos >= range.larger){ + view_cursor_move(current_view, + current_view->cursor.pos + shift_amount); + } + else if (current_view->cursor.pos > range.smaller){ + view_cursor_move(current_view, range.smaller); + } + + if (current_view->mark >= range.larger){ + current_view->mark += shift_amount; + } + else if (current_view->cursor.pos > range.smaller){ + current_view->mark = range.smaller; + } + } + } + } +} + +COMMAND_DECL(interactive_new){ + ProfileMomentFunction(); + USE_VARS(vars); + USE_LIVE_SET(live_set); + USE_PANEL(panel); + USE_MEM(mem); + USE_WORKING_SET(working_set); + USE_STYLE(style); + USE_DELAY(delay); + + View *new_view = live_set_alloc_view(live_set, &mem->general); + view_replace_minor(new_view, panel, live_set); + + new_view->map = &vars->map_ui; + Interactive_View *int_view = + interactive_view_init(new_view, &vars->hot_directory, style, + working_set, delay); + int_view->interaction = INTV_SYS_FILE_LIST; + int_view->action = INTV_NEW; + copy(&int_view->query, "New: "); +} + +COMMAND_DECL(interactive_open){ + ProfileMomentFunction(); + USE_VARS(vars); + USE_LIVE_SET(live_set); + USE_PANEL(panel); + USE_MEM(mem); + USE_WORKING_SET(working_set); + USE_STYLE(style); + USE_DELAY(delay); + + View *new_view = live_set_alloc_view(live_set, &mem->general); + view_replace_minor(new_view, panel, live_set); + + new_view->map = &vars->map_ui; + Interactive_View *int_view = + interactive_view_init(new_view, &vars->hot_directory, style, + working_set, delay); + int_view->style = command->style; + int_view->interaction = INTV_SYS_FILE_LIST; + int_view->action = INTV_OPEN; + copy(&int_view->query, "Open: "); +} + +// TODO(allen): Improvements to reopen +// - Preserve existing token stack +// - Keep current version open and do some sort of diff to keep +// the cursor position correct +COMMAND_DECL(reopen){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + USE_STYLE(style); + USE_LAYOUT(layout); + USE_MEM(mem); + + Editing_File temp_file; + if (buffer_create(&mem->general, &temp_file, (u8*)make_c_str(file->source_path), style->font)){ + buffer_close(&mem->general, file); + *file = temp_file; + file->source_path.str = file->source_path_; + file->live_name.str = file->live_name_; + if (file->tokens_exist) + buffer_first_lex_parallel(&mem->general, file); + + i32 panel_count = layout->panel_count; + Panel *panels = layout->panels; + for (i32 i = 0; i < panel_count; ++i){ + Panel *current_panel = panels + i; + View *current_view_ = current_panel->view; + File_View *current_view = view_to_file_view(current_view_); + if (current_view && current_view->file == file){ + view_set_file(current_view, current_view->file, style); + } + } + } +} + +COMMAND_DECL(save){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + + String *file_path = &file->source_path; + if (file_path->size > 0){ + buffer_save(file, (u8*)file_path->str); + } +} + +COMMAND_DECL(interactive_save_as){ + ProfileMomentFunction(); + USE_VARS(vars); + USE_LIVE_SET(live_set); + USE_PANEL(panel); + USE_MEM(mem); + USE_WORKING_SET(working_set); + USE_STYLE(style); + USE_DELAY(delay); + + View *new_view = live_set_alloc_view(live_set, &mem->general); + view_replace_minor(new_view, panel, live_set); + + new_view->map = &vars->map_ui; + Interactive_View *int_view = + interactive_view_init(new_view, &vars->hot_directory, style, + working_set, delay); + int_view->style = command->style; + int_view->interaction = INTV_SYS_FILE_LIST; + int_view->action = INTV_SAVE_AS; + copy(&int_view->query, "Save As: "); +} + +COMMAND_DECL(change_active_panel){ + ProfileMomentFunction(); + USE_LAYOUT(layout); + if (layout->panel_count > 1){ + ++layout->active_panel; + if (layout->active_panel >= layout->panel_count){ + layout->active_panel = 0; + } + } +} + +COMMAND_DECL(interactive_switch_file){ + ProfileMomentFunction(); + USE_VARS(vars); + USE_LIVE_SET(live_set); + USE_PANEL(panel); + USE_MEM(mem); + USE_WORKING_SET(working_set); + USE_STYLE(style); + USE_DELAY(delay); + + View *new_view = live_set_alloc_view(live_set, &mem->general); + view_replace_minor(new_view, panel, live_set); + + new_view->map = &vars->map_ui; + Interactive_View *int_view = + interactive_view_init(new_view, &vars->hot_directory, style, + working_set, delay); + int_view->style = command->style; + int_view->interaction = INTV_LIVE_FILE_LIST; + int_view->action = INTV_SWITCH; + copy(&int_view->query, "Switch File: "); +} + +COMMAND_DECL(interactive_kill_file){ + ProfileMomentFunction(); + USE_VARS(vars); + USE_LIVE_SET(live_set); + USE_PANEL(panel); + USE_MEM(mem); + USE_WORKING_SET(working_set); + USE_STYLE(style); + USE_DELAY(delay); + + View *new_view = live_set_alloc_view(live_set, &mem->general); + view_replace_minor(new_view, panel, live_set); + + new_view->map = &vars->map_ui; + Interactive_View *int_view = + interactive_view_init(new_view, &vars->hot_directory, style, + working_set, delay); + int_view->style = command->style; + int_view->interaction = INTV_LIVE_FILE_LIST; + int_view->action = INTV_KILL; + copy(&int_view->query, "Kill File: "); +} + +COMMAND_DECL(kill_file){ + ProfileMomentFunction(); + USE_MEM(mem); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + USE_LIVE_SET(live_set); + USE_LAYOUT(layout); + USE_WORKING_SET(working_set); + + table_remove(&working_set->table, file->source_path); + kill_file(&mem->general, file, live_set, layout); +} + +COMMAND_DECL(toggle_line_wrap){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + + Relative_Scrolling scrolling = view_get_relative_scrolling(view); + if (view->unwrapped_lines){ + view->unwrapped_lines = 0; + view->target_x = 0; + view->cursor = + view_compute_cursor_from_pos(view, view->cursor.pos); + } + else{ + view->unwrapped_lines = 1; + view->cursor = + view_compute_cursor_from_pos(view, view->cursor.pos); + } + view_set_relative_scrolling(view, scrolling); +} + +COMMAND_DECL(toggle_endline_mode){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + + switch (file->endline_mode){ + case ENDLINE_RN_COMBINED: + { + view->cursor.pos = pos_adjust_to_left(view->cursor.pos, view->file->data); + file->endline_mode = ENDLINE_RN_SEPARATE; + view->cursor = + view_compute_cursor_from_pos(view, view->cursor.pos); + }break; + + case ENDLINE_RN_SEPARATE: + { + file->endline_mode = ENDLINE_RN_SHOWALLR; + view->cursor = + view_compute_cursor_from_pos(view, view->cursor.pos); + }break; + + case ENDLINE_RN_SHOWALLR: + { + view->cursor.pos = pos_adjust_to_self(view->cursor.pos, view->file->data, + view->file->size); + file->endline_mode = ENDLINE_RN_COMBINED; + view->cursor = + view_compute_cursor_from_pos(view, view->cursor.pos); + }break; + } +} + +COMMAND_DECL(toggle_show_whitespace){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + view->show_whitespace = !view->show_whitespace; +} + +COMMAND_DECL(toggle_tokens){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + USE_MEM(mem); + + if (file->tokens_exist){ + buffer_kill_tokens(&mem->general, file); + } + else{ + buffer_first_lex_parallel(&mem->general, file); + } +} + +COMMAND_DECL(to_uppercase){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + USE_MEM(mem); + + Range range = get_range(view->cursor.pos, view->mark); + if (range.smaller < range.larger){ + if (file->still_lexing){ + system_cancel_job(BACKGROUND_THREADS, file->lex_job); + } + + u8 *data = file->data; + for (i32 i = range.smaller; i < range.larger; ++i){ + if (data[i] >= 'a' && data[i] <= 'z'){ + data[i] += (u8)('A' - 'a'); + } + } + + if (file->token_stack.tokens){ + buffer_relex_parallel(mem, file, range.smaller, range.larger, 0); + } + } +} + +COMMAND_DECL(to_lowercase){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + USE_MEM(mem); + + Range range = get_range(view->cursor.pos, view->mark); + if (range.smaller < range.larger){ + if (file->still_lexing){ + system_cancel_job(BACKGROUND_THREADS, file->lex_job); + } + + u8 *data = file->data; + for (i32 i = range.smaller; i < range.larger; ++i){ + if (data[i] >= 'A' && data[i] <= 'Z'){ + data[i] -= (u8)('A' - 'a'); + } + } + + if (file->token_stack.tokens){ + buffer_relex_parallel(mem, file, range.smaller, range.larger, 0); + } + } +} + +internal void +view_clean_line(Mem_Options *mem, File_View *view, Editing_File *file, i32 line_start){ + u8 *data = file->data; + + i32 pos = line_start; + i32 current_x_bumps = 0; + i32 first_hard = -1; + while (pos < file->size && data[pos] != '\n'){ + if (!char_is_whitespace(data[pos])){ + first_hard = pos; + break; + } + else{ + switch (data[pos]){ + case ' ': current_x_bumps += 1; break; + case '\t': current_x_bumps += 4; break; + } + } + ++pos; + } + + Indent_Definition indent = indent_by_width(current_x_bumps, 4); + buffer_set_indent_whitespace(mem, file, indent, + (u8*)partition_current(&mem->part), line_start); + + pos = line_start; + i32 last_hard_start = pos-1; + i32 last_hard = last_hard_start; + while (pos < file->size && data[pos] != '\n'){ + if (!char_is_whitespace(data[pos])){ + last_hard = pos; + } + ++pos; + } + + if (last_hard != last_hard_start){ + pos = pos_adjust_to_left(pos, data); + + if (last_hard + 1 < pos){ + buffer_replace_range(mem, file, last_hard+1, pos, 0, 0, REP_WHITESPACE); + + if (view->cursor.pos > last_hard){ + view->cursor = view_compute_cursor_from_pos(view, last_hard + 1); + } + if (view->mark > last_hard && view->mark <= pos){ + view->mark = pos_adjust_to_self(last_hard+1, file->data, file->size); + } + else if (view->mark > pos){ + view->mark -= pos - (last_hard + 1); + } + } + } + else{ + //view_auto_tab(mem, view, pos, pos); + } +} + +COMMAND_DECL(clean_line){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + USE_MEM(mem); + + i32 line_start = view_find_beginning_of_line(view, view->cursor.pos); + view_clean_line(mem, view, file, line_start); + view_measure_wraps(&mem->general, view); +} + +COMMAND_DECL(clean_all_lines){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + USE_MEM(mem); + + for (i32 i = 0; i < file->line_count; ++i){ + i32 line_start = file->line_starts[i]; + view_clean_line(mem, view, file, line_start); + } + + view_measure_wraps(&mem->general, view); + view->cursor = view_compute_cursor_from_pos(view, view->cursor.pos); +} + +COMMAND_DECL(eol_dosify){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + USE_MEM(mem); + + view_endline_convert(mem, view, ENDLINE_RN, ENDLINE_ERASE, ENDLINE_RN); + view_measure_wraps(&mem->general, view); +} + +COMMAND_DECL(eol_nixify){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + USE_MEM(mem); + + view_endline_convert(mem, view, ENDLINE_N, ENDLINE_ERASE, ENDLINE_N); + view_measure_wraps(&mem->general, view); +} + +COMMAND_DECL(auto_tab){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + USE_MEM(mem); + + Range range = get_range(view->cursor.pos, view->mark); + view_auto_tab(mem, view, range.smaller, range.larger); + view_measure_wraps(&mem->general, view); +} + +COMMAND_DECL(open_panel_vsplit){ + ProfileMomentFunction(); + USE_LAYOUT(layout); + USE_PANEL(panel); + + i32 panel_count = layout->panel_count; + if (panel_count < layout->panel_max_count){ + Split_Result split = layout_split_panel(layout, panel, 1); + + Panel *panel1 = panel; + Panel *panel2 = split.panel; + + panel2->screen_region = panel1->screen_region; + + panel2->full.x0 = split.divider->pos; + panel2->full.x1 = panel1->full.x1; + panel1->full.x1 = split.divider->pos; + + panel_fix_internal_area(panel1); + panel_fix_internal_area(panel2); + panel2->prev_inner = panel2->inner; + + layout->active_panel = (i32)(panel2 - layout->panels); + } +} + +COMMAND_DECL(open_panel_hsplit){ + ProfileMomentFunction(); + USE_LAYOUT(layout); + USE_PANEL(panel); + + i32 panel_count = layout->panel_count; + if (panel_count < layout->panel_max_count){ + Split_Result split = layout_split_panel(layout, panel, 0); + + Panel *panel1 = panel; + Panel *panel2 = split.panel; + + panel2->screen_region = panel1->screen_region; + + panel2->full.y0 = split.divider->pos; + panel2->full.y1 = panel1->full.y1; + panel1->full.y1 = split.divider->pos; + + panel_fix_internal_area(panel1); + panel_fix_internal_area(panel2); + panel2->prev_inner = panel2->inner; + + layout->active_panel = (i32)(panel2 - layout->panels); + } +} + +COMMAND_DECL(close_panel){ + ProfileMomentFunction(); + USE_LAYOUT(layout); + USE_PANEL(panel); + USE_VIEW(view); + + if (layout->panel_count > 1){ + if (view){ + live_set_free_view(command->live_set, view); + panel->view = 0; + } + + Divider_And_ID div = layout_get_divider(layout, panel->parent); + Assert(div.divider->child1 == -1 || div.divider->child2 == -1); + + i32 child; + if (div.divider->child1 == -1){ + child = div.divider->child2; + } + else{ + child = div.divider->child1; + } + + i32 parent = div.divider->parent; + i32 which_child = div.divider->which_child; + if (parent != -1){ + Divider_And_ID par = layout_get_divider(layout, parent); + if (which_child == -1){ + par.divider->child1 = child; + } + else{ + par.divider->child2 = child; + } + } + else{ + Assert(layout->root == div.id); + layout->root = child; + } + + if (child != -1){ + Divider_And_ID chi = layout_get_divider(layout, child); + chi.divider->parent = parent; + chi.divider->which_child = div.divider->which_child; + } + + layout_free_divider(layout, div.divider); + layout_free_panel(layout, panel); + + if (child == -1){ + panel = layout->panels; + layout->active_panel = -1; + for (i32 i = 0; i < layout->panel_count; ++i){ + if (panel->parent == div.id){ + panel->parent = parent; + panel->which_child = which_child; + layout->active_panel = i; + break; + } + ++panel; + } + Assert(layout->active_panel != -1); + } + else{ + layout->active_panel = layout->active_panel % layout->panel_count; + } + + layout_fix_all_panels(layout); + } +} + +COMMAND_DECL(move_left){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + + u8 *data = file->data; + i32 pos = view->cursor.pos; + if (pos > 0){ + if (file->endline_mode == ENDLINE_RN_COMBINED){ + pos = pos_adjust_to_left(pos, data); + if (pos > 0){ + --pos; + } + else{ + pos = pos_adjust_to_self(pos, data, file->size); + } + } + else{ + --pos; + } + } + + view_cursor_move(view, pos); +} + +COMMAND_DECL(move_right){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + + i32 size = file->size; + u8* data = file->data; + i32 pos = view->cursor.pos; + if (pos < size){ + ++pos; + if (file->endline_mode == ENDLINE_RN_COMBINED){ + pos = pos_adjust_to_self(pos, data, size); + } + } + + view_cursor_move(view, pos); +} + +COMMAND_DECL(delete){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + USE_LAYOUT(layout); + USE_MEM(mem); + + i32 cursor_pos = view->cursor.pos; + u8 *data = file->data; + if (file->size > 0){ + Range range = {}; + if (file->endline_mode == ENDLINE_RN_COMBINED){ + if (cursor_pos > 0 && + data[cursor_pos-1] == '\r' && + data[cursor_pos] == '\n'){ + range.start = cursor_pos-1; + range.end = cursor_pos+1; + buffer_delete_range(mem, file, range); + } + + else{ + range.start = cursor_pos; + range.end = cursor_pos+1; + buffer_delete(mem, file, cursor_pos); + } + } + + else{ + range.start = cursor_pos; + range.end = cursor_pos+1; + buffer_delete(mem, file, cursor_pos); + } + + Panel *panels = layout->panels; + i32 panel_count = layout->panel_count; + i32 shift_amount = range.end - range.start; + for (i32 i = 0; i < panel_count; ++i){ + Panel *current_panel = panels + i; + File_View *current_view = view_to_file_view(current_panel->view); + if (!current_view) continue; + + if (current_view->file == file){ + view_measure_wraps(&mem->general, current_view); + + if (current_view->mark >= range.end){ + current_view->mark -= shift_amount; + } + else if (current_view->mark > range.start){ + current_view->mark = range.start; + } + + if (current_view->cursor.pos >= range.end){ + current_view->cursor.pos -= shift_amount; + view_cursor_move(current_view, current_view->cursor.pos); + } + else if (current_view->cursor.pos > range.start){ + current_view->cursor.pos = range.start; + view_cursor_move(current_view, current_view->cursor.pos); + } + } + } + } +} + +COMMAND_DECL(backspace){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + USE_MEM(mem); + + i32 cursor_pos = view->cursor.pos; + u8 *data = file->data; + if (cursor_pos > 0 && cursor_pos <= (i32)file->size){ + i32 shift_amount = 0; + if (file->endline_mode == ENDLINE_RN_COMBINED){ + i32 target_pos = cursor_pos; + if (cursor_pos > 1 && + data[cursor_pos] == '\n' && + data[cursor_pos-1] == '\r'){ + --target_pos; + } + + if (target_pos > 1 && + data[target_pos-1] == '\n' && + data[target_pos-2] == '\r'){ + buffer_delete_range(mem, file, target_pos-2, target_pos); + shift_amount = 2; + } + else{ + if (target_pos > 0){ + buffer_delete(mem, file, target_pos-1); + shift_amount = 1; + } + } + } + else{ + buffer_delete(mem, file, cursor_pos-1); + shift_amount = 1; + } + + Editing_Layout *layout = command->layout; + Panel *panels = layout->panels; + i32 panel_count = layout->panel_count; + + for (i32 i = 0; i < panel_count; ++i){ + Panel *current_panel = panels + i; + File_View *current_view = view_to_file_view(current_panel->view); + if (current_view && current_view->file == view->file){ + if (current_view->mark >= cursor_pos){ + current_view->mark -= shift_amount; + } + if (current_view->cursor.pos >= cursor_pos){ + current_view->cursor.pos -= shift_amount; + } + view_measure_wraps(&mem->general, current_view); + view_cursor_move(current_view, current_view->cursor.pos); + } + } + } +} + +COMMAND_DECL(move_up){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + + real32 cy = view_get_cursor_y(view)-view->style->font->height; + real32 px = view->preferred_x; + if (cy >= 0){ + view->cursor = view_compute_cursor_from_xy(view, px, cy); + view->file->cursor.pos = view->cursor.pos; + } +} + +COMMAND_DECL(move_down){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + + real32 cy = view_get_cursor_y(view)+view->style->font->height; + real32 px = view->preferred_x; + view->cursor = view_compute_cursor_from_xy(view, px, cy); + view->file->cursor.pos = view->cursor.pos; +} + +COMMAND_DECL(seek_end_of_line){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + + i32 pos = view_find_end_of_line(view, view->cursor.pos); + view_cursor_move(view, pos); +} + +COMMAND_DECL(seek_beginning_of_line){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + REQ_FILE(file, view); + + i32 pos = view_find_beginning_of_line(view, view->cursor.pos); + view_cursor_move(view, pos); +} + +COMMAND_DECL(page_down){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + + real32 height = view_compute_height(view); + real32 max_target_y = view_compute_max_target_y(view); + real32 cursor_y = view_get_cursor_y(view); + + view->target_y += height; + if (view->target_y > max_target_y) view->target_y = max_target_y; + + if (view->target_y >= cursor_y){ + view->cursor = + view_compute_cursor_from_xy(view, 0, view->target_y); + } +} + +COMMAND_DECL(page_up){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + + real32 height = view_compute_height(view); + real32 cursor_y = view_get_cursor_y(view); + + view->target_y -= height; + if (view->target_y < 0) view->target_y = 0; + + if (view->target_y + height <= cursor_y){ + view->cursor = + view_compute_cursor_from_xy(view, 0, view->target_y + height - view->font_height); + } +} + +inline void +open_theme_options(App_Vars *vars, Live_Views *live_set, General_Memory *general, Panel *panel){ + View *new_view = live_set_alloc_view(live_set, general); + view_replace_minor(new_view, panel, live_set); + + new_view->map = &vars->map_ui; + Color_View *color_view = color_view_init(new_view, &vars->working_set); + color_view->hot_directory = &vars->hot_directory; + color_view->main_style = &vars->style; + color_view->styles = &vars->styles; + color_view->palette = vars->palette; + color_view->palette_size = vars->palette_size; + color_view->fonts = &vars->fonts; +} + +COMMAND_DECL(open_color_tweaker){ + ProfileMomentFunction(); + USE_VARS(vars); + USE_LIVE_SET(live_set); + USE_MEM(mem); + USE_PANEL(panel); + + open_theme_options(vars, live_set, &mem->general, panel); +} + +COMMAND_DECL(open_menu){ + ProfileMomentFunction(); + USE_VARS(vars); + USE_LIVE_SET(live_set); + USE_PANEL(panel); + USE_MEM(mem); + USE_WORKING_SET(working_set); + USE_STYLE(style); + + View *new_view = live_set_alloc_view(live_set, &mem->general); + view_replace_minor(new_view, panel, live_set); + + new_view->map = &vars->map_ui; + Menu_View *menu_view = menu_view_init(new_view, style, working_set, &vars->delay); + AllowLocal(menu_view); +} + +#if FRED_INTERNAL +COMMAND_DECL(open_debug_view){ + ProfileMomentFunction(); + USE_VARS(vars); + USE_STYLE(style); + USE_LIVE_SET(live_set); + USE_PANEL(panel); + USE_MEM(mem); + + View *new_view = live_set_alloc_view(live_set, &mem->general); + view_replace_major(new_view, panel, live_set); + + new_view->map = &vars->map_debug; + Debug_View *debug_view = debug_view_init(new_view); + debug_view->font = style->font; + debug_view->mode = DBG_MEMORY; +} + +COMMAND_DECL(debug_memory){ + ProfileMomentFunction(); + REQ_DBG_VIEW(view); + view->mode = DBG_MEMORY; +} + +COMMAND_DECL(debug_os_events){ + ProfileMomentFunction(); + REQ_DBG_VIEW(view); + view->mode = DBG_OS_EVENTS; +} + +COMMAND_DECL(debug_profile){ + ProfileMomentFunction(); + REQ_DBG_VIEW(view); + view->mode = DBG_PROFILE; +} + +COMMAND_DECL(pause_unpause_profile){ + ProfileMomentFunction(); + INTERNAL_updating_profile = !INTERNAL_updating_profile; +} +#endif + +COMMAND_DECL(close_minor_view){ + ProfileMomentFunction(); + REQ_VIEW(view); + USE_PANEL(panel); + USE_LIVE_SET(live_set); + + view_remove_minor(panel, live_set); +} + +COMMAND_DECL(cursor_mark_swap){ + ProfileMomentFunction(); + REQ_FILE_VIEW(view); + + i32 pos = view->cursor.pos; + view->cursor = view_compute_cursor_from_pos(view, view->mark); + view->mark = pos; +} + +internal void +fulfill_interaction(Command_Data *command, char *data, bool32 full_set){ + Panel *panel = command->panel; + View *view = panel->view; + Interactive_View *int_view = view_to_interactive_view(view); + if (int_view){ + String *dest = 0; + switch (int_view->interaction){ + case INTV_SYS_FILE_LIST: + dest = &int_view->hot_directory->string; + break; + case INTV_LIVE_FILE_LIST: + dest = &int_view->dest; + break; + } + if (full_set) dest->size = 0; + append(dest, data); + } + + interactive_view_complete(int_view); +} + +COMMAND_DECL(user_callback){ + ProfileMomentFunction(); + if (binding.custom) binding.custom(command, app_links); +} + +globalvar Command_Function command_table[cmdid_count]; + +extern "C"{ + EXECUTE_COMMAND_SIG(execute_command_from_id){ + Command_Data *cmd = (Command_Data*)cmd_context; + Command_Function function = command_table[command_id]; + Command_Binding binding; + binding.function = function; + if (function) function(cmd, binding); + + App_Vars *vars = cmd->vars; + Command_Data command_data; + command_data.vars = vars; + command_data.mem = &vars->mem; + command_data.working_set = &vars->working_set; + command_data.layout = &vars->layout; + command_data.panel = command_data.layout->panels + command_data.layout->active_panel; + command_data.view = command_data.panel->view; + command_data.live_set = &vars->live_set; + command_data.style = &vars->style; + command_data.delay = &vars->delay; + command_data.screen_width = cmd->screen_width; + command_data.screen_height = cmd->screen_height; + command_data.key = cmd->key; + + *cmd = command_data; + } + + FULFILL_INTERACTION_SIG(fulfill_interaction_external){ + Command_Data *cmd = (Command_Data*)cmd_context; + fulfill_interaction(cmd, data, full_set); + } +} + +inline void +app_links_init(){ + app_links.exec_command = execute_command_from_id; + app_links.fulfill_interaction = fulfill_interaction_external; +} + +#if FRED_INTERNAL +internal void +setup_debug_commands(Command_Map *commands, Key_Codes *codes){ + map_init(commands); + + map_add(commands, 'm', MDFR_NONE, command_debug_memory); + map_add(commands, 'o', MDFR_NONE, command_debug_os_events); + map_add(commands, 'p', MDFR_NONE, command_debug_profile); +} +#endif + +internal void +setup_ui_commands(Command_Map *commands, Key_Codes *codes){ + map_init(commands); + + commands->vanilla_keyboard_default.function = command_null; + + map_add(commands, codes->left, MDFR_NONE, command_null); + map_add(commands, codes->right, MDFR_NONE, command_null); + map_add(commands, codes->up, MDFR_NONE, command_null); + map_add(commands, codes->down, MDFR_NONE, command_null); + map_add(commands, codes->back, MDFR_NONE, command_null); + map_add(commands, codes->esc, MDFR_NONE, command_close_minor_view); +} + +internal void +setup_file_commands(Command_Map *commands, Key_Codes *codes){ + map_init(commands); + + commands->vanilla_keyboard_default.function = command_write_character; + + map_add(commands, codes->left, MDFR_NONE, command_move_left); + map_add(commands, codes->right, MDFR_NONE, command_move_right); + map_add(commands, codes->del, MDFR_NONE, command_delete); + map_add(commands, codes->back, MDFR_NONE, command_backspace); + map_add(commands, codes->up, MDFR_NONE, command_move_up); + map_add(commands, codes->down, MDFR_NONE, command_move_down); + map_add(commands, codes->end, MDFR_NONE, command_seek_end_of_line); + map_add(commands, codes->home, MDFR_NONE, command_seek_beginning_of_line); + map_add(commands, codes->page_up, MDFR_NONE, command_page_up); + map_add(commands, codes->page_down, MDFR_NONE, command_page_down); + + map_add(commands, codes->right, MDFR_CTRL, command_seek_white_or_token_right); + map_add(commands, codes->left, MDFR_CTRL, command_seek_white_or_token_left); + map_add(commands, codes->up, MDFR_CTRL, command_seek_whitespace_up); + map_add(commands, codes->down, MDFR_CTRL, command_seek_whitespace_down); + + map_add(commands, ' ', MDFR_CTRL, command_set_mark); + map_add(commands, 'm', MDFR_CTRL, command_cursor_mark_swap); + map_add(commands, 'c', MDFR_CTRL, command_copy); + map_add(commands, 'x', MDFR_CTRL, command_cut); + map_add(commands, 'v', MDFR_CTRL, command_paste); + map_add(commands, 'V', MDFR_CTRL, command_paste_next); + map_add(commands, 'd', MDFR_CTRL, command_delete_chunk); + map_add(commands, 'l', MDFR_CTRL, command_toggle_line_wrap); + map_add(commands, 'L', MDFR_CTRL, command_toggle_endline_mode); + map_add(commands, '?', MDFR_CTRL, command_toggle_show_whitespace); + map_add(commands, '|', MDFR_CTRL, command_toggle_tokens); + map_add(commands, 'u', MDFR_CTRL, command_to_uppercase); + map_add(commands, 'j', MDFR_CTRL, command_to_lowercase); + map_add(commands, '`', MDFR_CTRL, command_clean_line); + map_add(commands, '~', MDFR_CTRL, command_clean_all_lines); + map_add(commands, '1', MDFR_CTRL, command_eol_dosify); + map_add(commands, '!', MDFR_CTRL, command_eol_nixify); + map_add(commands, 'f', MDFR_CTRL, command_search); + map_add(commands, 'r', MDFR_CTRL, command_rsearch); + map_add(commands, 'g', MDFR_CTRL, command_goto_line); + + map_add(commands, '\t', MDFR_CTRL, command_auto_tab); + + map_add(commands, 'K', MDFR_CTRL, command_kill_file); + map_add(commands, 'O', MDFR_CTRL, command_reopen); + map_add(commands, 's', MDFR_CTRL, command_save); + map_add(commands, 'w', MDFR_CTRL, command_interactive_save_as); +} + +internal void +setup_top_commands(Command_Map *commands, Key_Codes *codes){ + map_init(commands); + +#if FRED_INTERNAL + map_add(commands, 'd', MDFR_ALT, command_open_debug_view); + map_add(commands, 'p', MDFR_CTRL | MDFR_ALT, command_pause_unpause_profile); +#endif + + map_add(commands, 'p', MDFR_CTRL, command_open_panel_vsplit); + map_add(commands, '-', MDFR_CTRL, command_open_panel_hsplit); + map_add(commands, 'P', MDFR_CTRL, command_close_panel); + map_add(commands, 'n', MDFR_CTRL, command_interactive_new); + map_add(commands, 'o', MDFR_CTRL, command_interactive_open); + map_add(commands, ',', MDFR_CTRL, command_change_active_panel); + map_add(commands, 'k', MDFR_CTRL, command_interactive_kill_file); + map_add(commands, 'i', MDFR_CTRL, command_interactive_switch_file); + map_add(commands, 'c', MDFR_ALT, command_open_color_tweaker); + map_add(commands, 'x', MDFR_ALT, command_open_menu); +} + +#if 0 // TODO(allen): Here's an idea +internal void +setup_command_table(){ + BEGIN_META_CODE{ + int count; + char **command_names = get_all_commands(&count); + for (int i = 0; i < count; ++i){ + char *name_ = command_names[i]; + String name = make_string_slowly(name_); + + outcode("command_table[cmdid_", out_str(name), "] = command_", out_str(name), "\n"); + } + }END_META_CODE +} +#endif + +internal void +setup_command_table(){ +#define SET(n) command_table[cmdid_##n] = command_##n + + SET(null); + SET(write_character); + SET(seek_whitespace_right); + SET(seek_whitespace_left); + SET(seek_whitespace_up); + SET(seek_whitespace_down); + SET(seek_token_left); + SET(seek_token_right); + SET(seek_white_or_token_right); + SET(seek_white_or_token_left); + SET(seek_alphanumeric_right); + SET(seek_alphanumeric_left); + SET(seek_alphanumeric_or_camel_right); + SET(seek_alphanumeric_or_camel_left); + SET(search); + SET(rsearch); + SET(goto_line); + SET(set_mark); + SET(copy); + SET(cut); + SET(paste); + SET(paste_next); + SET(delete_chunk); + SET(interactive_new); + SET(interactive_open); + SET(reopen); + SET(save); + SET(interactive_save_as); + SET(change_active_panel); + SET(interactive_switch_file); + SET(interactive_kill_file); + SET(kill_file); + SET(toggle_line_wrap); + SET(toggle_endline_mode); + SET(to_uppercase); + SET(to_lowercase); + SET(toggle_show_whitespace); + SET(clean_line); + SET(clean_all_lines); + SET(eol_dosify); + SET(eol_nixify); + SET(auto_tab); + SET(open_panel_vsplit); + SET(open_panel_hsplit); + SET(close_panel); + SET(move_left); + SET(move_right); + SET(delete); + SET(backspace); + SET(move_up); + SET(move_down); + SET(seek_end_of_line); + SET(seek_beginning_of_line); + SET(page_up); + SET(page_down); + SET(open_color_tweaker); + SET(close_minor_view); + SET(cursor_mark_swap); + SET(open_menu); + +#undef SET +} + +/* + * Interactive Bar + */ + +internal void +hot_directory_draw_helper(Render_Target *target, + Hot_Directory *hot_directory, + Interactive_Bar *bar, String *string, + bool32 include_files){ + persist u8 str_open_bracket[] = " {"; + persist u8 str_close_bracket[] = "}"; + persist u8 str_comma[] = ", "; + + intbar_draw_string(target, bar, *string, bar->style.pop1_color); + intbar_draw_string(target, bar, str_open_bracket, bar->style.base_color); + + char front_name_[256]; + String front_name = make_fixed_width_string(front_name_); + get_front_of_directory(&front_name, *string); + + bool32 is_first_string = 1; + File_List *files = &hot_directory->file_list; + + Absolutes absolutes; + get_absolutes(front_name, &absolutes, 1, 1); + + File_Info *info, *end; + end = files->infos + files->count; + for (info = files->infos; info != end; ++info){ + String filename = info->filename; + + if (filename_match(front_name, &absolutes, filename)){ + if (is_first_string){ + is_first_string = 0; + } + else{ + intbar_draw_string(target, bar, str_comma, bar->style.base_color); + } + if (info->folder){ + intbar_draw_string(target, bar, filename, bar->style.pop1_color); + intbar_draw_string(target, bar, (u8*)"/", bar->style.pop1_color); + } + else{ + intbar_draw_string(target, bar, filename, bar->style.base_color); + } + } + } + + intbar_draw_string(target, bar, str_close_bracket, bar->style.base_color); +} + +internal void +live_file_draw_helper(Render_Target *target, Working_Set *working_set, + Interactive_Bar *bar, String *string){ + persist u8 str_open_bracket[] = " {"; + persist u8 str_close_bracket[] = "}"; + persist u8 str_comma[] = ", "; + + intbar_draw_string(target, bar, *string, bar->style.base_color); + + intbar_draw_string(target, bar, str_open_bracket, bar->style.base_color); + + bool32 is_first_string = 1; + for (i32 file_i = 0; + file_i < working_set->file_index_count; + ++file_i){ + Editing_File *file = &working_set->files[file_i]; + if (file->live_name.str && + (string->size == 0 || has_substr_unsensitive(file->live_name, *string))){ + if (is_first_string){ + is_first_string = 0; + } + else{ + intbar_draw_string(target, bar, str_comma, bar->style.base_color); + } + intbar_draw_string(target, bar, file->live_name, bar->style.base_color); + } + } + intbar_draw_string(target, bar, str_close_bracket, bar->style.base_color); +} + +// App Functions + +internal void +app_hardcode_styles(App_Vars *vars){ + Interactive_Style file_info_style; + Style *styles, *style; + styles = vars->styles.styles; + style = styles; + + Font *fonts = vars->fonts.fonts; + + ///////////////// + style_set_name(style, make_lit_string("4coder")); + style->font = fonts + 1; + + style->main.back_color = 0xFF0C0C0C; + style->main.margin_color = 0xFF181818; + style->main.margin_hover_color = 0xFF252525; + style->main.margin_active_color = 0xFF323232; + style->main.cursor_color = 0xFF00EE00; + style->main.highlight_color = 0xFFDDEE00; + style->main.mark_color = 0xFF494949; + style->main.default_color = 0xFF90B080; + style->main.at_cursor_color = style->main.back_color; + style->main.at_highlight_color = 0xFFFF44DD; + style->main.comment_color = 0xFF2090F0; + style->main.keyword_color = 0xFFD08F20; + style->main.str_constant_color = 0xFF50FF30; + style->main.char_constant_color = style->main.str_constant_color; + style->main.int_constant_color = style->main.str_constant_color; + style->main.float_constant_color = style->main.str_constant_color; + style->main.bool_constant_color = style->main.str_constant_color; + style->main.include_color = style->main.str_constant_color; + style->main.preproc_color = style->main.default_color; + style->main.special_character_color = 0xFFFF0000; + + style->main.paste_color = 0xFFDDEE00; + + style->main.highlight_junk_color = 0xff3a0000; + style->main.highlight_white_color = 0xff003a3a; + + file_info_style.bar_color = 0xFF888888; + file_info_style.bar_active_color = 0xFF888888; + file_info_style.base_color = 0xFF000000; + file_info_style.pop1_color = 0xFF4444AA; + file_info_style.pop2_color = 0xFFFF0000; + style->main.file_info_style = file_info_style; + style->font_changed = 1; + ++style; + + ///////////////// + *style = *(style-1); + style_set_name(style, make_lit_string("4coder-mono")); + style->font = fonts; + ++style; + + ///////////////// + style_set_name(style, make_lit_string("Handmade Hero")); + style->font = fonts; + + style->main.back_color = 0xFF161616; + style->main.margin_color = 0xFF262626; + style->main.margin_hover_color = 0xFF333333; + style->main.margin_active_color = 0xFF404040; + style->main.cursor_color = 0xFF40FF40; + style->main.at_cursor_color = style->main.back_color; + style->main.mark_color = 0xFF808080; + style->main.highlight_color = 0xFF191970; + style->main.at_highlight_color = 0xFFCDAA7D; + style->main.default_color = 0xFFCDAA7D; + style->main.comment_color = 0xFF7F7F7F; + style->main.keyword_color = 0xFFCD950C; + style->main.str_constant_color = 0xFF6B8E23; + style->main.char_constant_color = style->main.str_constant_color; + style->main.int_constant_color = style->main.str_constant_color; + style->main.float_constant_color = style->main.str_constant_color; + style->main.bool_constant_color = style->main.str_constant_color; + style->main.include_color = style->main.str_constant_color; + style->main.preproc_color = style->main.default_color; + style->main.special_character_color = 0xFFFF0000; + + style->main.paste_color = 0xFFFFBB00; + + style->main.highlight_junk_color = 0xFF3A0000; + style->main.highlight_white_color = 0xFF003A3A; + + file_info_style.bar_color = 0xFFCACACA; + file_info_style.bar_active_color = 0xFFCACACA; + file_info_style.base_color = 0xFF000000; + file_info_style.pop1_color = 0xFF1504CF; + file_info_style.pop2_color = 0xFFFF0000; + style->main.file_info_style = file_info_style; + style->font_changed = 1; + ++style; + + ///////////////// + style_set_name(style, make_lit_string("Twilight")); + style->font = fonts + 2; + + style->main.back_color = 0xFF090D12; + style->main.margin_color = 0xFF1A2634; + style->main.margin_hover_color = 0xFF2D415B; + style->main.margin_active_color = 0xFF405D82; + style->main.cursor_color = 0xFFEEE800; + style->main.at_cursor_color = style->main.back_color; + style->main.mark_color = 0xFF8BA8CC; + style->main.highlight_color = 0xFF037A7B; + style->main.at_highlight_color = 0xFFFEB56C; + style->main.default_color = 0xFFB7C19E; + style->main.comment_color = 0xFF20ECF0; + style->main.keyword_color = 0xFFD86909; + style->main.str_constant_color = 0xFFC4EA5D; + style->main.char_constant_color = style->main.str_constant_color; + style->main.int_constant_color = style->main.str_constant_color; + style->main.float_constant_color = style->main.str_constant_color; + style->main.bool_constant_color = style->main.str_constant_color; + style->main.include_color = style->main.str_constant_color; + style->main.preproc_color = style->main.default_color; + style->main.special_character_color = 0xFFFF0000; + + style->main.paste_color = 0xFFDDEE00; + + style->main.highlight_junk_color = 0xff3a0000; + style->main.highlight_white_color = 0xFF151F2A; + + file_info_style.bar_color = 0xFF315E68; + file_info_style.bar_active_color = 0xFF315E68; + file_info_style.base_color = 0xFF000000; + file_info_style.pop1_color = 0xFF1BFF0C; + file_info_style.pop2_color = 0xFFFF200D; + style->main.file_info_style = file_info_style; + style->font_changed = 1; + ++style; + + ///////////////// + style_set_name(style, make_lit_string("Wolverine")); + style->font = fonts + 1; + + style->main.back_color = 0xFF070711; + style->main.margin_color = 0xFF111168; + style->main.margin_hover_color = 0xFF191996; + style->main.margin_active_color = 0xFF2121C3; + style->main.cursor_color = 0xFF7082F9; + style->main.at_cursor_color = 0xFF000014; + style->main.mark_color = 0xFF4b5028; + style->main.highlight_color = 0xFFDDEE00; + style->main.at_highlight_color = 0xFF000019; + style->main.default_color = 0xFF8C9740; + style->main.comment_color = 0xFF3A8B29; + style->main.keyword_color = 0xFFD6B109; + style->main.str_constant_color = 0xFFAF5FA7; + style->main.char_constant_color = style->main.str_constant_color; + style->main.int_constant_color = style->main.str_constant_color; + style->main.float_constant_color = style->main.str_constant_color; + style->main.bool_constant_color = style->main.str_constant_color; + style->main.include_color = style->main.str_constant_color; + style->main.preproc_color = style->main.default_color; + style->main.special_character_color = 0xFFFF0000; + + style->main.paste_color = 0xFF900090; + + style->main.highlight_junk_color = 0xff3a0000; + style->main.highlight_white_color = 0xff003a3a; + + file_info_style.bar_color = 0xFF7082F9; + file_info_style.bar_active_color = 0xFF7082F9; + file_info_style.base_color = 0xFF000000; + file_info_style.pop1_color = 0xFFFAFA15; + file_info_style.pop2_color = 0xFFD20000; + style->main.file_info_style = file_info_style; + style->font_changed = 1; + ++style; + + ///////////////// + style_set_name(style, make_lit_string("stb")); + style->font = fonts + 3; + + style->main.back_color = 0xFFD6D6D6; + style->main.margin_color = 0xFF9E9E9E; + style->main.margin_hover_color = 0xFF7E7E7E; + style->main.margin_active_color = 0xFF5C5C5C; + style->main.cursor_color = 0xFF000000; + style->main.at_cursor_color = 0xFFD6D6D6; + style->main.mark_color = 0xFF525252; + style->main.highlight_color = 0xFF0044FF; + style->main.at_highlight_color = 0xFFD6D6D6; + style->main.default_color = 0xFF000000; + style->main.comment_color = 0xFF000000; + style->main.keyword_color = 0xFF000000; + style->main.str_constant_color = 0xFF000000; + style->main.char_constant_color = style->main.str_constant_color; + style->main.int_constant_color = style->main.str_constant_color; + style->main.float_constant_color = style->main.str_constant_color; + style->main.bool_constant_color = style->main.str_constant_color; + style->main.include_color = style->main.str_constant_color; + style->main.preproc_color = style->main.default_color; + style->main.special_character_color = 0xFF9A0000; + + style->main.paste_color = 0xFF00B8B8; + + style->main.highlight_junk_color = 0xFFFF7878; + style->main.highlight_white_color = 0xFFBCBCBC; + + file_info_style.bar_color = 0xFF606060; + file_info_style.bar_active_color = 0xFF888888; + file_info_style.base_color = 0xFF000000; + file_info_style.pop1_color = 0xFF1111DC; + file_info_style.pop2_color = 0xFFE80505; + style->main.file_info_style = file_info_style; + style->font_changed = 1; + ++style; + + vars->styles.count = (i32)(style - styles); + vars->styles.max = ArrayCount(vars->styles.styles); + style_copy(&vars->style, vars->styles.styles); + vars->style.font_changed = 0; +} + +internal bool32 +app_load_font(Font *font, char *filename, i32 size, void *memory, + i32 *used, i32 tab_width, String name){ + if (font_load(font, filename, size, memory, font_predict_size(size), used, tab_width)){ + font->loaded = 1; + font->name_[ArrayCount(font->name_)-1] = 0; + font->name = make_string(font->name_, 0, ArrayCount(font->name_)-1); + copy(&font->name, name); + } + return font->loaded; +} + +internal bool32 +app_init(Thread_Context *thread, Application_Memory *memory, + Key_Codes *loose_codes, Clipboard_Contents clipboard){ + app_links_init(); + + Partition _partition = partition_open(memory->vars_memory, memory->vars_memory_size); + App_Vars *vars = push_struct(&_partition, App_Vars); + Assert(vars); + *vars = {}; + vars->mem.part = _partition; + Partition *partition = &vars->mem.part; + general_memory_open(&vars->mem.general, memory->target_memory, memory->target_memory_size); + + i32 panel_max_count = vars->layout.panel_max_count = 16; + i32 panel_count = vars->layout.panel_count = 1; + i32 divider_max_count = panel_max_count - 1; + + Panel *panels = vars->layout.panels = + push_array(partition, Panel, panel_max_count); + + Panel_Divider *dividers = vars->layout.dividers = + push_array(partition, Panel_Divider, divider_max_count); + + Panel_Divider *divider = dividers; + for (i32 i = 0; i < divider_max_count-1; ++i, ++divider){ + divider->next_free = (divider + 1); + } + divider->next_free = 0; + vars->layout.free_divider = dividers; + + vars->live_set.count = 0; + vars->live_set.max = 1 + 2*panel_max_count; + i32 view_sizes[] = { + sizeof(File_View), + sizeof(Color_View), + sizeof(Interactive_View), +#if FRED_INTERNAL + sizeof(Debug_View) +#endif + }; + i32 view_chunk_size = 0; + for (i32 i = 0; i < ArrayCount(view_sizes); ++i){ + view_chunk_size = Max(view_chunk_size, view_sizes[i]); + } + vars->live_set.stride = view_chunk_size; + vars->live_set.views = (File_View*) + push_block(partition, view_chunk_size*vars->live_set.max); + + char *views_ = (char*)vars->live_set.views; + for (i32 i = panel_max_count-2; i >= 0; --i){ + View *view = (View*)(views_ + i*view_chunk_size); + View *view_next = (View*)((char*)view + view_chunk_size); + view->next_free = view_next; + } + { + View *view = (View*)(views_ + (panel_max_count-1)*view_chunk_size); + view->next_free = 0; + } + vars->live_set.free_view = (View*)views_; + + setup_command_table(); + + if (TEMP.get_bindings){ + i32 size = partition_remaining(partition); + void *data = partition_current(partition); + + i32 wanted_size = TEMP.get_bindings(data, size, loose_codes); + + bool32 did_top = 0; + bool32 did_file = 0; + + if (wanted_size <= size){ + Binding_Unit *unit = (Binding_Unit*)data; + if (unit->type == UNIT_HEADER && unit->header.error == 0){ + Binding_Unit *end = unit + unit->header.total_size; + + Command_Map *mapptr = 0; + int mapid = -1; + for (++unit; unit < end; ++unit){ + switch (unit->type){ + case UNIT_MAP_BEGIN: + { + mapid = unit->map_begin.mapid; + if (mapid == MAPID_GLOBAL){ + mapptr = &vars->map_top; + map_init(mapptr); + did_top = 1; + } + else if (mapid == MAPID_FILE){ + mapptr = &vars->map_file; + map_init(mapptr); + did_file = 1; + } + else mapptr = 0; + }break; + + case UNIT_BINDING: + if (mapptr){ + Command_Function func = 0; + if (unit->binding.command_id >= 0 && unit->binding.command_id < cmdid_count) + func = command_table [unit->binding.command_id]; + if (func){ + if (unit->binding.code == 0 && unit->binding.modifiers == 0){ + mapptr->vanilla_keyboard_default.function = func; + } + else{ + map_add(mapptr, unit->binding.code, unit->binding.modifiers, func); + } + } + } + break; + + case UNIT_CALLBACK: + if (mapptr){ + Command_Function func = command_user_callback; + Custom_Command_Function *custom = unit->callback.func; + if (func){ + if (unit->callback.code == 0 && unit->callback.modifiers == 0){ + mapptr->vanilla_keyboard_default.function = func; + mapptr->vanilla_keyboard_default.custom = custom; + } + else{ + map_add(mapptr, unit->callback.code, unit->callback.modifiers, func, custom); + } + } + } + break; + } + } + } + } + + if (!did_top) setup_top_commands(&vars->map_top, loose_codes); + if (!did_file) setup_file_commands(&vars->map_file, loose_codes); + } + else{ + setup_top_commands(&vars->map_top, loose_codes); + setup_file_commands(&vars->map_file, loose_codes); + } + + setup_ui_commands(&vars->map_ui, loose_codes); +#if FRED_INTERNAL + setup_debug_commands(&vars->map_debug, loose_codes); +#endif + + if (!font_init()) return 0; + + vars->fonts.max = 6; + vars->fonts.fonts = push_array(partition, Font, vars->fonts.max); + + { + i32 font_count = 0; + i32 memory_used; + + memory_used = 0; + app_load_font(vars->fonts.fonts + font_count++, "liberation-mono.ttf", 17, + partition_current(partition), + &memory_used, 4, make_lit_string("liberation mono")); + push_block(partition, memory_used); + + memory_used = 0; + app_load_font(vars->fonts.fonts + font_count++, "LiberationSans-Regular.ttf", 17, + partition_current(partition), + &memory_used, 4, make_lit_string("liberation sans")); + push_block(partition, memory_used); + + memory_used = 0; + app_load_font(vars->fonts.fonts + font_count++, "Hack-Regular.ttf", 17, + partition_current(partition), + &memory_used, 4, make_lit_string("hack")); + push_block(partition, memory_used); + + memory_used = 0; + app_load_font(vars->fonts.fonts + font_count++, "CutiveMono-Regular.ttf", 17, + partition_current(partition), + &memory_used, 4, make_lit_string("cutive mono")); + push_block(partition, memory_used); + + memory_used = 0; + app_load_font(vars->fonts.fonts + font_count++, "Inconsolata-Regular.ttf", 17, + partition_current(partition), + &memory_used, 4, make_lit_string("inconsolata")); + push_block(partition, memory_used); + + if (TEMP.set_extra_font){ + Extra_Font extra; + extra.size = 17; + TEMP.set_extra_font(&extra); + memory_used = 0; + if (app_load_font(vars->fonts.fonts + font_count, extra.file_name, extra.size, + partition_current(partition), + &memory_used, 4, make_string_slowly(extra.font_name))){ + ++font_count; + } + else{ + vars->fonts.fonts[font_count] = {}; + } + push_block(partition, memory_used); + } + + vars->fonts.count = font_count; + } + + // NOTE(allen): file setup + vars->working_set.file_index_count = 1; + vars->working_set.file_max_count = 29; + vars->working_set.files = + push_array(partition, Editing_File, vars->working_set.file_max_count); + + buffer_get_dummy(&vars->working_set.files[0]); + + vars->working_set.table.max = vars->working_set.file_max_count * 3 / 2; + vars->working_set.table.count = 0; + vars->working_set.table.table = + push_array(partition, File_Table_Entry, vars->working_set.table.max); + memset(vars->working_set.table.table, 0, sizeof(File_Table_Entry) * vars->working_set.table.max); + + // NOTE(allen): clipboard setup + vars->working_set.clipboard_max_size = ArrayCount(vars->working_set.clipboards); + vars->working_set.clipboard_size = 0; + vars->working_set.clipboard_current = 0; + vars->working_set.clipboard_rolling = 0; + + // TODO(allen): more robust allocation solution for the clipboard + if (clipboard.str){ + String *dest = working_set_next_clipboard_string(&vars->mem.general, &vars->working_set, clipboard.size); + copy(dest, make_string((char*)clipboard.str, clipboard.size)); + } + + // NOTE(allen): delay setup + vars->delay.max = ArrayCount(vars->delay.acts); + + // NOTE(allen): style setup + app_hardcode_styles(vars); + + vars->palette_size = 40; + vars->palette = push_array(partition, u32, vars->palette_size); + + AllowLocal(panel_count); + panel_init(&panels[0]); + + vars->hot_dir_base = make_fixed_width_string(vars->hot_dir_base_); + hot_directory_init(&vars->hot_directory, vars->hot_dir_base); + + vars->mini_str = make_string((char*)vars->mini_buffer, 0, 512); + + return 1; +} + +internal Application_Step_Result +app_step(Thread_Context *thread, Key_Codes *codes, + Key_Input_Data *input, Mouse_State *mouse, + bool32 time_step, Render_Target *target, + Application_Memory *memory, + Clipboard_Contents clipboard, + bool32 first_step, bool32 force_redraw){ + + ProfileStart(OS_syncing); + Application_Step_Result app_result = {}; + app_result.redraw = force_redraw; + + App_Vars *vars = (App_Vars*)memory->vars_memory; + + if (first_step || !time_step){ + app_result.redraw = 1; + } + + Panel *panels = vars->layout.panels; + Panel *active_panel = &panels[vars->layout.active_panel]; + + // NOTE(allen): OS clipboard event handling + if (clipboard.str){ + String *dest = working_set_next_clipboard_string(&vars->mem.general, &vars->working_set, clipboard.size); + copy(dest, make_string((char*)clipboard.str, clipboard.size)); + } + + // NOTE(allen): check files are up to date + for (i32 i = 0; i < vars->working_set.file_index_count; ++i){ + Editing_File *file = vars->working_set.files + i; + + if (!file->is_dummy){ + Time_Stamp time_stamp; + time_stamp = system_file_time_stamp((u8*)make_c_str(file->source_path)); + + if (time_stamp.success){ + file->last_sys_write_time = time_stamp.time; + if (file->last_sys_write_time != file->last_4ed_write_time){ + app_result.redraw = 1; + } + } + } + } + + // NOTE(allen): reorganizing panels on screen + i32 prev_width = vars->layout.full_width; + i32 prev_height = vars->layout.full_height; + i32 prev_y_off = 0; + i32 prev_x_off = 0; + + i32 y_off = 0; + i32 x_off = 0; + i32 full_width = vars->layout.full_width = target->width; + i32 full_height = vars->layout.full_height = target->height; + + if (prev_width != full_width || prev_height != full_height || + prev_x_off != x_off || prev_y_off != y_off){ + layout_refit(&vars->layout, prev_x_off, prev_y_off, prev_width, prev_height); + int view_count = vars->layout.panel_max_count; + for (i32 view_i = 0; view_i < view_count; ++view_i){ + View *view_ = live_set_get_view(&vars->live_set, view_i); + if (!view_->is_active) continue; + File_View *view = view_to_file_view(view_); + if (!view) continue; + view_measure_wraps(&vars->mem.general, view); + view->cursor = view_compute_cursor_from_pos(view, view->cursor.pos); + } + app_result.redraw = 1; + } + + // NOTE(allen): collect input information + Key_Summary key_data = {}; + for (i32 i = 0; i < input->press_count; ++i){ + key_data.keys[key_data.count++] = input->press[i]; + } + + for (i32 i = 0; i < input->hold_count; ++i){ + key_data.keys[key_data.count++] = input->hold[i]; + } + + for (i32 i = 0; i < CONTROL_KEY_COUNT; ++i){ + key_data.modifiers[i] = input->control_keys[i]; + } + + Mouse_Summary mouse_data; + mouse_data.mx = mouse->x; + mouse_data.my = mouse->y; + + mouse_data.l = mouse->left_button; + mouse_data.r = mouse->right_button; + mouse_data.press_l = mouse_data.l && !mouse->left_button_prev; + mouse_data.press_r = mouse_data.r && !mouse->right_button_prev; + mouse_data.release_l = !mouse_data.l && mouse->left_button_prev; + mouse_data.release_r = !mouse_data.r && mouse->right_button_prev; + + mouse_data.out_of_window = mouse->out_of_window; + mouse_data.wheel_used = (mouse->wheel != 0); + mouse_data.wheel_amount = -mouse->wheel; + ProfileEnd(OS_syncing); + + ProfileStart(hover_status); + // NOTE(allen): detect mouse hover status + i32 mx = mouse_data.mx; + i32 my = mouse_data.my; + bool32 mouse_in_edit_area = 0; + bool32 mouse_in_margin_area = 0; + Panel *mouse_panel = 0; + i32 mouse_panel_i = 0; + + { + Panel *panel = 0; + bool32 in_edit_area = 0; + bool32 in_margin_area = 0; + i32 panel_count = vars->layout.panel_count; + i32 panel_i; + + for (panel_i = 0; panel_i < panel_count; ++panel_i){ + panel = panels + panel_i; + if (hit_check(mx, my, panel->inner)){ + in_edit_area = 1; + break; + } + else if (hit_check(mx, my, panel->full)){ + in_margin_area = 1; + break; + } + } + + mouse_in_edit_area = in_edit_area; + mouse_in_margin_area = in_margin_area; + if (in_edit_area || in_margin_area){ + mouse_panel = panel; + mouse_panel_i = panel_i; + } + } + + bool32 mouse_on_divider = 0; + bool32 mouse_divider_vertical = 0; + i32 mouse_divider_id = 0; + i32 mouse_divider_side = 0; + + if (mouse_in_margin_area){ + bool32 resize_area = 0; + i32 divider_id = 0; + bool32 seeking_v_divider = 0; + i32 seeking_child_on_side = 0; + Panel *panel = mouse_panel; + if (mx >= panel->inner.x0 && mx < panel->inner.x1){ + seeking_v_divider = 0; + if (my > panel->inner.y0){ + seeking_child_on_side = -1; + } + else{ + seeking_child_on_side = 1; + } + } + else{ + seeking_v_divider = 1; + if (mx > panel->inner.x0){ + seeking_child_on_side = -1; + } + else{ + seeking_child_on_side = 1; + } + } + mouse_divider_vertical = seeking_v_divider; + mouse_divider_side = seeking_child_on_side; + + if (vars->layout.panel_count > 1){ + i32 which_child; + divider_id = panel->parent; + which_child = panel->which_child; + for (;;){ + Divider_And_ID div = layout_get_divider(&vars->layout, divider_id); + + if (which_child == seeking_child_on_side && + div.divider->v_divider == seeking_v_divider){ + resize_area = 1; + break; + } + + if (divider_id == vars->layout.root){ + break; + } + else{ + divider_id = div.divider->parent; + which_child = div.divider->which_child; + } + } + + mouse_on_divider = resize_area; + mouse_divider_id = divider_id; + } + } + ProfileEnd(hover_status); + + ProfileStart(resizing); + // NOTE(allen): panel resizing + switch (vars->state){ + case APP_STATE_EDIT: + { + if (mouse_data.press_l && mouse_on_divider){ + vars->state = APP_STATE_RESIZING; + Divider_And_ID div = layout_get_divider(&vars->layout, mouse_divider_id); + vars->resizing.divider = div.divider; + + i32 min, max; + { + i32 mid, MIN, MAX; + mid = div.divider->pos; + if (mouse_divider_vertical){ + MIN = 0; + MAX = MIN + vars->layout.full_width; + } + else{ + MIN = 0; + MAX = MIN + vars->layout.full_height; + } + min = MIN; + max = MAX; + + i32 divider_id = div.id; + do{ + Divider_And_ID other_div = layout_get_divider(&vars->layout, divider_id); + bool32 divider_match = (other_div.divider->v_divider == mouse_divider_vertical); + i32 pos = other_div.divider->pos; + if (divider_match && pos > mid && pos < max){ + max = pos; + } + else if (divider_match && pos < mid && pos > min){ + min = pos; + } + divider_id = other_div.divider->parent; + }while(divider_id != -1); + + Temp_Memory temp = begin_temp_memory(&vars->mem.part); + i32 *divider_stack = push_array(&vars->mem.part, i32, vars->layout.panel_count); + i32 top = 0; + divider_stack[top++] = div.id; + + while (top > 0){ + Divider_And_ID other_div = layout_get_divider(&vars->layout, divider_stack[--top]); + bool32 divider_match = (other_div.divider->v_divider == mouse_divider_vertical); + i32 pos = other_div.divider->pos; + if (divider_match && pos > mid && pos < max){ + max = pos; + } + else if (divider_match && pos < mid && pos > min){ + min = pos; + } + if (other_div.divider->child1 != -1){ + divider_stack[top++] = other_div.divider->child1; + } + if (other_div.divider->child2 != -1){ + divider_stack[top++] = other_div.divider->child2; + } + } + + end_temp_memory(temp); + } + + vars->resizing.min = min; + vars->resizing.max = max; + } + }break; + + case APP_STATE_RESIZING: + { + app_result.redraw = 1; + if (mouse_data.l){ + Panel_Divider *divider = vars->resizing.divider; + if (divider->v_divider){ + divider->pos = mx; + } + else{ + divider->pos = my; + } + + if (divider->pos < vars->resizing.min){ + divider->pos = vars->resizing.min; + } + else if (divider->pos > vars->resizing.max){ + divider->pos = vars->resizing.max - 1; + } + + layout_fix_all_panels(&vars->layout); + } + else{ + vars->state = APP_STATE_EDIT; + } + }break; + } + ProfileEnd(resizing); + + ProfileStart(command); + // NOTE(allen): update active panel + if (mouse_in_edit_area && mouse_panel != 0 && mouse_data.press_l){ + active_panel = mouse_panel; + vars->layout.active_panel = mouse_panel_i; + app_result.redraw = 1; + } + + Command_Data command_data; + command_data.mem = &vars->mem; + command_data.panel = active_panel; + command_data.view = active_panel->view; + command_data.working_set = &vars->working_set; + command_data.layout = &vars->layout; + command_data.live_set = &vars->live_set; + command_data.style = &vars->style; + command_data.delay = &vars->delay; + command_data.vars = vars; + command_data.screen_width = target->width; + command_data.screen_height = target->height; + + if (first_step && TEMP.start_hook){ + TEMP.start_hook(&command_data, app_links); + } + + // NOTE(allen): command input to active view + for (i32 key_i = 0; key_i < key_data.count; ++key_i){ + Command_Binding cmd = {}; + Command_Map *override_map = 0; + View *view = active_panel->view; + + Key_Single key = get_single_key(&key_data, key_i); + command_data.key = key; + + if (view) override_map = view->map; + if (override_map) cmd = map_extract(override_map, key); + if (cmd.function == 0) cmd = map_extract(&vars->map_top, key); + + switch (vars->state){ + case APP_STATE_EDIT: + { + Handle_Command_Function *handle_command = 0; + if (view) handle_command = view->handle_command; + if (handle_command){ + handle_command(view, &command_data, cmd, key, codes); + app_result.redraw = 1; + } + else{ + if (cmd.function){ + cmd.function(&command_data, cmd); + app_result.redraw = 1; + } + } + }break; + + case APP_STATE_RESIZING: + { + if (key_data.count > 0){ + vars->state = APP_STATE_EDIT; + } + }break; + } + } + + active_panel = panels + vars->layout.active_panel; + ProfileEnd(command); + + ProfileStart(step); + View *active_view = active_panel->view; + + Input_Summary dead_input = {}; + dead_input.mouse.mx = mouse_data.mx; + dead_input.mouse.my = mouse_data.my; + dead_input.codes = codes; + dead_input.keys.modifiers[0] = key_data.modifiers[0]; + dead_input.keys.modifiers[1] = key_data.modifiers[1]; + dead_input.keys.modifiers[2] = key_data.modifiers[2]; + + Input_Summary active_input = {}; + dead_input.mouse.mx = mouse_data.mx; + dead_input.mouse.my = mouse_data.my; + active_input.keys = key_data; + active_input.codes = codes; + + // NOTE(allen): pass raw input to the panels + { + Panel *panel = panels; + for (i32 panel_i = vars->layout.panel_count; panel_i > 0; --panel_i, ++panel){ + View *view_ = panel->view; + if (view_){ + Assert(view_->do_view); + bool32 active = (panel == active_panel); + Input_Summary input = (active)?(active_input):(dead_input); + if (panel == mouse_panel){ + input.mouse = mouse_data; + } + if (view_->do_view(thread, view_, panel->inner, active_view, + VMSG_STEP, 0, &input, &active_input)){ + app_result.redraw = 1; + } + } + } + } + ProfileEnd(step); + + ProfileStart(delayed_actions); + if (vars->delay.count > 0){ + Style *style = &vars->style; + Working_Set *working_set = &vars->working_set; + Live_Views *live_set = &vars->live_set; + General_Memory *general = &vars->mem.general; + + i32 count = vars->delay.count; + vars->delay.count = 0; + + for (i32 i = 0; i < count; ++i){ + Delayed_Action *act = vars->delay.acts + i; + String *string = &act->string; + Panel *panel = act->panel; + + switch (act->type){ + case DACT_OPEN: + { + Editing_File *target_file = 0; + bool32 created_file = 0; + + target_file = working_set_contains(working_set, *string); + if (!target_file){ + Get_File_Result file = working_set_get_available_file(working_set); + if (file.file){ + buffer_get_dummy(file.file); + created_file = buffer_create(general, file.file, (u8*)string->str, style->font); + table_add(&working_set->table, file.file->source_path, file.index); + if (created_file){ + target_file = file.file; + } + } + } + + if (target_file){ + View *new_view = live_set_alloc_view(live_set, &vars->mem.general); + + new_view->map = &vars->map_file; + view_replace_major(new_view, panel, live_set); + + File_View *file_view = file_view_init(new_view); + view_set_file(file_view, target_file, style); + if (created_file && target_file->tokens_exist) + buffer_first_lex_parallel(general, target_file); + } + }break; + + case DACT_SAVE_AS: + { + View *view = panel->view; + File_View *fview = view_to_file_view(view); + + if (!fview && view->is_minor) fview = view_to_file_view(view->major); + if (fview){ + Editing_File *file = fview->file; + if (file && !file->is_dummy){ + buffer_save_and_set_names(file, (u8*)string->str); + } + } + }break; + + case DACT_NEW: + { + Get_File_Result file = working_set_get_available_file(working_set); + buffer_create_empty(general, file.file, (u8*)string->str, style->font); + table_add(&working_set->table, file.file->source_path, file.index); + + View *new_view = live_set_alloc_view(live_set, general); + view_replace_major(new_view, panel, live_set); + new_view->map = &vars->map_file; + + File_View *file_view = file_view_init(new_view); + view_set_file(file_view, file.file, style); + if (file.file->tokens_exist) buffer_first_lex_parallel(general, file.file); + }break; + + case DACT_SWITCH: + { + Editing_File *file = working_set_lookup_file(working_set, *string); + if (file){ + View *new_view = live_set_alloc_view(live_set, general); + view_replace_major(new_view, panel, live_set); + new_view->map = &vars->map_file; + + File_View *file_view = file_view_init(new_view); + view_set_file(file_view, file, style); + } + }break; + + case DACT_KILL: + { + Editing_File *file = working_set_lookup_file(working_set, *string); + if (file){ + table_remove(&working_set->table, file->source_path); + kill_file(general, file, live_set, &vars->layout); + } + }break; + + case DACT_CLOSE_MINOR: + { + view_remove_minor(panel, live_set); + }break; + + case DACT_CLOSE_MAJOR: + { + view_remove_major(panel, live_set); + }break; + + case DACT_THEME_OPTIONS: + { + open_theme_options(vars, live_set, &vars->mem.general, panel); + }break; + } + } + } + ProfileEnd(delayed_actions); + + ProfileStart(resize); + // NOTE(allen): send resize messages to panels that have changed size + { + Panel *panel = panels; + for (i32 panel_i = vars->layout.panel_count; panel_i > 0; --panel_i, ++panel){ + i32_Rect prev = panel->prev_inner; + i32_Rect inner = panel->inner; + if (prev.x0 != inner.x0 || prev.y0 != inner.y0 || + prev.x1 != inner.x1 || prev.y1 != inner.y1){ + View *view = panel->view; + if (view){ + view->do_view(thread, view, inner, active_view, + VMSG_RESIZE, 0, &dead_input, &active_input); + view = (view->is_minor)?view->major:0; + if (view){ + view->do_view(thread, view, inner, active_view, + VMSG_RESIZE, 0, &dead_input, &active_input); + } + } + } + panel->prev_inner = inner; + } + } + ProfileEnd(resize); + + ProfileStart(style_change); + // NOTE(allen): send style change messages if the style has changed + if (vars->style.font_changed){ + vars->style.font_changed = 0; + + Editing_File *file = vars->working_set.files; + for (i32 i = vars->working_set.file_index_count; i > 0; --i, ++file){ + if (file->data && !file->is_dummy){ + buffer_measure_widths(&vars->mem.general, file, vars->style.font); + } + } + + Panel *panel = panels; + for (i32 panel_i = vars->layout.panel_count; panel_i > 0; --panel_i, ++panel){ + View *view = panel->view; + if (view){ + view->do_view(thread, view, panel->inner, active_view, + VMSG_STYLE_CHANGE, 0, &dead_input, &active_input); + view = (view->is_minor)?view->major:0; + if (view){ + view->do_view(thread, view, panel->inner, active_view, + VMSG_STYLE_CHANGE, 0, &dead_input, &active_input); + } + } + } + } + ProfileEnd(style_change); + + ProfileStart(redraw); + if (mouse_panel != vars->prev_mouse_panel) app_result.redraw = 1; + if (app_result.redraw){ + target->clip_top = -1; + draw_push_clip(target, rect_from_target(target)); + + // NOTE(allen): render the panels + Panel *panel = panels; + for (i32 panel_i = vars->layout.panel_count; panel_i > 0; --panel_i, ++panel){ + i32_Rect full = panel->full; + i32_Rect inner = panel->inner; + + View *view_ = panel->view; + Style *style = &vars->style; + + bool32 active = (panel == active_panel); + u32 back_color = style->main.back_color; + draw_rectangle(target, full, back_color); + + if (view_){ + Assert(view_->do_view); + draw_push_clip(target, panel->inner); + view_->do_view(thread, view_, panel->inner, active_view, + VMSG_DRAW, target, &dead_input, &active_input); + draw_pop_clip(target); + } + + u32 margin_color; + if (active){ + margin_color = style->main.margin_active_color; + } + else if (panel == mouse_panel){ + margin_color = style->main.margin_hover_color; + } + else{ + margin_color = style->main.margin_color; + } + draw_rectangle(target, i32R(full.x0, full.y0, full.x1, inner.y0), margin_color); + draw_rectangle(target, i32R(full.x0, inner.y1, full.x1, full.y1), margin_color); + draw_rectangle(target, i32R(full.x0, inner.y0, inner.x0, inner.y1), margin_color); + draw_rectangle(target, i32R(inner.x1, inner.y0, full.x1, inner.y1), margin_color); + } + } + ProfileEnd(redraw); + + ProfileStart(get_cursor); + // NOTE(allen): get cursor type + if (mouse_in_edit_area){ + View *view = mouse_panel->view; + if (view){ + app_result.mouse_cursor_type = view->mouse_cursor_type; + } + else{ + app_result.mouse_cursor_type = APP_MOUSE_CURSOR_ARROW; + } + } + else if (mouse_in_margin_area){ + if (mouse_on_divider){ + if (mouse_divider_vertical){ + 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; + } + } + vars->prev_mouse_panel = mouse_panel; + ProfileEnd(get_cursor); + + return app_result; +} + +// BOTTOM + diff --git a/4ed.h b/4ed.h new file mode 100644 index 00000000..8a3b4382 --- /dev/null +++ b/4ed.h @@ -0,0 +1,355 @@ +/* + * Mr. 4th Dimention - Allen Webster + * + * 12.12.2014 + * + * Win32 Layer for project codename "4ed" + * + */ + +#ifndef FRED_H +#define FRED_H + +struct Partition{ + u8 *base; + i32 pos, max; +}; + +internal Partition +partition_open(void *memory, i32 size){ + Partition partition; + partition.base = (u8*)memory;; + partition.pos = 0; + partition.max = size; + return partition; +} + +internal void* +partition_allocate(Partition *data, i32 size){ + void *ret = 0; + if (size > 0 && data->pos + size < data->max){ + ret = data->base + data->pos; + data->pos += size; + } + return ret; +} + +inline void* +partition_current(Partition *data){ + return data->base + data->pos; +} + +inline i32 +partition_remaining(Partition *data){ + return data->max - data->pos; +} + +#define push_struct(part, T) (T*)partition_allocate(part, sizeof(T)) +#define push_array(part, T, size) (T*)partition_allocate(part, sizeof(T)*(size)) +#define push_block(part, size) partition_allocate(part, size) + +enum Memory_Bubble_Flag{ + MEM_BUBBLE_USED = 0x1, + MEM_BUBBLE_DEBUG = 0xD3000000, + MEM_BUBBLE_SYS_DEBUG = 0x5D000000, + MEM_BUBBLE_DEBUG_MASK = 0xFF000000 +}; +struct Bubble{ + Bubble *prev; + Bubble *next; + u32 flags; + i32 size; + u32 type; + u32 _unused_; +}; +struct General_Memory{ + Bubble sentinel; +}; + +inline void +insert_bubble(Bubble *prev, Bubble *bubble){ + bubble->prev = prev; + bubble->next = prev->next; + bubble->prev->next = bubble; + bubble->next->prev = bubble; +} + +inline void +remove_bubble(Bubble *bubble){ + bubble->prev->next = bubble->next; + bubble->next->prev = bubble->prev; +} + +#if FRED_INTERNAL +#define MEM_BUBBLE_FLAG_INIT MEM_BUBBLE_DEBUG +#else +#define MEM_BUBBLE_FLAG_INIT 0 +#endif + +internal void +general_memory_open(General_Memory *general, void *memory, i32 size){ + general->sentinel.prev = &general->sentinel; + general->sentinel.next = &general->sentinel; + general->sentinel.flags = MEM_BUBBLE_USED; + general->sentinel.size = 0; + + Bubble *first = (Bubble*)memory; + first->flags = (u32)MEM_BUBBLE_FLAG_INIT; + first->size = size - sizeof(Bubble); + insert_bubble(&general->sentinel, first); +} + +#define BUBBLE_MIN_SIZE 1024 + +internal void +general_memory_attempt_split(Bubble *bubble, i32 wanted_size){ + i32 remaining_size = bubble->size - wanted_size; + if (remaining_size >= BUBBLE_MIN_SIZE){ + bubble->size = wanted_size; + Bubble *new_bubble = (Bubble*)((u8*)(bubble + 1) + wanted_size); + new_bubble->flags = (u32)MEM_BUBBLE_FLAG_INIT; + new_bubble->size = remaining_size - sizeof(Bubble); + insert_bubble(bubble, new_bubble); + } +} + +internal void* +general_memory_allocate(General_Memory *general, i32 size, u32 type = 0){ + void *result = 0; + if (size < Kbytes(1)){ + int x = 1; AllowLocal(x); + } + for (Bubble *bubble = general->sentinel.next; + bubble != &general->sentinel; + bubble = bubble->next){ + if (!(bubble->flags & MEM_BUBBLE_USED)){ + if (bubble->size >= size){ + result = bubble + 1; + bubble->flags |= MEM_BUBBLE_USED; + bubble->type = type; + general_memory_attempt_split(bubble, size); + break; + } + } + } + return result; +} + +inline void +general_memory_do_merge(Bubble *left, Bubble *right){ + left->size += sizeof(Bubble) + right->size; + remove_bubble(right); +} + +inline void +general_memory_attempt_merge(Bubble *left, Bubble *right){ + if (!(left->flags & MEM_BUBBLE_USED) && + !(right->flags & MEM_BUBBLE_USED)){ + general_memory_do_merge(left, right); + } +} + +internal void +general_memory_free(General_Memory *general, void *memory){ + Bubble *bubble = ((Bubble*)memory) - 1; + Assert((!FRED_INTERNAL) || (bubble->flags & MEM_BUBBLE_DEBUG_MASK) == MEM_BUBBLE_DEBUG); + bubble->flags &= ~MEM_BUBBLE_USED; + bubble->type = 0; + Bubble *prev, *next; + prev = bubble->prev; + next = bubble->next; + general_memory_attempt_merge(bubble, next); + general_memory_attempt_merge(prev, bubble); +} + +internal void* +general_memory_reallocate(General_Memory *general, void *old, i32 old_size, i32 size, u32 type = 0){ + void *result = old; + Bubble *bubble = ((Bubble*)old) - 1; + bubble->type = type; + Assert((!FRED_INTERNAL) || (bubble->flags & MEM_BUBBLE_DEBUG_MASK) == MEM_BUBBLE_DEBUG); + i32 additional_space = size - bubble->size; + if (additional_space > 0){ + Bubble *next = bubble->next; + if (!(next->flags & MEM_BUBBLE_USED) && + next->size + sizeof(Bubble) >= additional_space){ + general_memory_do_merge(bubble, next); + general_memory_attempt_split(bubble, size); + } + else{ + result = general_memory_allocate(general, size, type); + if (old_size) memcpy(result, old, old_size); + general_memory_free(general, old); + } + } + return result; +} + +inline void* +general_memory_reallocate_nocopy(General_Memory *general, void *old, i32 size, u32 type = 0){ + return general_memory_reallocate(general, old, 0, size, type); +} + +struct Temp_Memory{ + Partition *part; + i32 pos; +}; + +internal Temp_Memory +begin_temp_memory(Partition *data){ + Temp_Memory result; + result.part = data; + result.pos = data->pos; + return result; +} + +internal void +end_temp_memory(Temp_Memory temp){ + temp.part->pos = temp.pos; +} + +struct Mem_Options{ + Partition part; + General_Memory general; +}; + +#if SOFTWARE_RENDER +struct Render_Target{ + void *pixel_data; + i32 width, height, pitch; +}; +#else +struct Render_Target{ + void *handle; + void *context; + i32_Rect clip_boxes[5]; + i32 clip_top; + i32 width, height; + i32 bound_texture; + u32 color; +}; +#endif + +struct Application_Memory{ + void *vars_memory; + i32 vars_memory_size; + + void *target_memory; + i32 target_memory_size; +}; + +#define KEY_INPUT_BUFFER_SIZE 4 +#define KEY_INPUT_BUFFER_DSIZE (KEY_INPUT_BUFFER_SIZE << 1) + +enum Key_Control{ + CONTROL_KEY_SHIFT, + CONTROL_KEY_CONTROL, + CONTROL_KEY_ALT, + // always last + CONTROL_KEY_COUNT +}; + +struct Key_Event_Data{ + u16 keycode; + u16 loose_keycode; + u16 character; + u16 character_no_caps_lock; +}; + +struct Key_Input_Data{ + // NOTE(allen): keycodes here + Key_Event_Data press[KEY_INPUT_BUFFER_SIZE]; + Key_Event_Data hold[KEY_INPUT_BUFFER_SIZE]; + i32 press_count; + i32 hold_count; + + // NOTE(allen): + // true when the key is held down + // false when the key is not held down + bool8 control_keys[CONTROL_KEY_COUNT]; + bool8 caps_lock; +}; + +struct Key_Summary{ + i32 count; + Key_Event_Data keys[KEY_INPUT_BUFFER_DSIZE]; + bool8 modifiers[CONTROL_KEY_COUNT]; +}; + +struct Key_Single{ + Key_Event_Data key; + bool8 *modifiers; +}; + +inline Key_Single +get_single_key(Key_Summary *summary, i32 index){ + Assert(index >= 0 && index < summary->count); + Key_Single key; + key.key = summary->keys[index]; + key.modifiers = summary->modifiers; + return key; +} + +struct Mouse_State{ + bool32 out_of_window; + bool32 left_button, right_button; + bool32 left_button_prev, right_button_prev; + i32 x, y; + i16 wheel; +}; + +struct Mouse_Summary{ + i32 mx, my; + bool32 l, r; + bool32 press_l, press_r; + bool32 release_l, release_r; + bool32 out_of_window; + bool32 wheel_used; + i16 wheel_amount; +}; + +struct Input_Summary{ + Mouse_Summary mouse; + Key_Summary keys; + Key_Codes *codes; +}; + +// TODO(allen): This can go, and we can just use a String for it. +struct Clipboard_Contents{ + u8 *str; + i32 size; +}; + +struct Thread_Context; + +internal bool32 +app_init(Thread_Context *thread, + Application_Memory *memory, + Key_Codes *lose_codes, + Clipboard_Contents clipboard); + +enum Application_Mouse_Cursor{ + 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 +}; + +struct Application_Step_Result{ + Application_Mouse_Cursor mouse_cursor_type; + bool32 redraw; +}; + +internal Application_Step_Result +app_step(Thread_Context *thread, + Key_Codes *codes, + Key_Input_Data *input, Mouse_State *state, + bool32 time_step, Render_Target *target, + Application_Memory *memory, + Clipboard_Contents clipboard, + bool32 first_step, bool32 force_redraw); + +#endif diff --git a/4ed_color_view.cpp b/4ed_color_view.cpp new file mode 100644 index 00000000..9ad104a9 --- /dev/null +++ b/4ed_color_view.cpp @@ -0,0 +1,2247 @@ +/* + * Mr. 4th Dimention - Allen Webster + * + * 20.08.2015 + * + * Color customizing view for 4coder + * + */ + +// TOP + +enum Color_View_Mode{ + CV_MODE_LIBRARY, + CV_MODE_IMPORT_FILE, + CV_MODE_EXPORT_FILE, + CV_MODE_IMPORT, + CV_MODE_EXPORT, + CV_MODE_ADJUSTING +}; + +struct Super_Color{ + Vec4 hsla; + Vec4 rgba; + u32 *out; +}; + +internal Super_Color +super_color_create(u32 packed){ + Super_Color result = {}; + result.rgba = unpack_color4(packed); + result.hsla = rgba_to_hsla(result.rgba); + return result; +} + +internal void +super_color_post_hsla(Super_Color *color, Vec4 hsla){ + color->hsla = hsla; + if (hsla.h == 1.f) + hsla.h = 0.f; + color->rgba = hsla_to_rgba(hsla); + *color->out = pack_color4(color->rgba); +} + +internal void +super_color_post_rgba(Super_Color *color, Vec4 rgba){ + color->rgba = rgba; + color->hsla = rgba_to_hsla(rgba); + *color->out = pack_color4(rgba); +} + +internal void +super_color_post_packed(Super_Color *color, u32 packed){ + color->rgba = unpack_color4(packed); + color->hsla = rgba_to_hsla(color->rgba); + *color->out = packed; +} + +u32 super_color_clear_masks[] = {0xFF00FFFF, 0xFFFF00FF, 0xFFFFFF00}; +u32 super_color_shifts[] = {16, 8, 0}; + +internal u32 +super_color_post_byte(Super_Color *color, i32 channel, u8 byte){ + u32 packed = *color->out; + packed &= super_color_clear_masks[channel]; + packed |= (byte << super_color_shifts[channel]); + super_color_post_packed(color, packed); + return packed; +} + +struct Color_Highlight{ + i32 ids[4]; +}; + +struct Widget_ID{ + i32 id; + i32 sub_id0; + i32 sub_id1; + i32 sub_id2; +}; + +inline bool32 +widget_match(Widget_ID s1, Widget_ID s2){ + return (s1.id == s2.id && s1.sub_id0 == s2.sub_id0 && + s1.sub_id1 == s2.sub_id1 && s1.sub_id2 == s2.sub_id2); +} + +struct UI_State{ + Render_Target *target; + Style *style; + Font *font; + Mouse_Summary *mouse; + Key_Summary *keys; + Key_Codes *codes; + Working_Set *working_set; + + Widget_ID selected, hover, hot; + bool32 activate_me; + bool32 redraw; + bool32 input_stage; + i32 sub_id1_change; + + real32 height, view_y; +}; + +inline bool32 +is_selected(UI_State *state, Widget_ID id){ + return widget_match(state->selected, id); +} + +inline bool32 +is_hot(UI_State *state, Widget_ID id){ + return widget_match(state->hot, id); +} + +inline bool32 +is_hover(UI_State *state, Widget_ID id){ + return widget_match(state->hover, id); +} + +struct UI_Layout{ + i32 row_count; + i32 row_item_width; + i32 row_max_item_height; + + i32_Rect rect; + i32 x, y; +}; + +struct UI_Layout_Restore{ + UI_Layout layout; + UI_Layout *dest; +}; + +struct Library_UI{ + UI_State state; + UI_Layout layout; + + Font_Set *fonts; + + Style_Library *styles; + Hot_Directory *hot_directory; +}; + +struct Color_UI{ + UI_State state; + UI_Layout layout; + + Font_Set *fonts; + + real32 hex_advance; + u32 *palette; + i32 palette_size; + + Color_Highlight highlight; + Super_Color color; + + bool32 has_hover_color; + Super_Color hover_color; +}; + +struct Color_View{ + View view_base; + Hot_Directory *hot_directory; + Style *main_style; + Style_Library *styles; + File_View *hot_file_view; + Font_Set *fonts; + u32 *palette; + Working_Set *working_set; + i32 palette_size; + Color_View_Mode mode; + UI_State state; + Super_Color color; + Color_Highlight highlight; + bool32 p4c_only; + Style_Library inspecting_styles; + bool8 import_export_check[64]; +}; + +inline Color_View* +view_to_color_view(View *view){ + Assert(!view || view->type == VIEW_TYPE_COLOR); + return (Color_View*)view; +} + +inline void +begin_layout(UI_Layout *layout, i32_Rect rect){ + layout->rect = rect; + layout->x = rect.x0; + layout->y = rect.y0; + layout->row_count = 0; + layout->row_max_item_height = 0; +} + +inline void +begin_row(UI_Layout *layout, i32 count){ + layout->row_count = count; + layout->row_item_width = (layout->rect.x1 - layout->x) / count; +} + +inline i32_Rect +layout_rect(UI_Layout *layout, i32 height){ + i32_Rect rect; + rect.x0 = layout->x; + rect.y0 = layout->y; + rect.x1 = rect.x0; + rect.y1 = rect.y0 + height; + if (layout->row_count > 0){ + --layout->row_count; + rect.x1 = rect.x0 + layout->row_item_width; + layout->x += layout->row_item_width; + layout->row_max_item_height = Max(height, layout->row_max_item_height); + } + if (layout->row_count == 0){ + rect.x1 = layout->rect.x1; + layout->row_max_item_height = Max(height, layout->row_max_item_height); + layout->y += layout->row_max_item_height; + layout->x = layout->rect.x0; + layout->row_max_item_height = 0; + } + return rect; +} + +inline UI_Layout_Restore +begin_sub_layout(UI_Layout *layout, i32_Rect area){ + UI_Layout_Restore restore; + restore.layout = *layout; + restore.dest = layout; + begin_layout(layout, area); + return restore; +} + +inline void +end_sub_layout(UI_Layout_Restore restore){ + *restore.dest = restore.layout; +} + +internal real32 +font_string_width(Font *font, char *str){ + real32 x = 0; + for (i32 i = 0; str[i]; ++i){ + x += font_get_glyph_width(font, str[i]); + } + return x; +} + +internal void +draw_gradient_slider(Render_Target *target, Vec4 base, i32 channel, + i32 steps, real32 top, real32_Rect slider, bool32 hsla){ + Vec4 low, high; + real32 *lowv, *highv; + real32 x; + real32 next_x; + real32 x_step; + real32 v_step; + real32 m; + + x = (real32)slider.x0; + x_step = (real32)(slider.x1 - slider.x0) / steps; + v_step = top / steps; + m = 1.f / top; + lowv = &low.v[channel]; + highv = &high.v[channel]; + + if (hsla){ + for (i32 i = 0; i < steps; ++i){ + low = high = base; + *lowv = (i * v_step); + *highv = *lowv + v_step; + *lowv *= m; + *highv *= m; + low = hsla_to_rgba(low); + high = hsla_to_rgba(high); + + next_x = x + x_step; + draw_gradient_2corner_clipped( + target, x, slider.y0, next_x, slider.y1, + low, high); + x = next_x; + } + } + else{ + for (i32 i = 0; i < steps; ++i){ + low = high = base; + *lowv = (i * v_step); + *highv = *lowv + v_step; + *lowv *= m; + *highv *= m; + + next_x = x + x_step; + draw_gradient_2corner_clipped( + target, x, slider.y0, next_x, slider.y1, + low, high); + x = next_x; + } + } +} + +inline void +draw_hsl_slider(Render_Target *target, Vec4 base, i32 channel, i32 steps, real32 top, + real32_Rect slider){ + draw_gradient_slider(target, base, channel, steps, top, slider, 1); +} + +inline void +draw_rgb_slider(Render_Target *target, Vec4 base, i32 channel, i32 steps, real32 top, + real32_Rect slider){ + draw_gradient_slider(target, base, channel, steps, top, slider, 0); +} + +inline Widget_ID +make_id(UI_State *state, i32 id){ + Widget_ID r = state->selected; + r.id = id; + return r; +} + +inline Widget_ID +make_sub0(UI_State *state, i32 id){ + Widget_ID r = state->selected; + r.sub_id0 = id; + return r; +} + +inline Widget_ID +make_sub1(UI_State *state, i32 id){ + Widget_ID r = state->selected; + r.sub_id1 = id; + return r; +} + +inline Widget_ID +make_sub2(UI_State *state, i32 id){ + Widget_ID r = state->selected; + r.sub_id2 = id; + return r; +} + +internal bool32 +ui_do_button_input(UI_State *state, i32_Rect rect, Widget_ID id, bool32 activate, bool32 *right = 0){ + bool32 result = 0; + Mouse_Summary *mouse = state->mouse; + bool32 hover = hit_check(mouse->mx, mouse->my, rect); + if (hover){ + state->hover = id; + if (activate) state->activate_me = 1; + if (mouse->press_l || (mouse->press_r && right)) state->hot = id; + if (mouse->l && mouse->r) state->hot = {}; + } + bool32 is_match = widget_match(state->hot, id); + if (mouse->release_l && is_match){ + if (hover) result = 1; + state->redraw = 1; + } + if (right && mouse->release_r && is_match){ + if (hover) *right = 1; + state->redraw = 1; + } + return result; +} + +internal bool32 +ui_do_subdivided_button_input(UI_State *state, i32_Rect rect, i32 parts, Widget_ID id, bool32 activate, i32 *indx_out, bool32 *right = 0){ + bool32 result = 0; + real32 x0, x1; + i32_Rect sub_rect; + Widget_ID sub_widg = id; + real32 sub_width = (rect.x1 - rect.x0) / (real32)parts; + sub_rect.y0 = rect.y0; + sub_rect.y1 = rect.y1; + x1 = (real32)rect.x0; + + for (i32 i = 0; i < parts; ++i){ + x0 = x1; + x1 = x1 + sub_width; + sub_rect.x0 = TRUNC32(x0); + sub_rect.x1 = TRUNC32(x1); + sub_widg.sub_id2 = i; + if (ui_do_button_input(state, sub_rect, sub_widg, activate, right)){ + *indx_out = i; + break; + } + } + + return result; +} + +internal real32 +ui_do_vscroll_input(UI_State *state, i32_Rect top, i32_Rect bottom, i32_Rect slider, + Widget_ID id, real32 val, real32 step_amount, + real32 smin, real32 smax, real32 vmin, real32 vmax){ + Mouse_Summary *mouse = state->mouse; + i32 mx = mouse->mx; + i32 my = mouse->my; + if (hit_check(mx, my, top)){ + state->hover = id; + state->hover.sub_id2 = 1; + } + if (hit_check(mx, my, bottom)){ + state->hover = id; + state->hover.sub_id2 = 2; + } + if (hit_check(mx, my, slider)){ + state->hover = id; + state->hover.sub_id2 = 3; + } + if (mouse->press_l) state->hot = state->hover; + if (id.id == state->hot.id){ + if (mouse->release_l){ + Widget_ID wid1, wid2; + wid1 = wid2 = id; + wid1.sub_id2 = 1; + wid2.sub_id2 = 2; + if (state->hot.sub_id2 == 1 && is_hover(state, wid1)) val -= step_amount; + if (state->hot.sub_id2 == 2 && is_hover(state, wid2)) val += step_amount; + state->redraw = 1; + } + if (state->hot.sub_id2 == 3){ + real32 S, L; + S = (real32)mouse->my - (slider.y1 - slider.y0) / 2; + if (S < smin) S = smin; + if (S > smax) S = smax; + L = unlerp(smin, S, smax); + val = lerp(vmin, L, vmax); + state->redraw = 1; + } + } + return val; +} + +internal bool32 +ui_do_text_field_input(UI_State *state, String *str){ + bool32 result = 0; + Key_Summary *keys = state->keys; + for (i32 key_i = 0; key_i < keys->count; ++key_i){ + Key_Single key = get_single_key(keys, key_i); + char c = (char)key.key.character; + if (char_is_basic(c) && str->size < str->memory_size-1){ + str->str[str->size++] = c; + str->str[str->size] = 0; + } + else if (c == '\n'){ + result = 1; + } + else if (key.key.keycode == state->codes->back && str->size > 0){ + str->str[--str->size] = 0; + } + } + return result; +} + +internal bool32 +ui_do_file_field_input(UI_State *state, Hot_Directory *hot_dir){ + bool32 result = 0; + Key_Summary *keys = state->keys; + for (i32 key_i = 0; key_i < keys->count; ++key_i){ + Key_Single key = get_single_key(keys, key_i); + String *str = &hot_dir->string; + terminate_with_null(str); + Single_Line_Input_Step step = + app_single_file_input_step(state->codes, state->working_set, key, str, hot_dir, 1); + if (step.hit_newline || step.hit_ctrl_newline) result = 1; + } + return result; +} + +internal bool32 +ui_do_line_field_input(UI_State *state, String *string){ + bool32 result = 0; + Key_Summary *keys = state->keys; + for (i32 key_i = 0; key_i < keys->count; ++key_i){ + Key_Single key = get_single_key(keys, key_i); + terminate_with_null(string); + Single_Line_Input_Step step = + app_single_line_input_step(state->codes, key, string); + if (step.hit_newline || step.hit_ctrl_newline) result = 1; + } + return result; +} + +internal bool32 +ui_do_slider_input(UI_State *state, i32_Rect rect, Widget_ID wid, + real32 min, real32 max, real32 *v){ + bool32 result = 0; + ui_do_button_input(state, rect, wid, 0); + Mouse_Summary *mouse = state->mouse; + if (is_hot(state, wid)){ + result = 1; + *v = unlerp(min, (real32)mouse->mx, max); + state->redraw = 1; + } + return result; +} + +struct UI_Style{ + u32 dark, dim, bright; +}; + +internal UI_Style +get_ui_style(Style *style){ + UI_Style ui_style; + ui_style.dark = style->main.back_color; + ui_style.dim = style->main.margin_color; + ui_style.bright = style->main.margin_active_color; + return ui_style; +} + +internal void +do_label(UI_State *state, UI_Layout *layout, char *text, real32 height = 2.f){ + Style *style = state->style; + Font *font = style->font; + i32_Rect label = layout_rect(layout, FLOOR32(font->height * height)); + + if (!state->input_stage){ + Render_Target *target = state->target; + u32 back = style->main.margin_color; + u32 fore = style->main.default_color; + draw_rectangle(target, label, back); + i32 height = label.y1 - label.y0; + draw_string(target, font, text, label.x0, + label.y0 + (height - font->height)/2, fore); + } +} + +internal void +get_colors(UI_State *state, u32 *back, u32 *fore, Widget_ID wid, UI_Style style){ + bool32 hover = is_hover(state, wid); + bool32 hot = is_hot(state, wid); + i32 level = hot + hover; + switch (level){ + case 2: + *back = style.bright; + *fore = style.dark; + break; + case 1: + *back = style.dim; + *fore = style.bright; + break; + case 0: + *back = style.dark; + *fore = style.bright; + break; + } +} + +internal bool32 +do_button(i32 id, UI_State *state, UI_Layout *layout, char *text, + bool32 is_toggle = 0, bool32 on = 0){ + bool32 result = 0; + Font *font = state->font; + i32 character_h = font->height; + + i32_Rect btn_rect = layout_rect(layout, font->height * 2); + btn_rect = get_inner_rect(btn_rect, 2); + + Widget_ID wid = make_id(state, id); + + if (state->input_stage){ + if (ui_do_button_input(state, btn_rect, wid, 0)){ + result = 1; + } + } + else{ + Render_Target *target = state->target; + UI_Style ui_style = get_ui_style(state->style); + u32 back, fore, outline; + outline = ui_style.bright; + get_colors(state, &back, &fore, wid, ui_style); + + draw_rectangle(target, btn_rect, back); + draw_rectangle_outline(target, btn_rect, outline); + real32 text_width = font_string_width(font, text); + i32 box_width = btn_rect.x1 - btn_rect.x0; + i32 box_height = btn_rect.y1 - btn_rect.y0; + i32 x_pos = TRUNC32(btn_rect.x0 + (box_width - text_width)*.5f); + draw_string(target, font, text, x_pos, btn_rect.y0 + (box_height - character_h) / 2, fore); + + if (is_toggle){ + i32_Rect on_box = get_inner_rect(btn_rect, character_h/2); + on_box.x1 = on_box.x0 + (on_box.y1 - on_box.y0); + + if (on) draw_rectangle(target, on_box, fore); + else draw_rectangle(target, on_box, back); + draw_rectangle_outline(target, on_box, fore); + } + } + + return result; +} + +internal void +do_scroll_bar(UI_State *state, i32_Rect rect){ + i32 id = 1; + i32 w = (rect.x1 - rect.x0); + i32 h = (rect.y1 - rect.y0); + + i32_Rect top_arrow, bottom_arrow; + top_arrow.x0 = rect.x0; + top_arrow.x1 = rect.x1; + top_arrow.y0 = rect.y0; + top_arrow.y1 = top_arrow.y0 + w; + + bottom_arrow.x0 = rect.x0; + bottom_arrow.x1 = rect.x1; + bottom_arrow.y1 = rect.y1; + bottom_arrow.y0 = bottom_arrow.y1 - w; + + real32 space_h = (real32)(bottom_arrow.y0 - top_arrow.y1); + if (space_h <= w) return; + + i32 slider_h = w; + + real32 view_hmin = 0; + real32 view_hmax = state->height - h; + real32 L = unlerp(view_hmin, state->view_y, view_hmax); + + real32 slider_hmin = (real32)top_arrow.y1; + real32 slider_hmax = (real32)bottom_arrow.y0 - slider_h; + real32 S = lerp(slider_hmin, L, slider_hmax); + + i32_Rect slider; + slider.x0 = rect.x0; + slider.x1 = rect.x1; + slider.y0 = FLOOR32(S); + slider.y1 = slider.y0 + slider_h; + + Widget_ID wid = make_id(state, id); + + if (state->input_stage){ + state->view_y = + ui_do_vscroll_input(state, top_arrow, bottom_arrow, slider, + wid, state->view_y, (real32)state->font->height, + slider_hmin, slider_hmax, view_hmin, view_hmax); + } + else{ + Render_Target *target = state->target; + real32 x0, y0, x1, y1, x2, y2; + real32 w_1_2 = w*.5f; + real32 w_1_3 = w*.333333f; + real32 w_2_3 = w*.666667f; + + + UI_Style ui_style = get_ui_style(state->style); + u32 outline, back, fore; + + outline = ui_style.bright; + + wid.sub_id2 = 0; + + x0 = (w_1_2 + top_arrow.x0); + x1 = (w_1_3 + top_arrow.x0); + x2 = (w_2_3 + top_arrow.x0); + + ++wid.sub_id2; + y0 = (w_1_3 + top_arrow.y0); + y1 = (w_2_3 + top_arrow.y0); + y2 = (w_2_3 + top_arrow.y0); + get_colors(state, &back, &fore, wid, ui_style); + draw_rectangle(target, top_arrow, back); + draw_rectangle_outline(target, top_arrow, outline); + draw_triangle_3corner(target, x0, y0, x1, y1, x2, y2, fore); + + ++wid.sub_id2; + y0 = (w_2_3 + bottom_arrow.y0); + y1 = (w_1_3 + bottom_arrow.y0); + y2 = (w_1_3 + bottom_arrow.y0); + get_colors(state, &back, &fore, wid, ui_style); + draw_rectangle(target, bottom_arrow, back); + draw_rectangle_outline(target, bottom_arrow, outline); + draw_triangle_3corner(target, x0, y0, x1, y1, x2, y2, fore); + + ++wid.sub_id2; + get_colors(state, &back, &fore, wid, ui_style); + draw_rectangle(target, slider, back); + draw_rectangle_outline(target, slider, outline); + + draw_rectangle_outline(target, rect, outline); + } +} + +internal void +do_single_slider(i32 sub_id, Color_UI *ui, i32 channel, bool32 is_rgba, + i32 grad_steps, real32 top, real32_Rect slider, real32 v_handle, + i32_Rect rect){ + real32_Rect click_box = slider; + click_box.y0 -= v_handle; + + if (ui->state.input_stage){ + real32 v; + if (ui_do_slider_input(&ui->state, i32R(click_box), make_sub1(&ui->state, sub_id), + slider.x0, slider.x1, &v)){ + Vec4 new_color; + if (is_rgba) new_color = ui->color.rgba; + else new_color = ui->color.hsla; + new_color.v[channel] = clamp(0.f, v, 1.f); + if (is_rgba) super_color_post_rgba(&ui->color, new_color); + else super_color_post_hsla(&ui->color, new_color); + } + } + else{ + Render_Target *target = ui->state.target; + Vec4 color; + real32 x; + if (is_rgba){ + color = ui->color.rgba; + draw_rgb_slider(target, V4(0,0,0,1.f), channel, 10, 100.f, slider); + } + else{ + i32 steps; + real32 top; + if (channel == 0){ + steps = 45; + top = 360.f; + } + else{ + steps = 10; + top = 100.f; + } + color = ui->color.hsla; + draw_hsl_slider(target, color, channel, steps, top, slider); + } + + x = lerp(slider.x0, color.v[channel], slider.x1); + draw_rectangle( + target, real32R(x, slider.y0, x + 1, slider.y1), 0xff000000); + + draw_rectangle( + target, real32R(x-2, click_box.y0, x+3, slider.y0-4), 0xff777777); + } +} + +internal void +do_hsl_sliders(Color_UI *ui, i32_Rect rect){ + real32 bar_width = (real32)(rect.x1 - rect.x0 - 20); + if (bar_width > 45){ + real32_Rect slider; + real32 y; + i32 sub_id; + + real32 v_full_space = 30.f; + real32 v_half_space = 15.f; + real32 v_quarter_space = 12.f; + real32 v_handle = 9.f; + + slider.x0 = rect.x0 + 10.f; + slider.x1 = slider.x0 + bar_width; + + sub_id = 0; + + i32 step_count[] = {45, 10, 10}; + real32 tops[] = {360.f, 100.f, 100.f}; + + y = rect.y0 + v_quarter_space; + for (i32 i = 0; i < 3; ++i){ + ++sub_id; + slider.y0 = y; + slider.y1 = slider.y0 + v_half_space; + do_single_slider(sub_id, ui, i, 0, step_count[i], tops[i], slider, v_handle, rect); + y += v_full_space; + } + } +} + +enum Channel_Field_Type{ + CF_DEC, + CF_HEX +}; + +internal void +fill_buffer_color_channel(char *buffer, u8 x, Channel_Field_Type ftype){ + if (ftype == CF_DEC){ + u8 x0; + x0 = x / 10; + buffer[2] = (x - (10*x0)) + '0'; + x = x0; + x0 = x / 10; + buffer[1] = (x - (10*x0)) + '0'; + x = x0; + x0 = x / 10; + buffer[0] = (x - (10*x0)) + '0'; + } + else{ + u8 n; + n = x & 0xF; + buffer[1] = int_to_hexchar(n); + x >>= 4; + n = x & 0xF; + buffer[0] = int_to_hexchar(n); + } +} + +internal bool32 +do_channel_field(i32 sub_id, Color_UI *ui, u8 *channel, Channel_Field_Type ftype, + i32 y, u32 color, u32 back, i32 x0, i32 x1){ + bool32 result = 0; + Render_Target *target = ui->state.target; + Font *font = ui->state.font; + + i32_Rect hit_region; + hit_region.x0 = x0; + hit_region.x1 = x1; + hit_region.y0 = y; + hit_region.y1 = y + font->height; + + i32 digit_count; + if (ftype == CF_DEC) digit_count = 3; + else digit_count = 2; + + if (ui->state.input_stage){ + i32 indx; + ui_do_subdivided_button_input(&ui->state, hit_region, digit_count, + make_sub1(&ui->state, sub_id), 1, &indx); + } + else{ + if (ui->state.hover.sub_id1 == sub_id && ui->state.selected.sub_id1 != sub_id){ + draw_rectangle(target, hit_region, back); + } + } + + char string_buffer[4]; + string_buffer[digit_count] = 0; + fill_buffer_color_channel(string_buffer, *channel, ftype); + + if (ui->state.selected.sub_id1 == sub_id){ + i32 indx = ui->state.selected.sub_id2; + if (ui->state.input_stage){ + Key_Summary *keys = ui->state.keys; + Key_Codes *codes = ui->state.codes; + for (i32 key_i = 0; key_i < keys->count; ++key_i){ + Key_Single key = get_single_key(keys, key_i); + + if (key.key.keycode == codes->right){ + ++indx; + if (indx > digit_count-1) indx = 0; + } + if (key.key.keycode == codes->left){ + --indx; + if (indx < 0) indx = digit_count-1; + } + + i32 new_value = *channel; + if (key.key.keycode == codes->up || key.key.keycode == codes->down){ + i32 place = digit_count-1-indx; + i32 base = (ftype == CF_DEC)?10:0x10; + i32 step_amount = 1; + while (place > 0){ + step_amount *= base; + --place; + } + if (key.key.keycode == codes->down){ + step_amount = 0 - step_amount; + } + new_value += step_amount; + } + + u8 c = (u8)key.key.character; + bool32 is_good = (ftype == CF_DEC)?char_is_numeric(c):char_is_hex(c); + if (is_good){ + string_buffer[indx] = c; + if (ftype == CF_DEC) + new_value = str_to_int(make_string(string_buffer, 3)); + else + new_value = hexstr_to_int(make_string(string_buffer, 2)); + ++indx; + if (indx > digit_count-1) indx = 0; + } + + if (c == '\n'){ + switch (sub_id){ + case 1: case 2: + case 4: case 5: + ui->state.sub_id1_change = sub_id + 3; break; + + case 7: case 8: + ui->state.sub_id1_change = sub_id - 6; break; + } + } + + if (new_value != *channel){ + if (new_value > 255){ + *channel = 255; + } + else if (new_value < 0){ + *channel = 0; + } + else{ + *channel = (u8)new_value; + } + fill_buffer_color_channel(string_buffer, *channel, ftype); + result = 1; + } + ui->state.selected.sub_id2 = indx; + } + } + else{ + real32_Rect r = real32R(hit_region); + r.x0 += indx*ui->hex_advance+1; + r.x1 = r.x0+ui->hex_advance+1; + draw_rectangle(target, r, back); + } + } + + if (!ui->state.input_stage) + draw_string_mono(target, font, string_buffer, + (real32)x0 + 1, (real32)y, ui->hex_advance, + color); + + return result; +} + +internal void +do_rgb_sliders(Color_UI *ui, i32_Rect rect){ + i32 dec_x0, dec_x1; + dec_x0 = rect.x0 + 10; + dec_x1 = TRUNC32(dec_x0 + ui->hex_advance*3 + 2); + + i32 hex_x0, hex_x1; + hex_x0 = dec_x1 + 10; + hex_x1 = TRUNC32(hex_x0 + ui->hex_advance*2 + 2); + + rect.x0 = hex_x1; + real32 bar_width = (real32)(rect.x1 - rect.x0 - 20); + + real32_Rect slider; + real32 y; + i32 sub_id; + u8 channel; + + real32 v_full_space = 30.f; + real32 v_half_space = 15.f; + real32 v_quarter_space = 12.f; + real32 v_handle = 9.f; + + u32 packed_color = *ui->color.out; + + y = rect.y0 + v_quarter_space; + slider.x0 = rect.x0 + 10.f; + slider.x1 = slider.x0 + bar_width; + + sub_id = 0; + + persist i32 shifts[3] = { 16, 8, 0 }; + persist u32 fore_colors[3] = { 0xFFFF0000, 0xFF00FF00, 0xFF1919FF }; + persist u32 back_colors[3] = { 0xFF222222, 0xFF222222, 0xFF131313 }; + + for (i32 i = 0; i < 3; ++i){ + i32 shift = shifts[i]; + u32 fore = fore_colors[i]; + u32 back = back_colors[i]; + + ++sub_id; + channel = (packed_color >> shift) & 0xFF; + if (do_channel_field(sub_id, ui, &channel, CF_DEC, + (i32)y, fore, back, dec_x0, dec_x1)) + super_color_post_byte(&ui->color, i, channel); + + ++sub_id; + channel = (packed_color >> shift) & 0xFF; + if (do_channel_field(sub_id, ui, &channel, CF_HEX, + (i32)y, fore, back, hex_x0, hex_x1)) + super_color_post_byte(&ui->color, i, channel); + + ++sub_id; + slider.y0 = y; + slider.y1 = slider.y0 + v_half_space; + if (bar_width > 45.f) + do_single_slider(sub_id, ui, i, 1, 10, 100.f, slider, v_handle, rect); + y += v_full_space; + } +} + +struct Blob_Layout{ + i32_Rect rect; + i32 x, y; + i32 size, space; +}; + +internal void +begin_layout(Blob_Layout *layout, i32_Rect rect){ + layout->rect = rect; + layout->x = rect.x0 + 10; + layout->y = rect.y0; + layout->size = 20; + layout->space = 5; +} + +internal void +do_blob(Color_UI *ui, Blob_Layout *layout, u32 color, bool32 *set_me, i32 sub_id){ + i32_Rect rect = layout->rect; + real32_Rect blob; + blob.x0 = (real32)layout->x; + blob.y0 = (real32)layout->y; + blob.x1 = blob.x0 + layout->size; + blob.y1 = blob.y0 + layout->size; + + layout->y += layout->size + layout->space; + if (layout->y + layout->size + layout->space*2 > rect.y1){ + layout->y = rect.y0; + layout->x += layout->size + layout->space; + } + + if (ui->state.input_stage){ + bool32 right = 0; + if (ui_do_button_input(&ui->state, i32R(blob), make_sub1(&ui->state, sub_id), 0, &right)){ + super_color_post_packed(&ui->color, color); + } + else if (right) *set_me = 1; + } + else{ + Render_Target *target = ui->state.target; + draw_rectangle(target, blob, color); + persist u32 silver = 0xFFa0a0a0; + draw_rectangle_outline(target, blob, silver); + } +} + +inline void +do_blob(Color_UI *ui, Blob_Layout *layout, u32 *color, bool32 *set_me){ + i32 sub_id = (i32)((char*)color - (char*)ui->state.style); + do_blob(ui, layout, *color, set_me, sub_id); +} + +internal void +do_v_divide(Color_UI *ui, Blob_Layout *layout, i32 width){ + i32_Rect rect = layout->rect; + if (layout->y > rect.y0){ + layout->x += layout->size + layout->space; + } + layout->x += width; + layout->y = rect.y0; +} + +internal void +do_palette(Color_UI *ui, i32_Rect rect){ + Style *style = ui->state.style; + Blob_Layout layout; + begin_layout(&layout, rect); + bool32 set_me; + + do_blob(ui, &layout, &style->main.back_color, &set_me); + do_blob(ui, &layout, &style->main.margin_color, &set_me); + do_blob(ui, &layout, &style->main.margin_active_color, &set_me); + + do_blob(ui, &layout, &style->main.cursor_color, &set_me); + do_blob(ui, &layout, &style->main.at_cursor_color, &set_me); + do_blob(ui, &layout, &style->main.mark_color, &set_me); + + do_blob(ui, &layout, &style->main.highlight_color, &set_me); + do_blob(ui, &layout, &style->main.at_highlight_color, &set_me); + + do_blob(ui, &layout, &style->main.default_color, &set_me); + do_blob(ui, &layout, &style->main.comment_color, &set_me); + do_blob(ui, &layout, &style->main.keyword_color, &set_me); + do_blob(ui, &layout, &style->main.str_constant_color, &set_me); + do_blob(ui, &layout, &style->main.char_constant_color, &set_me); + do_blob(ui, &layout, &style->main.int_constant_color, &set_me); + do_blob(ui, &layout, &style->main.float_constant_color, &set_me); + do_blob(ui, &layout, &style->main.bool_constant_color, &set_me); + do_blob(ui, &layout, &style->main.include_color, &set_me); + do_blob(ui, &layout, &style->main.preproc_color, &set_me); + do_blob(ui, &layout, &style->main.special_character_color, &set_me); + + do_blob(ui, &layout, &style->main.highlight_junk_color, &set_me); + do_blob(ui, &layout, &style->main.highlight_white_color, &set_me); + + do_blob(ui, &layout, &style->main.paste_color, &set_me); + + do_blob(ui, &layout, &style->main.file_info_style.bar_color, &set_me); + do_blob(ui, &layout, &style->main.file_info_style.base_color, &set_me); + do_blob(ui, &layout, &style->main.file_info_style.pop1_color, &set_me); + do_blob(ui, &layout, &style->main.file_info_style.pop2_color, &set_me); + + do_v_divide(ui, &layout, 20); + + if (!ui->state.input_stage){ + Render_Target *target = ui->state.target; + Font *font = style->font; + draw_string(target, font, "Global Palette: right click to save color", + layout.x, layout.rect.y0, style->main.default_color); + } + + layout.rect.y0 += layout.size + layout.space; + layout.y = layout.rect.y0; + i32 palette_size = ui->palette_size + 1000; + u32 *color = ui->palette; + for (i32 i = 1000; i < palette_size; ++i, ++color){ + set_me = 0; + do_blob(ui, &layout, *color, &set_me, i); + if (set_me){ + *color = *ui->color.out; + ui->state.redraw = 1; + } + } +} + +internal void +do_sub_button(i32 id, Color_UI *ui, char *text){ + Font *font = ui->state.font; + + i32_Rect rect = layout_rect(&ui->layout, font->height + 2); + + if (ui->state.input_stage){ + ui_do_button_input(&ui->state, rect, make_sub0(&ui->state, id), 1); + } + else{ + Render_Target *target = ui->state.target; + u32 back_color, text_color; + text_color = 0xFFDDDDDD; + if (ui->state.selected.sub_id0 == id){ + back_color = 0xFF444444; + } + else if (ui->state.hover.sub_id0 == id){ + back_color = 0xFF222222; + } + else{ + back_color = 0xFF111111; + } + + draw_rectangle(target, rect, back_color); + draw_string(target, font, text, rect.x0, rect.y0 + 1, + text_color); + } +} + +internal void +do_color_adjuster(Color_UI *ui, u32 *color, + u32 text_color, u32 back_color, char *name){ + i32 id = raw_ptr_dif(color, ui->state.style); + Render_Target *target = ui->state.target; + Font *font = ui->state.font; + i32 character_h = font->height; + u32 text = 0, back = 0; + + i32_Rect bar = layout_rect(&ui->layout, character_h); + + if (ui->state.input_stage){ + if (ui_do_button_input(&ui->state, bar, make_id(&ui->state, id), 1)){ + ui->has_hover_color = 1; + ui->hover_color = super_color_create(*color); + } + } + + else{ + u32 text_hover = 0xFF101010; + u32 back_hover = 0xFF999999; + if (ui->state.selected.id != id && ui->state.hover.id == id){ + text = text_hover; + back = back_hover; + } + else{ + text = text_color; + back = back_color; + } + + draw_rectangle(target, bar, back); + i32 end_pos = draw_string(target, font, name, bar.x0, bar.y0, text); + + real32 x_spacing = ui->hex_advance; + i32_Rect temp_rect = bar; + temp_rect.x0 = temp_rect.x1 - CEIL32(x_spacing * 9.f); + if (temp_rect.x0 >= end_pos + x_spacing){ + u32 n = *color; + char full_hex_string[] = "0x000000"; + for (i32 i = 7; i >= 2; --i){ + i32 m = (n & 0xF); + n >>= 4; + full_hex_string[i] = int_to_hexchar(m); + } + draw_string_mono(target, font, full_hex_string, + (real32)temp_rect.x0, (real32)bar.y0, + x_spacing, text); + } + + for (i32 i = 0; i < ArrayCount(ui->highlight.ids); ++i){ + if (ui->highlight.ids[i] == id){ + draw_rectangle_outline(target, real32R(bar), text_color); + break; + } + } + } + + if (ui->state.selected.id == id){ + i32_Rect expanded = layout_rect(&ui->layout, 115 + (character_h + 2)); + UI_Layout_Restore restore = begin_sub_layout(&ui->layout, expanded); + + ui->color.out = color; + + if (ui->state.input_stage){ + if (ui->state.selected.sub_id0 == 0){ + ui->state.selected.sub_id0 = 1; + } + } + else{ + draw_rectangle(target, expanded, 0xff000000); + } + + begin_row(&ui->layout, 3); + do_sub_button(1, ui, "HSL"); + do_sub_button(2, ui, "RGB"); + do_sub_button(3, ui, "Palette"); + + i32_Rect sub_rect; + sub_rect = expanded; + sub_rect.y0 += 10 + character_h; + + switch (ui->state.selected.sub_id0){ + case 1: do_hsl_sliders(ui, sub_rect); break; + case 2: do_rgb_sliders(ui, sub_rect); break; + case 3: do_palette(ui, sub_rect); break; + } + + end_sub_layout(restore); + } +} + +internal void +do_style_name(Color_UI *ui){ + i32 id = -3; + Font *font = ui->state.font; + + i32_Rect srect = layout_rect(&ui->layout, font->height); + + Widget_ID wid = make_id(&ui->state, id); + bool32 selected = is_selected(&ui->state, wid); + + if (ui->state.input_stage){ + if (!selected){ + ui_do_button_input(&ui->state, srect, wid, 1); + } + else{ + Style *style = ui->state.style; + if (ui_do_text_field_input(&ui->state, &style->name)){ + ui->state.selected = {}; + } + } + } + else{ + Render_Target *target = ui->state.target; + Style *style = ui->state.style; + u32 back, fore_text, fore_label; + if (selected){ + back = 0xFF000000; + fore_label = 0xFF808080; + fore_text = 0xFFFFFFFF; + } + else if (is_hover(&ui->state, wid)){ + back = 0xFF999999; + fore_text = fore_label = 0xFF101010; + } + else{ + back = style->main.back_color; + fore_text = fore_label = style->main.default_color; + } + + draw_rectangle(target, srect, back); + i32 x = srect.x0; + x = draw_string(target, font, "NAME: ", + x, srect.y0, fore_label); + x = draw_string(target, font, style->name.str, + x, srect.y0, fore_text); + } +} + +internal bool32 +do_font_option(Color_UI *ui, Font *font){ + bool32 result = 0; + i32 sub_id = (i32)(font); + i32_Rect orect = layout_rect(&ui->layout, font->height); + + Widget_ID wid = make_sub0(&ui->state, sub_id); + if (ui->state.input_stage){ + if (ui_do_button_input(&ui->state, orect, wid, 0)){ + result = 1; + } + } + else{ + Render_Target *target = ui->state.target; + u32 back, fore; + if (is_hover(&ui->state, wid)){ + back = 0xFF999999; + fore = 0xFF101010; + } + else{ + back = 0xFF000000; + fore = 0xFFFFFFFF; + } + draw_rectangle(target, orect, back); + i32 x = orect.x0; + x = draw_string(target, font, "->", + x, orect.y0, fore); + x = draw_string(target, font, font->name.str, + x, orect.y0, fore); + } + + return result; +} + +internal void +do_font_switch(Color_UI *ui){ + i32 id = -2; + Render_Target *target = ui->state.target; + Font *font = ui->state.font; + i32 character_h = font->height; + + i32_Rect srect = layout_rect(&ui->layout, character_h); + + Widget_ID wid = make_id(&ui->state, id); + + if (ui->state.input_stage){ + ui_do_button_input(&ui->state, srect, wid, 1); + } + else{ + Style *style = ui->state.style; + u32 back, fore; + if (is_hover(&ui->state, wid) && !is_selected(&ui->state, wid)){ + back = 0xFF999999; + fore = 0xFF101010; + } + else{ + back = style->main.back_color; + fore = style->main.default_color; + } + draw_rectangle(target, srect, back); + i32 x = srect.x0; + x = draw_string(target, font, "FONT: ", + x, srect.y0, fore); + x = draw_string(target, font, font->name.str, + x, srect.y0, fore); + } + + if (is_selected(&ui->state, wid)){ + Font_Set *fonts = ui->fonts; + Font *font_opt = fonts->fonts; + i32 count = fonts->count; + srect = layout_rect(&ui->layout, character_h/2); + if (!ui->state.input_stage) + draw_rectangle(target, srect, 0xFF000000); + + for (i32 i = 0; i < count; ++i, ++font_opt){ + if (font_opt == font) continue; + if (do_font_option(ui, font_opt)){ + ui->state.style->font = font_opt; + ui->state.style->font_changed = 1; + } + } + + srect = layout_rect(&ui->layout, character_h/2); + if (!ui->state.input_stage) + draw_rectangle(target, srect, 0xFF000000); + } +} + +internal UI_State +ui_state_init(UI_State *state_in, Render_Target *target, Input_Summary *user_input, + Style *style, Working_Set *working_set, bool32 input_stage){ + UI_State state; + state.target = target; + state.style = style; + state.font = style->font; + state.working_set = working_set; + state.mouse = &user_input->mouse; + state.keys = &user_input->keys; + state.codes = user_input->codes; + state.selected = state_in->selected; + state.hot = state_in->hot; + if (input_stage) state.hover = {}; + else state.hover = state_in->hover; + state.redraw = 0; + state.activate_me = 0; + state.input_stage = input_stage; + state.height = state_in->height; + state.view_y = state_in->view_y; + return state; +} + +inline bool32 +ui_state_match(UI_State a, UI_State b){ + return (widget_match(a.selected, b.selected) && + widget_match(a.hot, b.hot) && + widget_match(a.hover, b.hover)); +} + +internal bool32 +ui_finish_frame(UI_State *persist_state, UI_State *state, UI_Layout *layout, i32_Rect rect, + bool32 do_wheel, bool32 *did_activation){ + bool32 result = 0; + real32 h = layout->y + persist_state->view_y - rect.y0; + real32 max_y = h - (rect.y1 - rect.y0); + + persist_state->height = h; + persist_state->view_y = state->view_y; + + if (state->input_stage){ + Mouse_Summary *mouse = state->mouse; + if (mouse->wheel_used && do_wheel){ + persist_state->view_y += mouse->wheel_amount*state->font->height; + result = 1; + } + if (mouse->release_l && widget_match(state->hot, state->hover)){ + if (did_activation) *did_activation = 1; + if (state->activate_me){ + state->selected = state->hot; + } + } + if (!mouse->l && !mouse->r){ + state->hot = {}; + } + + if (!ui_state_match(*persist_state, *state) || state->redraw){ + result = 1; + } + + *persist_state = *state; + } + + if (persist_state->view_y >= max_y) persist_state->view_y = max_y; + if (persist_state->view_y < 0) persist_state->view_y = 0; + + return result; +} + +internal i32 +step_draw_adjusting(Color_View *color_view, i32_Rect rect, View_Message message, + Render_Target *target, Input_Summary *user_input){ + Style *style = color_view->main_style; + i32 result = 0; + + if (message != VMSG_DRAW && message != VMSG_STEP) return result; + + Color_UI ui; + ui.state = ui_state_init(&color_view->state, target, user_input, + style, color_view->working_set, (message == VMSG_STEP)); + + begin_layout(&ui.layout, rect); + + ui.fonts = color_view->fonts; + ui.highlight = color_view->highlight; + ui.color = color_view->color; + ui.has_hover_color = 0; + ui.state.sub_id1_change = 0; + ui.hex_advance = font_get_max_width(ui.state.font, "0123456789abcdefx"); + ui.palette = color_view->palette; + ui.palette_size = color_view->palette_size; + + i32_Rect bar_rect = ui.layout.rect; + bar_rect.x0 = bar_rect.x1 - 20; + do_scroll_bar(&ui.state, bar_rect); + + ui.layout.y -= FLOOR32(color_view->state.view_y); + ui.layout.rect.x1 -= 20; + + if (!ui.state.input_stage) draw_push_clip(target, ui.layout.rect); + if (do_button(-1, &ui.state, &ui.layout, "Back to Library")){ + color_view->mode = CV_MODE_LIBRARY; + ui.state.view_y = 0; + } + + do_style_name(&ui); + do_font_switch(&ui); + + do_color_adjuster(&ui, &style->main.back_color, + style->main.default_color, style->main.back_color, + "Background"); + do_color_adjuster(&ui, &style->main.margin_color, + style->main.default_color, style->main.margin_color, + "Margin"); + do_color_adjuster(&ui, &style->main.margin_hover_color, + style->main.default_color, style->main.margin_hover_color, + "Margin Hover"); + do_color_adjuster(&ui, &style->main.margin_active_color, + style->main.default_color, style->main.margin_active_color, + "Margin Active"); + + do_color_adjuster(&ui, &style->main.cursor_color, + style->main.at_cursor_color, style->main.cursor_color, + "Cursor"); + do_color_adjuster(&ui, &style->main.at_cursor_color, + style->main.at_cursor_color, style->main.cursor_color, + "Text At Cursor"); + do_color_adjuster(&ui, &style->main.mark_color, + style->main.mark_color, style->main.back_color, + "Mark"); + + do_color_adjuster(&ui, &style->main.highlight_color, + style->main.at_highlight_color, style->main.highlight_color, + "Highlight"); + do_color_adjuster(&ui, &style->main.at_highlight_color, + style->main.at_highlight_color, style->main.highlight_color, + "Text At Highlight"); + + do_color_adjuster(&ui, &style->main.default_color, + style->main.default_color, style->main.back_color, + "Text Default"); + do_color_adjuster(&ui, &style->main.comment_color, + style->main.comment_color, style->main.back_color, + "Comment"); + do_color_adjuster(&ui, &style->main.keyword_color, + style->main.keyword_color, style->main.back_color, + "Keyword"); + do_color_adjuster(&ui, &style->main.str_constant_color, + style->main.str_constant_color, style->main.back_color, + "String Constant"); + do_color_adjuster(&ui, &style->main.char_constant_color, + style->main.char_constant_color, style->main.back_color, + "Character Constant"); + do_color_adjuster(&ui, &style->main.int_constant_color, + style->main.int_constant_color, style->main.back_color, + "Integer Constant"); + do_color_adjuster(&ui, &style->main.float_constant_color, + style->main.float_constant_color, style->main.back_color, + "Float Constant"); + do_color_adjuster(&ui, &style->main.bool_constant_color, + style->main.bool_constant_color, style->main.back_color, + "Boolean Constant"); + do_color_adjuster(&ui, &style->main.preproc_color, + style->main.preproc_color, style->main.back_color, + "Preprocessor"); + do_color_adjuster(&ui, &style->main.include_color, + style->main.include_color, style->main.back_color, + "Include Constant"); + do_color_adjuster(&ui, &style->main.special_character_color, + style->main.special_character_color, style->main.back_color, + "Special Character"); + + do_color_adjuster(&ui, &style->main.highlight_junk_color, + style->main.default_color, style->main.highlight_junk_color, + "Junk Highlight"); + do_color_adjuster(&ui, &style->main.highlight_white_color, + style->main.default_color, style->main.highlight_white_color, + "Whitespace Highlight"); + + do_color_adjuster(&ui, &style->main.paste_color, + style->main.paste_color, style->main.back_color, + "Paste Color"); + + Interactive_Style *bar_style = &style->main.file_info_style; + do_color_adjuster(&ui, &bar_style->bar_color, + bar_style->base_color, bar_style->bar_color, + "Bar"); + do_color_adjuster(&ui, &bar_style->base_color, + bar_style->base_color, bar_style->bar_color, + "Bar Text"); + do_color_adjuster(&ui, &bar_style->pop1_color, + bar_style->pop1_color, bar_style->bar_color, + "Bar Pop 1"); + do_color_adjuster(&ui, &bar_style->pop2_color, + bar_style->pop2_color, bar_style->bar_color, + "Bar Pop 2"); + + i32 did_activation = 0; + if (ui_finish_frame(&color_view->state, &ui.state, &ui.layout, rect, 1, &did_activation)){ + result = 1; + } + if (did_activation){ + if (ui.has_hover_color){ + ui.color = ui.hover_color; + } + } + if (!ui.state.input_stage) draw_pop_clip(target); + color_view->color = ui.color; + + return result; +} + +internal void +update_highlighting(Color_View *color_view){ + Style *style = color_view->main_style; + File_View *file_view = color_view->hot_file_view; + if (!file_view){ + color_view->highlight = {}; + return; + } + + Editing_File *file = file_view->file; + i32 pos = view_get_cursor_pos(file_view); + char c = file->data[pos]; + + if (c == '\r'){ + color_view->highlight.ids[0] = + raw_ptr_dif(&style->main.special_character_color, style); + } + + else if (file->tokens_complete){ + Cpp_Token_Stack *tokens = &file->token_stack; + Cpp_Get_Token_Result result = cpp_get_token(tokens, pos); + Cpp_Token token = tokens->tokens[result.token_index]; + if (!result.in_whitespace){ + u32 *color = style_get_color(style, token); + color_view->highlight.ids[0] = raw_ptr_dif(color, style); + if (token.type == CPP_TOKEN_JUNK){ + color_view->highlight.ids[1] = + raw_ptr_dif(&style->main.highlight_junk_color, style); + } + else if (char_is_whitespace(c)){ + color_view->highlight.ids[1] = + raw_ptr_dif(&style->main.highlight_white_color, style); + } + else{ + color_view->highlight.ids[1] = 0; + } + } + else{ + color_view->highlight.ids[0] = 0; + color_view->highlight.ids[1] = + raw_ptr_dif(&style->main.highlight_white_color, style); + } + } + + else{ + if (char_is_whitespace(c)){ + color_view->highlight.ids[0] = 0; + color_view->highlight.ids[1] = + raw_ptr_dif(&style->main.highlight_white_color, style); + } + else{ + color_view->highlight.ids[0] = + raw_ptr_dif(&style->main.default_color, style); + color_view->highlight.ids[1] = 0; + } + } + + if (file_view->show_temp_highlight){ + color_view->highlight.ids[2] = + raw_ptr_dif(&style->main.highlight_color, style); + color_view->highlight.ids[3] = + raw_ptr_dif(&style->main.at_highlight_color, style); + } + else if (file_view->paste_effect.tick_down > 0){ + color_view->highlight.ids[2] = + raw_ptr_dif(&style->main.paste_color, style); + color_view->highlight.ids[3] = 0; + } + else{ + color_view->highlight.ids[2] = 0; + color_view->highlight.ids[3] = 0; + } +} + +internal bool32 +do_style_preview(Library_UI *ui, Style *style, i32 toggle = -1){ + bool32 result = 0; + Font *font = style->font; + i32 id; + if (style == ui->state.style) id = 2; + else id = raw_ptr_dif(style, ui->styles->styles) + 100; + + i32_Rect prect = layout_rect(&ui->layout, font->height*3 + 6); + + Widget_ID wid = make_id(&ui->state, id); + + if (ui->state.input_stage){ + if (ui_do_button_input(&ui->state, prect, wid, 0)){ + result = 1; + } + } + else{ + Render_Target *target = ui->state.target; + + u32 margin_color = style->main.margin_color; + if (is_hover(&ui->state, wid)){ + margin_color = style->main.margin_active_color; + } + + i32_Rect inner; + if (toggle != -1){ + i32_Rect toggle_box = prect; + toggle_box.x1 = toggle_box.x0 + font->height*2 + 6; + prect.x0 = toggle_box.x1; + + inner = get_inner_rect(toggle_box, 3); + draw_margin(target, toggle_box, inner, margin_color); + draw_rectangle(target, inner, style->main.back_color); + + i32 d; + d = font->height/2; + i32_Rect b; + b.x0 = (inner.x1 + inner.x0)/2 - d; + b.y0 = (inner.y1 + inner.y0)/2 - d; + b.x1 = b.x0 + font->height; + b.y1 = b.y0 + font->height; + if (toggle) draw_rectangle(target, b, margin_color); + else draw_rectangle_outline(target, b, margin_color); + } + + inner = get_inner_rect(prect, 3); + draw_margin(target, prect, inner, margin_color); + draw_rectangle(target, inner, style->main.back_color); + + i32 text_y = inner.y0; + i32 text_x = inner.x0; + text_x = draw_string(target, font, style->name.str, + text_x, text_y, style->main.default_color); + i32 font_x = (i32)(inner.x1 - font_string_width(font, font->name.str)); + if (font_x > text_x + 10) + draw_string(target, font, font->name.str, + font_x, text_y, style->main.default_color); + + text_x = inner.x0; + text_y += font->height; + text_x = draw_string(target, font, "if ", text_x, text_y, + style->main.keyword_color); + text_x = draw_string(target, font, "(x < ", text_x, text_y, + style->main.default_color); + text_x = draw_string(target, font, "0", text_x, text_y, + style->main.int_constant_color); + text_x = draw_string(target, font, ") { x = ", text_x, text_y, + style->main.default_color); + text_x = draw_string(target, font, "0", text_x, text_y, + style->main.int_constant_color); + text_x = draw_string(target, font, "; } ", text_x, text_y, + style->main.default_color); + text_x = draw_string(target, font, "// comment", text_x, text_y, + style->main.comment_color); + + text_x = inner.x0; + text_y += font->height; + text_x = draw_string(target, font, "[] () {}; * -> +-/ <>= ! && || % ^", + text_x, text_y, style->main.default_color); + } + + ui->layout.y = prect.y1; + return result; +} + +internal bool32 +do_main_file_box(UI_State *state, UI_Layout *layout, Hot_Directory *hot_directory, char *end = 0){ + bool32 result = 0; + Style *style = state->style; + Font *font = style->font; + i32_Rect box = layout_rect(layout, font->height + 2); + String *string = &hot_directory->string; + + if (state->input_stage){ + if (ui_do_file_field_input(state, hot_directory)){ + result = 1; + } + } + else{ + Render_Target *target = state->target; + u32 back = style->main.margin_color; + u32 fore = style->main.default_color; + u32 special = style->main.special_character_color; + draw_rectangle(target, box, back); + i32 x = box.x0; + x = draw_string(target, font, string->str, x, box.y0, fore); + if (end) draw_string(target, font, end, x, box.y0, special); + } + + layout->y = box.y1; + return result; +} + +internal bool32 +do_main_string_box(UI_State *state, UI_Layout *layout, String *string){ + bool32 result = 0; + Style *style = state->style; + Font *font = style->font; + i32_Rect box = layout_rect(layout, font->height + 2); + + if (state->input_stage){ + if (ui_do_line_field_input(state, string)){ + result = 1; + } + } + else{ + Render_Target *target = state->target; + u32 back = style->main.margin_color; + u32 fore = style->main.default_color; + draw_rectangle(target, box, back); + i32 x = box.x0; + x = draw_string(target, font, string->str, x, box.y0, fore); + } + + layout->y = box.y1; + return result; +} + +internal bool32 +do_list_option(i32 id, UI_State *state, UI_Layout *layout, String text){ + bool32 result = 0; + Style *style = state->style; + Font *font = style->font; + i32 character_h = font->height; + + i32_Rect box = layout_rect(layout, font->height*2); + Widget_ID wid = make_id(state, id); + + if (state->input_stage){ + if (ui_do_button_input(state, box, wid, 0)){ + result = 1; + } + } + else{ + Render_Target *target = state->target; + i32_Rect inner = get_inner_rect(box, 3); + u32 back, outline, fore, pop; + back = style->main.back_color; + fore = style->main.default_color; + pop = style->main.file_info_style.pop2_color; + if (is_hover(state, wid)) outline = style->main.margin_active_color; + else outline = style->main.margin_color; + + draw_rectangle(target, inner, back); + i32 x = inner.x0, y = box.y0 + character_h/2; + x = draw_string(target, font, text, x, y, fore); + draw_margin(target, box, inner, outline); + } + + layout->y = box.y1; + return result; +} + +#define do_list_option_lit(id,state,layout,str) do_list_option(id, state, layout, make_lit_string(str)) + +internal bool32 +do_file_option(i32 id, UI_State *state, UI_Layout *layout, String filename, bool32 is_folder, String extra){ + bool32 result = 0; + Style *style = state->style; + Font *font = style->font; + i32 character_h = font->height; + + i32_Rect box = layout_rect(layout, font->height*2); + Widget_ID wid = make_id(state, id); + + if (state->input_stage){ + if (ui_do_button_input(state, box, wid, 0)){ + result = 1; + } + } + else{ + Render_Target *target = state->target; + i32_Rect inner = get_inner_rect(box, 3); + u32 back, outline, fore, pop; + back = style->main.back_color; + fore = style->main.default_color; + pop = style->main.file_info_style.pop2_color; + if (is_hover(state, wid)) outline = style->main.margin_active_color; + else outline = style->main.margin_color; + + draw_rectangle(target, inner, back); + i32 x = inner.x0, y = box.y0 + character_h/2; + x = draw_string(target, font, filename, x, y, fore); + if (is_folder) x = draw_string(target, font, "\\", x, y, fore); + draw_string(target, font, extra, x, y, pop); + draw_margin(target, box, inner, outline); + } + + layout->y = box.y1; + return result; +} + +internal bool32 +do_file_list_box(UI_State *state, UI_Layout *layout, Hot_Directory *hot_dir, bool32 has_filter, + bool32 *new_dir, bool32 *selected, char *end){ + bool32 result = 0; + File_List *files = &hot_dir->file_list; + + if (do_main_file_box(state, layout, hot_dir, end)){ + *selected = 1; + terminate_with_null(&hot_dir->string); + } + else{ + String p4c_extension = make_lit_string("p4c"); + String message_loaded = make_lit_string(" LOADED"); + String message_unsaved = make_lit_string(" LOADED *"); + String message_unsynced = make_lit_string(" LOADED BEHIND OS"); + String message_nothing = {}; + + char front_name_space[256]; + String front_name = make_fixed_width_string(front_name_space); + get_front_of_directory(&front_name, hot_dir->string); + + Absolutes absolutes; + get_absolutes(front_name, &absolutes, 1, 1); + + char full_path_[256]; + String full_path = make_fixed_width_string(full_path_); + get_path_of_directory(&full_path, hot_dir->string); + i32 restore_size = full_path.size; + + i32 i; + File_Info *info, *end; + end = files->infos + files->count; + for (info = files->infos, i = 0; info != end; ++info, ++i){ + String filename = info->filename; + + append(&full_path, filename); + terminate_with_null(&full_path); + Editing_File *file = working_set_contains(state->working_set, full_path); + full_path.size = restore_size; + + bool8 is_folder = (info->folder != 0); + bool8 ext_match = (match(file_extension(filename), p4c_extension) != 0); + bool8 name_match = (filename_match(front_name, &absolutes, filename) != 0); + bool8 is_loaded = (file != 0); + + String message = message_nothing; + if (is_loaded){ + if (file->last_4ed_write_time != file->last_sys_write_time){ + message = message_unsynced; + } + else if (file->last_4ed_edit_time > file->last_sys_write_time){ + message = message_unsaved; + } + else{ + message = message_loaded; + } + } + + if ((is_folder || !has_filter || ext_match) && name_match){ + if (do_file_option(100+i, state, layout, filename, is_folder, message)){ + result = 1; + hot_directory_clean_end(hot_dir); + append(&hot_dir->string, filename); + if (is_folder){ + *new_dir = 1; + append(&hot_dir->string, "\\"); + } + else{ + *selected = 1; + } + terminate_with_null(&hot_dir->string); + } + } + } + } + + return result; +} + +internal bool32 +do_live_file_list_box(UI_State *state, UI_Layout *layout, Working_Set *working_set, + String *string, bool32 *selected){ + bool32 result = 0; + + if (do_main_string_box(state, layout, string)){ + *selected = 1; + terminate_with_null(string); + } + else{ + Absolutes absolutes; + get_absolutes(*string, &absolutes, 1, 1); + + i32 count = working_set->file_index_count; + Editing_File *files = working_set->files; + for (i32 i = 0; i < count; ++i){ + Editing_File *file = files + i; + + if (!file->is_dummy){ + if (filename_match(*string, &absolutes, file->live_name)){ + if (do_file_option(100+i, state, layout, file->live_name, 0, {})){ + result = 1; + *selected = 1; + copy(string, file->live_name); + terminate_with_null(string); + } + } + } + } + } + + return result; +} + +internal i32 +step_draw_library(Color_View *color_view, i32_Rect rect, View_Message message, + Render_Target *target, Input_Summary *user_input){ + i32 result = 0; + + Library_UI ui; + ui.state = ui_state_init(&color_view->state, target, user_input, + color_view->main_style, color_view->working_set, (message == VMSG_STEP)); + + ui.fonts = color_view->fonts; + ui.hot_directory = color_view->hot_directory; + ui.styles = color_view->styles; + + begin_layout(&ui.layout, rect); + + Color_View_Mode mode = color_view->mode; + + i32_Rect bar_rect = ui.layout.rect; + bar_rect.x0 = bar_rect.x1 - 20; + do_scroll_bar(&ui.state, bar_rect); + + ui.layout.y -= FLOOR32(color_view->state.view_y); + ui.layout.rect.x1 -= 20; + + if (!ui.state.input_stage) draw_push_clip(ui.state.target, ui.layout.rect); + switch (mode){ + case CV_MODE_LIBRARY: + { + do_label(&ui.state, &ui.layout, "Current Theme - Click to Edit"); + if (do_style_preview(&ui, color_view->main_style)){ + color_view->mode = CV_MODE_ADJUSTING; + color_view->state.selected = {}; + ui.state.view_y = 0; + result = 1; + } + + begin_row(&ui.layout, 3); + if (ui.state.style->name.size >= 1){ + if (do_button(-2, &ui.state, &ui.layout, "Save")){ + style_library_add(ui.styles, ui.state.style); + } + } + else{ + do_button(-2, &ui.state, &ui.layout, "~Need's Name~"); + } + if (do_button(-3, &ui.state, &ui.layout, "Import")){ + color_view->mode = CV_MODE_IMPORT_FILE; + hot_directory_clean_end(color_view->hot_directory); + hot_directory_reload(color_view->hot_directory, color_view->working_set); + } + if (do_button(-4, &ui.state, &ui.layout, "Export")){ + color_view->mode = CV_MODE_EXPORT; + hot_directory_clean_end(color_view->hot_directory); + hot_directory_reload(color_view->hot_directory, color_view->working_set); + memset(color_view->import_export_check, 0, sizeof(color_view->import_export_check)); + } + + do_label(&ui.state, &ui.layout, "Theme Library - Click to Select"); + + i32 style_count = color_view->styles->count; + Style *style = color_view->styles->styles; + for (i32 i = 0; i < style_count; ++i, ++style){ + if (do_style_preview(&ui, style)){ + style_copy(color_view->main_style, style); + result = 1; + } + } + }break; + + case CV_MODE_IMPORT_FILE: + { + do_label(&ui.state, &ui.layout, "Current Theme"); + do_style_preview(&ui, color_view->main_style); + + bool32 file_selected = 0; + + do_label(&ui.state, &ui.layout, "Import Which File?"); + begin_row(&ui.layout, 2); + if (do_button(-2, &ui.state, &ui.layout, "*.p4c only", 1, color_view->p4c_only)){ + color_view->p4c_only = !color_view->p4c_only; + } + if (do_button(-3, &ui.state, &ui.layout, "Cancel")){ + color_view->mode = CV_MODE_LIBRARY; + } + + bool32 new_dir = 0; + if (do_file_list_box(&ui.state, &ui.layout, ui.hot_directory, color_view->p4c_only, + &new_dir, &file_selected, 0)){ + result = 1; + } + + if (new_dir){ + hot_directory_reload(ui.hot_directory, ui.state.working_set); + } + if (file_selected){ + memset(&color_view->inspecting_styles, 0, sizeof(Style_Library)); + memset(color_view->import_export_check, 1, sizeof(color_view->import_export_check)); + Style *styles = color_view->inspecting_styles.styles; + i32 count, max; + max = ArrayCount(color_view->inspecting_styles.styles); + if (style_library_import((u8*)color_view->hot_directory->string.str, + ui.fonts, styles, max, &count)){ + color_view->mode = CV_MODE_IMPORT; + } + else{ + color_view->mode = CV_MODE_LIBRARY; + } + color_view->inspecting_styles.count = count; + } + }break; + + case CV_MODE_EXPORT_FILE: + { + do_label(&ui.state, &ui.layout, "Current Theme"); + do_style_preview(&ui, color_view->main_style); + + bool32 file_selected = 0; + + do_label(&ui.state, &ui.layout, "Export File Name?"); + begin_row(&ui.layout, 2); + if (do_button(-2, &ui.state, &ui.layout, "Finish Export")){ + file_selected = 1; + } + if (do_button(-3, &ui.state, &ui.layout, "Cancel")){ + color_view->mode = CV_MODE_LIBRARY; + } + + bool32 new_dir = 0; + if (do_file_list_box(&ui.state, &ui.layout, ui.hot_directory, 1, + &new_dir, &file_selected, ".p4c")){ + result = 1; + } + + if (new_dir){ + hot_directory_reload(ui.hot_directory, ui.state.working_set); + } + if (file_selected){ + i32 count = ui.styles->count; + // TODO(allen): pass the transient memory in here + Style **styles = (Style**) + system_get_memory(sizeof(Style*)*count); + Style *style = ui.styles->styles; + bool8 *export_check = color_view->import_export_check; + i32 export_count = 0; + for (i32 i = 0; i < count; ++i, ++style){ + if (export_check[i]){ + styles[export_count++] = style; + } + } + char *mem = (char*)system_get_memory(ui.hot_directory->string.size + 5); + String str = make_string(mem, 0, ui.hot_directory->string.size + 5); + copy(&str, ui.hot_directory->string); + append(&str, make_lit_string(".p4c")); + style_library_export((u8*)str.str, styles, export_count); + system_free_memory(mem); + system_free_memory(styles); + color_view->mode = CV_MODE_LIBRARY; + } + }break; + + case CV_MODE_IMPORT: + { + do_label(&ui.state, &ui.layout, "Current Theme"); + do_style_preview(&ui, color_view->main_style); + + i32 style_count = color_view->inspecting_styles.count; + Style *styles = color_view->inspecting_styles.styles; + bool8 *import_check = color_view->import_export_check; + + do_label(&ui.state, &ui.layout, "Pack"); + begin_row(&ui.layout, 2); + if (do_button(-2, &ui.state, &ui.layout, "Finish Import")){ + Style *style = styles; + for (i32 i = 0; i < style_count; ++i, ++style){ + if (import_check[i]) style_library_add(ui.styles, style); + } + color_view->mode = CV_MODE_LIBRARY; + } + if (do_button(-3, &ui.state, &ui.layout, "Cancel")){ + color_view->mode = CV_MODE_LIBRARY; + } + + Style *style = styles; + for (i32 i = 0; i < style_count; ++i, ++style){ + if (do_style_preview(&ui, style, import_check[i])){ + import_check[i] = !import_check[i]; + result = 1; + } + } + }break; + + case CV_MODE_EXPORT: + { + do_label(&ui.state, &ui.layout, "Current Theme"); + do_style_preview(&ui, color_view->main_style); + + do_label(&ui.state, &ui.layout, "Export Which Themes?"); + begin_row(&ui.layout, 2); + if (do_button(-2, &ui.state, &ui.layout, "Export")){ + color_view->mode = CV_MODE_EXPORT_FILE; + } + if (do_button(-3, &ui.state, &ui.layout, "Cancel")){ + color_view->mode = CV_MODE_LIBRARY; + } + + i32 style_count = color_view->styles->count; + Style *style = color_view->styles->styles; + bool8 *export_check = color_view->import_export_check; + for (i32 i = 0; i < style_count; ++i, ++style){ + if (do_style_preview(&ui, style, export_check[i])){ + export_check[i] = !export_check[i]; + result = 1; + } + } + }break; + } + if (!ui.state.input_stage) draw_pop_clip(ui.state.target); + + if (ui_finish_frame(&color_view->state, &ui.state, &ui.layout, rect, 1, 0)){ + result = 1; + } + + return result; +} + +internal +DO_VIEW_SIG(do_color_view){ + view->mouse_cursor_type = APP_MOUSE_CURSOR_ARROW; + Color_View *color_view = (Color_View*)view; + i32 result = 0; + + switch (color_view->mode){ + case CV_MODE_LIBRARY: + case CV_MODE_IMPORT_FILE: + case CV_MODE_EXPORT_FILE: + case CV_MODE_IMPORT: + case CV_MODE_EXPORT: + switch (message){ + case VMSG_STEP: + { + result = step_draw_library(color_view, rect, message, target, user_input); + }break; + case VMSG_DRAW: + { + step_draw_library(color_view, rect, message, target, user_input); + }break; + }break; + + case CV_MODE_ADJUSTING: + switch (message){ + case VMSG_STEP: + { + result = step_draw_adjusting(color_view, rect, message, target, user_input); + }break; + case VMSG_DRAW: + { + if (view != active){ + File_View *file_view = view_to_file_view(active); + color_view->hot_file_view = file_view; + } + if (color_view->hot_file_view && !color_view->hot_file_view->view_base.is_active){ + color_view->hot_file_view = 0; + } + update_highlighting(color_view); + step_draw_adjusting(color_view, rect, message, target, user_input); + }break; + }break; + } + + return result; +} + +internal Color_View* +color_view_init(View *view, Working_Set *working_set){ + Color_View* result = (Color_View*)view; + view->type = VIEW_TYPE_COLOR; + view->do_view = do_color_view; + result->working_set = working_set; + return result; +} + +// BOTTOM + diff --git a/4ed_command.cpp b/4ed_command.cpp new file mode 100644 index 00000000..e4ff3991 --- /dev/null +++ b/4ed_command.cpp @@ -0,0 +1,135 @@ +/* + * Mr. 4th Dimention - Allen Webster + * + * 19.08.2015 + * + * Command management functions for 4coder + * + */ + +// TOP + +typedef void (*Command_Function)(struct Command_Data *command, struct Command_Binding binding); + +struct Command_Binding{ + Command_Function function; + Custom_Command_Function *custom; + i64 hash; +}; + +struct Command_Map{ + Command_Binding vanilla_keyboard_default; + Command_Binding commands[101]; + i32 count, max; +}; + +internal void command_null(Command_Data *command); + +internal i64 +map_hash(u16 event_code, u8 modifiers){ + i64 result = (event_code << 4) | modifiers; + return result; +} + +internal bool32 +map_add(Command_Map *map, u16 event_code, u8 modifiers, Command_Function function, Custom_Command_Function *custom = 0){ + Assert(map->count * 8 < map->max * 7); + Command_Binding bind; + bind.function = function; + bind.custom = custom; + bind.hash = map_hash(event_code, modifiers); + + i32 max = map->max; + i32 index = bind.hash % max; + Command_Binding entry; + while ((entry = map->commands[index]).function && entry.hash != -1){ + if (entry.hash == bind.hash){ + return 1; + } + index = (index + 1) % max; + } + map->commands[index] = bind; + ++map->count; + return 0; +} + +internal bool32 +map_find_entry(Command_Map *map, u16 event_code, u8 modifiers, i32 *index_out){ + i64 hash = map_hash(event_code, modifiers); + i32 max = map->max; + i32 index = hash % map->max; + Command_Binding entry; + while ((entry = map->commands[index]).function){ + if (entry.hash == hash){ + *index_out = index; + return 1; + } + index = (index + 1) % max; + } + return 0; +} + +internal bool32 +map_find(Command_Map *map, u16 event_code, u8 modifiers, Command_Binding *bind_out){ + bool32 result; + i32 index; + result = map_find_entry(map, event_code, modifiers, &index); + if (result){ + *bind_out = map->commands[index]; + } + return result; +} + +internal bool32 +map_drop(Command_Map *map, u16 event_code, u8 modifiers){ + bool32 result; + i32 index; + result = map_find_entry(map, event_code, modifiers, &index); + if (result){ + map->commands[index].function = 0; + map->commands[index].hash = -1; + } + return result; +} + +internal void +map_init(Command_Map *commands){ + commands->vanilla_keyboard_default = {}; + memset(commands->commands, 0, sizeof(commands->commands)); + commands->max = ArrayCount(commands->commands); + commands->count = 0; +} + +internal void +map_get_vanilla_keyboard_default(Command_Map *map, u8 command, Command_Binding *bind_out){ + if (command == MDFR_NONE){ + *bind_out = map->vanilla_keyboard_default; + } +} + +internal Command_Binding +map_extract(Command_Map *map, Key_Single key){ + Command_Binding bind = {}; + + u8 command = MDFR_NONE; + bool32 ctrl = key.modifiers[CONTROL_KEY_CONTROL]; + bool32 alt = key.modifiers[CONTROL_KEY_ALT]; + bool32 shift = key.modifiers[CONTROL_KEY_SHIFT] && key.key.loose_keycode; + + if (shift) command |= MDFR_SHIFT; + if (ctrl) command |= MDFR_CTRL; + if (alt) command |= MDFR_ALT; + + u16 code = key.key.character_no_caps_lock; + if (code != 0) map_get_vanilla_keyboard_default(map, command, &bind); + + if (bind.function == 0){ + if (code == 0) code = key.key.keycode; + map_find(map, code, command, &bind); + } + + return bind; +} + +// BOTTOM + diff --git a/4ed_debug_view.cpp b/4ed_debug_view.cpp new file mode 100644 index 00000000..bd6375c8 --- /dev/null +++ b/4ed_debug_view.cpp @@ -0,0 +1,527 @@ +/* + * Mr. 4th Dimention - Allen Webster + * + * 13.09.2015 + * + * Internal debug view for 4coder + * + */ + +// TOP +#if FRED_INTERNAL + +enum Debug_Mode{ + DBG_MEMORY, + DBG_OS_EVENTS, + DBG_PROFILE +}; + +struct Dbg_Past_Key{ + Key_Event_Data key; + i32 frame_index; + bool8 modifiers[3]; +}; + +struct Debug_View{ + View view_base; + Font *font; + Debug_Mode mode; + Dbg_Past_Key past_keys[32]; + i32 past_key_count, past_key_pos; + i16 prev_mouse_wheel; +}; + +inline Debug_View* +view_to_debug_view(View *view){ + Assert(!view || view->type == VIEW_TYPE_DEBUG); + return (Debug_View*)view; +} + +internal i32 +draw_general_memory(Debug_View *view, i32_Rect rect, Render_Target *target, i32 y){ + Font *font = view->font; + i32 y_advance = font->height; + Bubble *sentinel = &view->view_base.general->sentinel; + + for (Bubble *bubble = sentinel->next; + bubble != sentinel; + bubble = bubble->next){ + bool32 used = (bubble->flags & MEM_BUBBLE_USED) != 0; + u32 color; + if (used) color = 0xFFFFFFFF; + else color = 0xFF00FFFF; + + char str[256]; + String s = make_fixed_width_string(str); + if (used){ + switch (bubble->type){ + case BUBBLE_BUFFER: append(&s, "buffer"); break; + case BUBBLE_STARTS: append(&s, "starts"); break; + case BUBBLE_WIDTHS: append(&s, "widths"); break; + case BUBBLE_WRAPS: append(&s, "wraps"); break; + case BUBBLE_TOKENS: append(&s, "tokens"); break; + default: append(&s, "unknown"); break; + } + } + else{ + append(&s, "unused"); + } + append(&s, " "); + append_int_to_str(bubble->size, &s); + terminate_with_null(&s); + + draw_string(target, font, str, rect.x0, y, color); + y += y_advance; + + Bubble *next = bubble->next; + if (next != sentinel){ + u8 *end_ptr = (u8*)(bubble + 1) + bubble->size; + u8 *next_ptr = (u8*)(next); + if (end_ptr != next_ptr){ + color = 0xFFFF0000; + s = make_fixed_width_string(str); + append(&s, "discontinuity"); + terminate_with_null(&s); + + draw_string(target, font, str, rect.x0, y, color); + y += y_advance; + } + } + } + + return y; +} + +internal i32 +draw_system_memory(Debug_View *view, i32_Rect rect, Render_Target *target, i32 y){ + Font *font = view->font; + i32 y_advance = font->height; + Bubble *sentinel = INTERNAL_system_sentinel(); + + for (Bubble *bubble = sentinel->next; + bubble != sentinel; + bubble = bubble->next){ + Sys_Bubble *sysb = (Sys_Bubble*)bubble; + u32 color = 0xFFFFFFFF; + + char str[256]; + String s = make_fixed_width_string(str); + + append(&s, sysb->file_name); + append(&s, " "); + append_int_to_str(sysb->line_number, &s); + append(&s, " "); + append_int_to_str(bubble->size, &s); + terminate_with_null(&s); + + draw_string(target, font, str, rect.x0, y, color); + + y += y_advance; + } + + return y; +} + +internal void +draw_background_threads(Debug_View *view, i32_Rect rect, Render_Target *target){ + i32 pending; + bool8 running[4]; + INTERNAL_get_thread_states(BACKGROUND_THREADS, running, &pending); + + i32 box_size = 30; + + i32_Rect trect; + trect.x0 = rect.x1 - box_size; + trect.y0 = rect.y0; + trect.x1 = rect.x1; + trect.y1 = rect.y0 + box_size; + + u32 light = 0xFF606060; + for (i32 i = 0; i < 4; ++i){ + u32 color; + if (running[i]) color = 0xFF9090FF; + else color = 0xFF101010; + draw_rectangle(target, trect, color); + draw_rectangle_outline(target, trect, light); + trect.x0 -= box_size; + trect.x1 -= box_size; + } + + char str[256]; + String s = make_fixed_width_string(str); + append_int_to_str(pending, &s); + terminate_with_null(&s); + draw_string(target, view->font, str, trect.x1, trect.y1, light); +} + +struct Dbg_Modifier{ + char *name; + u8 modifier; +}; +internal void +draw_modifiers(Debug_View *view, Render_Target *target, + bool8 *modifiers, u32 on_color, u32 off_color, i32 *x, i32 y){ + persist Dbg_Modifier dm[] = { + {"CTRL", CONTROL_KEY_CONTROL}, + {"ALT", CONTROL_KEY_ALT}, + {"SHIFT", CONTROL_KEY_SHIFT} + }; + for (i32 i = 0; i < CONTROL_KEY_COUNT; ++i){ + Dbg_Modifier m = dm[i]; + u32 color; + + if (modifiers[m.modifier]) color = on_color; + else color = off_color; + + *x = draw_string(target, view->font, m.name, *x, y, color); + *x += 5; + } +} + +internal i32 +draw_key_event(Debug_View *view, Render_Target *target, + Dbg_Past_Key *key, i32 x, i32 y, u32 on_color, u32 off_color){ + Font *font = view->font; + draw_modifiers(view, target, key->modifiers, + on_color, off_color, &x, y); + + if (font->glyphs[key->key.character].exists){ + char c[2]; + c[0] = (char)key->key.character; + c[1] = 0; + x = draw_string(target, font, c, x, y, on_color); + } + else{ + char c[10] = {}; + String str = make_fixed_width_string(c); + append(&str, "\\"); + append_int_to_str(key->key.keycode, &str); + terminate_with_null(&str); + x = draw_string(target, font, c, x, y, on_color); + } + + return x; +} + +internal void +draw_os_events(Debug_View *view, i32_Rect rect, Render_Target *target, + Input_Summary *active_input){ + persist i32 max_past = ArrayCount(view->past_keys); + + i32 x, y, max_x, max_y; + x = rect.x0; + y = rect.y0; + + Font *font = view->font; + + draw_modifiers(view, target, active_input->keys.modifiers, + 0xFFFFFFFF, 0xFF444444, &x, y); + max_x = x; + x = rect.x0; + y += font->height; + + for (i32 j = 0; j < view->past_key_count; ++j){ + Dbg_Past_Key *key = view->past_keys + j; + u32 on_color, off_color; + + switch ((view->past_key_pos - j - 1 + max_past*2) % max_past){ + case 0: on_color = 0xFFAAAAFF; off_color = 0xFF505088; break; + case 1: on_color = 0xFF9999CC; off_color = 0xFF404077; break; + case 2: on_color = 0xFF8888AA; off_color = 0xFF303066; break; + default: on_color = 0xFF888888; off_color = 0xFF303030; break; + } + + x = draw_key_event(view, target, key, x, y, on_color, off_color); + + if (max_x < x) max_x = x; + x = rect.x0; + y += font->height; + } + + i32_Rect mrect = rect; + mrect.x0 = max_x + 1; + + max_y = y; + x = mrect.x0; + y = mrect.y0; + + { + u32 color; + if (active_input->mouse.out_of_window){ + color = 0xFFFF0000; + draw_string(target, font, "OUT", x, y, color); + } + else{ + color = 0xFF008800; + draw_string(target, font, "IN", x, y, color); + } + y += font->height; + + char c[16]; + String s = make_fixed_width_string(c); + append_int_to_str(active_input->mouse.mx, &s); + append(&s, ", "); + append_int_to_str(active_input->mouse.my, &s); + terminate_with_null(&s); + draw_string(target, font, c, x, y, color); + y += font->height; + + u32 btn_color; + if (active_input->mouse.l) btn_color = color; + else btn_color = 0xFF444444; + x = draw_string(target, font, "L ", x, y, btn_color); + + if (active_input->mouse.r) btn_color = color; + else btn_color = 0xFF444444; + x = draw_string(target, font, "R", x, y, btn_color); + + x = mrect.x0; + y += font->height; + + s = make_fixed_width_string(c); + append_int_to_str(view->prev_mouse_wheel, &s); + terminate_with_null(&s); + + if (active_input->mouse.wheel_used) btn_color = color; + else btn_color = 0xFF444444; + draw_string(target, font, c, x, y, btn_color); + + y += font->height; + } +} + +internal i32 +draw_profile_frame(Render_Target *target, Profile_Frame *frame, + i32 x, i32 top, i32 bottom, i32 goal, Input_Summary *active_input){ + i32 result = -1; + + persist u32 colors[] = { + 0x80C06000, + 0x8000C060, + 0x806000C0, + 0x8060C000, + 0x800060C0, + 0x80C00060, + }; + + persist i32 color_max = ArrayCount(colors); + Mouse_Summary *mouse = &active_input->mouse; + + i32 count = frame->events.count; + Debug_Event *events = frame->events.e; + + i32 i; + for (i = 0; i < count && events[i].type == DBGEV_START; ++i){ + i64 start = events[i++].time; + i64 end = events[i].time; + + real32 rtop = unlerp(0, (real32)end, FRAME_TIME); + real32 rbot = unlerp(0, (real32)start, FRAME_TIME); + + rtop = lerp((real32)bottom, rtop, (real32)goal); + rbot = lerp((real32)bottom, rbot, (real32)goal); + + i32_Rect r = i32R(x, (i32)rtop, x+5, (i32)rbot); + draw_rectangle(target, r, colors[events[i].event_index % color_max]); + if (hit_check(mouse->mx, mouse->my, r)) result = (i - 1); + } + + { + real32 rtop = unlerp(0, (real32)frame->dbg_procing_end, FRAME_TIME); + real32 rbot = unlerp(0, (real32)frame->dbg_procing_start, FRAME_TIME); + + rtop = lerp((real32)bottom, rtop, (real32)goal); + rbot = lerp((real32)bottom, rbot, (real32)goal); + + i32_Rect r = i32R(x, (i32)rtop, x+5, (i32)rbot); + draw_rectangle(target, r, 0xFF808080); + } + + for (; i < count; ++i){ + + Assert(events[i].type == DBGEV_MOMENT); + + real32 ry = unlerp(0, (real32)events[i].time, FRAME_TIME); + ry = lerp((real32)bottom, ry, (real32)goal); + + i32_Rect r = i32R(x-1, (i32)ry, x+6, (i32)ry+1); + draw_rectangle(target, r, 0xFFFFFFFF); + if (hit_check(mouse->mx, mouse->my, r)) result = i; + } + + return result; +} + +internal void +draw_profile(Debug_View *view, i32_Rect rect, Render_Target *target, Input_Summary *active_input){ + i32 j = (INTERNAL_frame_index % 30); + + i32 event_index = -1; + i32 frame_index = -1; + + i32 target_time = (rect.y0 + rect.y1)/2; + + i32 x = rect.x0; + for (i32 i = 0; i < PAST_PROFILE_COUNT; ++i){ + Profile_Frame *frame = past_frames + j; + i32 s = draw_profile_frame(target, frame, x, rect.y0, rect.y1, target_time, active_input); + if (s != -1){ + event_index = s; + frame_index = j; + } + x += 10; + j = ((j+1) % PAST_PROFILE_COUNT); + } + + draw_rectangle(target, i32R(rect.x0, target_time, rect.x1, target_time + 1), 0xFFFFFFFF); + + char c[200]; + + if (frame_index != -1){ + Profile_Frame *frame = past_frames + frame_index; + Debug_Event *events = frame->events.e; + Debug_Event *event = events + event_index; + + Font *font = view->font; + + u32 color = 0xFFFFFFFF; + + i32 x, y; + x = rect.x0; + y = rect.y0; + + String s = make_fixed_width_string(c); + append(&s, event->name); + append(&s, ": "); + + Assert(event->type == DBGEV_START || event->type == DBGEV_MOMENT); + if (event->type == DBGEV_START){ + Debug_Event *next_event = event + 1; + Assert(next_event->type == DBGEV_END); + append_int_to_str((i32)(next_event->time - event->time), &s); + } + else{ + append_int_to_str((i32)event->time, &s); + } + terminate_with_null(&s); + draw_string(target, font, c, x, y, color); + y += font->height; + + if (frame->first_key != -1){ + Dbg_Past_Key *key = view->past_keys + frame->first_key; + Dbg_Past_Key *end_key = view->past_keys + ArrayCount(view->past_keys); + while (key->frame_index == frame->index){ + draw_key_event(view, target, key, + x, y, 0xFFFFFFFF, 0xFF808080); + y += font->height; + ++key; + if (key == end_key) key = view->past_keys; + } + } + + i32 count = frame->events.count; + for (i32 i = 0; i < count; ++i){ + if (events[i].type == DBGEV_START) ++i; + else{ + s = make_fixed_width_string(c); + append(&s, events[i].name); + append(&s, ": "); + append_int_to_str((i32)events[i].time, &s); + terminate_with_null(&s); + draw_string(target, font, c, x, y, color); + y += font->height; + } + } + } +} + +internal i32 +draw_debug_view(Debug_View *view, i32_Rect rect, Render_Target *target, + Input_Summary *active_input){ + i32 result = 0; + + draw_rectangle(target, rect, 0xFF000000); + + switch (view->mode){ + case DBG_MEMORY: + { + i32 y = rect.y0; + y = draw_general_memory(view, rect, target, y); + draw_rectangle(target, i32R(rect.x0, y, rect.x1, y+2), 0xFF222222); + y += 2; + y = draw_system_memory(view, rect, target, y); + }break; + case DBG_OS_EVENTS: + { + draw_os_events(view, rect, target, active_input); + }break; + case DBG_PROFILE: + { + draw_background_threads(view, rect, target); + draw_profile(view, rect, target, active_input); + }break; + } + return result; +} + +internal void +step_debug_view(Debug_View *view, i32_Rect rect, Render_Target *target, + Input_Summary *active_input){ + persist i32 max_past = ArrayCount(view->past_keys); + + bool8 *modifiers = active_input->keys.modifiers; + for (i32 i = 0; i < active_input->keys.count; ++i){ + i32 this_index = view->past_key_pos; + Dbg_Past_Key *past_key = view->past_keys + view->past_key_pos; + ++view->past_key_pos; + view->past_key_pos = view->past_key_pos % max_past; + + past_key->key = active_input->keys.keys[i]; + past_key->modifiers[0] = modifiers[0]; + past_key->modifiers[1] = modifiers[1]; + past_key->modifiers[2] = modifiers[2]; + + if (INTERNAL_updating_profile){ + past_key->frame_index = INTERNAL_frame_index; + if (profile_frame.first_key == -1){ + profile_frame.first_key = this_index; + } + } + else{ + past_key->frame_index = -1; + } + + if (view->past_key_count < max_past) ++view->past_key_count; + } + + if (active_input->mouse.wheel_used) + view->prev_mouse_wheel = active_input->mouse.wheel_amount; +} + +internal +DO_VIEW_SIG(do_debug_view){ + view->mouse_cursor_type = APP_MOUSE_CURSOR_ARROW; + Debug_View *debug_view = (Debug_View*)view; + i32 result = 0; + + switch (message){ + case VMSG_RESIZE: break; + case VMSG_STYLE_CHANGE: break; + case VMSG_STEP: step_debug_view(debug_view, rect, target, active_input); result = 1; break; + case VMSG_DRAW: draw_debug_view(debug_view, rect, target, active_input); break; + case VMSG_FREE: break; + } + + return result; +} + +internal Debug_View* +debug_view_init(View *view){ + Debug_View *result = (Debug_View*)view; + view->type = VIEW_TYPE_DEBUG; + view->do_view = do_debug_view; + return result; +} + +#endif +// BOTTOM diff --git a/4ed_file_view.cpp b/4ed_file_view.cpp new file mode 100644 index 00000000..1933ca7a --- /dev/null +++ b/4ed_file_view.cpp @@ -0,0 +1,3244 @@ +/* + * Mr. 4th Dimention - Allen Webster + * + * 19.08.2015 + * + * File editing view for 4coder + * + */ + +// TOP + +struct Cursor_Data{ + i32 pos; +}; + +enum Endline_Mode{ + ENDLINE_RN_COMBINED, + ENDLINE_RN_SEPARATE, + ENDLINE_RN_SHOWALLR +}; + +struct Editing_File{ + i32 size, max_size; + u8 *data; + + i32 line_count, line_max; + i32 *line_starts; + + Font *font; + i32 width_count, width_max; + real32 *line_width; + + Endline_Mode endline_mode; + Cursor_Data cursor; + bool32 is_dummy; + + char source_path_[256]; + char live_name_[256]; + char extension_[16]; + String source_path; + String live_name; + String extension; + + Cpp_Token_Stack token_stack; + bool32 tokens_complete; + bool32 tokens_exist; + bool32 still_lexing; + u32 lex_job; + + u64 last_4ed_write_time; + u64 last_4ed_edit_time; + u64 last_sys_write_time; +}; + +struct File_Table_Entry{ + String name; + u32 hash; + i32 index; +}; + +struct File_Table{ + File_Table_Entry *table; + i32 count, max; +}; + +internal u32 +get_file_hash(String name){ + u32 x = 5381; + int i = 0; + char c; + while (i < name.size){ + c = name.str[i++]; + x = ((x << 5) + x) + c; + } + return x; +} + +internal bool32 +table_add(File_Table *table, String name, i32 index){ + Assert(table->count * 3 < table->max * 2); + + File_Table_Entry entry, e; + i32 i; + + entry.name = name; + entry.index = index; + entry.hash = get_file_hash(name); + i = entry.hash % table->max; + while ((e = table->table[i]).name.str){ + if (e.hash == entry.hash && match(e.name, entry.name)){ + return 1; + } + i = (i + 1) % table->max; + } + table->table[i] = entry; + ++table->count; + + return 0; +} + +internal bool32 +table_find_pos(File_Table *table, String name, i32 *index){ + File_Table_Entry e; + i32 i; + u32 hash; + + hash = get_file_hash(name); + i = hash % table->max; + while ((e = table->table[i]).name.size){ + if (e.name.str && e.hash == hash && match(e.name, name)){ + *index = i; + return 1; + } + i = (i + 1) % table->max; + } + + return 0; +} + +inline bool32 +table_find(File_Table *table, String name, i32 *index){ + i32 pos; + bool32 r = table_find_pos(table, name, &pos); + if (r) *index = table->table[pos].index; + return r; +} + +inline bool32 +table_remove(File_Table *table, String name){ + i32 pos; + bool32 r = table_find_pos(table, name, &pos); + if (r){ + table->table[pos].name.str = 0; + --table->count; + } + return r; +} + +struct Working_Set{ + Editing_File *files; + i32 file_index_count, file_max_count; + + File_Table table; + + String clipboards[64]; + i32 clipboard_size, clipboard_max_size; + i32 clipboard_current, clipboard_rolling; +}; + +struct Text_Effect{ + i32 start, end; + u32 color; + i32 tick_down, tick_max; +}; + +struct View_Cursor_Data{ + i32 pos; + i32 line, character; + real32 unwrapped_x, unwrapped_y; + real32 wrapped_x, wrapped_y; +}; + +struct File_View_Mode{ + bool32 rewrite; +}; + +enum File_View_State{ + FVIEW_STATE_EDIT, + FVIEW_STATE_SEARCH, + FVIEW_STATE_GOTO_LINE, + // never below this + FVIEW_STATE_COUNT +}; + +struct Incremental_Search{ + String str; + bool32 reverse; + i32 pos; +}; + +struct File_View{ + View view_base; + + Editing_File *file; + Style *style; + + i32 font_advance; + i32 font_height; + + File_View_State state; + View_Cursor_Data cursor; + i32 mark; + real32 scroll_y, target_y, vel_y; + real32 scroll_x, target_x, vel_x; + real32 preferred_x; + View_Cursor_Data scroll_y_cursor; + union{ + Incremental_Search isearch; + struct{ + String str; + } gotoline; + }; + + View_Cursor_Data temp_highlight; + i32 temp_highlight_end_pos; + bool32 show_temp_highlight; + + File_View_Mode mode, next_mode; + bool32 unwrapped_lines; + bool32 show_whitespace; + + i32 line_count, line_max; + real32 *line_wrap_y; + + Text_Effect paste_effect; +}; + +struct Range{ + union{ + struct{ + i32 smaller, larger; + }; + struct{ + i32 start, end; + }; + }; + bool32 swapped; +}; + +inline File_View* +view_to_file_view(View *view){ + File_View* result = 0; + if (view && view->type == VIEW_TYPE_FILE){ + result = (File_View*)view; + } + return result; +} + +internal Range +get_range(i32 a, i32 b){ + Range result = {}; + if (a < b){ + result.smaller = a; + result.larger = b; + } + else{ + result.smaller = b; + result.larger = a; + result.swapped = 1; + } + return result; +} + +internal Range +range_adjust_to_left(Range range, u8 *data){ + Range result = range; + if (result.smaller > 0 && + data[result.smaller] == '\n' && + data[result.smaller-1] == '\r'){ + --result.smaller; + } + if (data[result.larger] == '\n' && + data[result.larger-1] == '\r'){ + --result.larger; + } + return result; +} + +internal i32 +pos_adjust_to_left(i32 pos, u8 *data){ + if (pos > 0 && + data[pos] == '\n' && + data[pos-1] == '\r'){ + --pos; + } + return pos; +} + +internal i32 +pos_adjust_to_self(i32 pos, u8 *data, i32 size){ + if (pos+1 < size && + data[pos] == '\r' && + data[pos+1] == '\n'){ + ++pos; + } + return pos; +} + +internal i32 +pos_universal_fix(i32 pos, u8 *data, i32 size, Endline_Mode mode){ + if (mode == ENDLINE_RN_COMBINED){ + pos = pos_adjust_to_self(pos, data, size); + } + return pos; +} + +inline i32 +starts_new_line(u8 character, Endline_Mode mode){ + return (character == '\n' || (mode == ENDLINE_RN_SEPARATE && character == '\r')); +} + +inline void +buffer_init_strings(Editing_File *file){ + file->source_path = make_fixed_width_string(file->source_path_); + file->live_name = make_fixed_width_string(file->live_name_); + file->extension = make_fixed_width_string(file->extension_); +} + +inline void +buffer_set_name(Editing_File *file, u8 *filename){ + String f, ext; + f = make_string_slowly((char*)filename); + copy_checked(&file->source_path, f); + file->live_name = make_fixed_width_string(file->live_name_); + get_front_of_directory(&file->live_name, f); + ext = file_extension(f); + copy(&file->extension, ext); +} + +inline void +buffer_synchronize_times(Editing_File *file, u8 *filename){ + Time_Stamp stamp = system_file_time_stamp(filename); + if (stamp.success){ + file->last_4ed_write_time = stamp.time; + file->last_4ed_edit_time = stamp.time; + file->last_sys_write_time = stamp.time; + } +} + +inline bool32 +buffer_save(Editing_File *file, u8 *filename){ + bool32 result; + result = system_save_file(filename, file->data, file->size); + buffer_synchronize_times(file, filename); + return result; +} + +inline bool32 +buffer_save_and_set_names(Editing_File *file, u8 *filename){ + bool32 result = 0; + if (buffer_save(file, filename)){ + result = 1; + buffer_set_name(file, filename); + } + return result; +} + +internal i32 +buffer_count_newlines(Editing_File *file, i32 start, i32 end){ + i32 count = 0; + + u8 *data = file->data; + Endline_Mode end_mode = file->endline_mode; + + for (i32 i = start; i < end; ++i){ + bool32 new_line = 0; + switch(data[i]){ + case '\n': + { + new_line = 1; + }break; + case '\r': + { + if (end_mode == ENDLINE_RN_SEPARATE){ + new_line = 1; + } + }break; + } + count += new_line; + } + + return count; +} + +internal bool32 +buffer_check_newline(Endline_Mode end_mode, u8 character){ + bool32 result = 0; + if (character == '\n' || + (end_mode == ENDLINE_RN_SEPARATE && character == '\r')){ + result = 1; + } + return result; +} + +enum File_Bubble_Type{ + BUBBLE_BUFFER = 1, + BUBBLE_STARTS, + BUBBLE_WIDTHS, + BUBBLE_WRAPS, + BUBBLE_TOKENS, + // + FILE_BUBBLE_TYPE_END, +}; + +#define GROW_FAILED 0 +#define GROW_NOT_NEEDED 1 +#define GROW_SUCCESS 2 + +internal i32 +buffer_grow_starts_as_needed(General_Memory *general, Editing_File *file, i32 additional_lines){ + bool32 result = GROW_NOT_NEEDED; + i32 max = file->line_max; + i32 count = file->line_count; + i32 target_lines = count + additional_lines; + if (target_lines > max){ + max <<= 1; + i32 *new_lines = (i32*)general_memory_reallocate(general, file->line_starts, + sizeof(i32)*count, sizeof(i32)*max, BUBBLE_STARTS); + if (new_lines){ + file->line_starts = new_lines; + file->line_max = max; + result = GROW_SUCCESS; + } + else{ + result = GROW_FAILED; + } + } + return result; +} + +internal void +buffer_measure_starts(General_Memory *general, Editing_File *file){ + ProfileMomentFunction(); + if (!file->line_starts){ + i32 max = file->line_max = Kbytes(1); + file->line_starts = (i32*)general_memory_allocate(general, max*sizeof(i32), BUBBLE_STARTS); + // TODO(allen): when unable to allocate? + } + Endline_Mode mode = file->endline_mode; + + i32 size = file->size; + u8 *data = file->data; + + file->line_count = 0; + i32 start = 0; + for (i32 i = 0; i < size; ++i){ + if (buffer_check_newline(mode, data[i])){ + // TODO(allen): when unable to grow? + buffer_grow_starts_as_needed(general, file, 1); + file->line_starts[file->line_count++] = start; + start = i + 1; + } + } + + // TODO(allen): when unable to grow? + buffer_grow_starts_as_needed(general, file, 1); + file->line_starts[file->line_count++] = start; +} + +internal real32 +measure_character(Font *font, Endline_Mode end_mode, bool32 *new_line, + u8 character, u8 next){ + real32 width = 0; + switch (character){ + case 0: + { + width = 0; + *new_line = 1; + }break; + + case '\n': + { + width = font->chardata[character].xadvance; + *new_line = 1; + }break; + + case '\r': + { + switch (end_mode){ + case ENDLINE_RN_COMBINED: + { + if (next == '\n'){ + // DO NOTHING + } + else{ + width = font->chardata['\\'].xadvance; + width += font->chardata['r'].xadvance; + } + }break; + + case ENDLINE_RN_SEPARATE: + { + width = font->chardata[character].xadvance; + *new_line = 1; + }break; + + case ENDLINE_RN_SHOWALLR: + { + width = font->chardata['\\'].xadvance; + width += font->chardata['r'].xadvance; + }break; + } + }break; + + default: + { + width = font->chardata[character].xadvance; + }break; + } + return width; +} + +internal void +buffer_measure_widths(General_Memory *general, Editing_File *file, Font *font){ + ProfileMomentFunction(); + i32 line_count = file->line_count; + if (line_count > file->width_max){ + i32 new_max = LargeRoundUp(line_count, Kbytes(1)); + if (file->line_width){ + file->line_width = (real32*) + general_memory_reallocate_nocopy(general, file->line_width, sizeof(real32)*new_max, BUBBLE_WIDTHS); + } + else{ + file->line_width = (real32*) + general_memory_allocate(general, sizeof(real32)*new_max, BUBBLE_WIDTHS); + } + file->width_max = new_max; + } + + Endline_Mode mode = file->endline_mode; + i32 size = file->size; + u8 *data = file->data; + real32 *line_widths = file->line_width; + + // TODO(allen): Does this help at all with widths??? + // probably only if we go parallel... + i32 *starts = file->line_starts; AllowLocal(starts); + + data[size] = 0; + for (i32 i = 0, j = 0; i < line_count; ++i){ + TentativeAssert(j == starts[i]); + bool32 new_line = 0; + real32 width = 0; + u8 c = data[j]; + u8 n = data[++j]; + while (new_line == 0){ + width += measure_character(font, mode, &new_line, c, n); + c = n; + n = data[++j]; + } + --j; + line_widths[i] = width; + } +} + +internal void +buffer_remeasure_starts(General_Memory *general, Editing_File *file, + i32 line_start, i32 line_end, i32 line_shift, + i32 character_shift){ + ProfileMomentFunction(); + Assert(file->line_starts); + buffer_grow_starts_as_needed(general, file, line_shift); + i32 *lines = file->line_starts; + + if (line_shift != 0){ + memmove(lines + line_end + line_shift + 1, lines + line_end + 1, + sizeof(i32)*(file->line_count - line_end - 1)); + file->line_count += line_shift; + } + + if (character_shift != 0){ + i32 line_count = file->line_count; + i32 *line = lines + line_end + 1; + for (i32 i = line_end + 1; i < line_count; ++i, ++line){ + *line += character_shift; + } + } + + i32 size = file->size; + u8 *data = file->data; + i32 char_i = lines[line_start]; + i32 line_i = line_start; + + Endline_Mode end_mode = file->endline_mode; + + i32 start = char_i; + for (; char_i <= size; ++char_i){ + u8 character; + if (char_i == size){ + character = '\n'; + } + else{ + character = data[char_i]; + } + + if (buffer_check_newline(end_mode, character)){ + if (line_i > line_end && start == lines[line_i]){ + break; + } + + lines[line_i++] = start; + start = char_i + 1; + } + } +} + +internal i32 +buffer_get_line_index(Editing_File *file, i32 pos){ + i32 result; + i32 start = 0; + i32 end = file->line_count; + i32 *lines = file->line_starts; + while (1){ + i32 i = (start + end) / 2; + if (lines[i] < pos){ + start = i; + } + else if (lines[i] > pos){ + end = i; + } + else{ + result = i; + break; + } + Assert(start < end); + if (start == end - 1){ + result = start; + break; + } + } + return result; +} + +inline i32 +view_wrapped_line_span(real32 line_width, real32 max_width){ + i32 line_count = CEIL32(line_width / max_width); + if (line_count == 0) line_count = 1; + return line_count; +} + +internal i32 +view_compute_lowest_line(File_View *view){ + i32 last_line = view->line_count - 1; + i32 lowest_line = 0; + if (last_line > 0){ + if (view->unwrapped_lines){ + lowest_line = last_line; + } + else{ + real32 wrap_y = view->line_wrap_y[last_line]; + Editing_File *file = view->file; + AllowLocal(file); + Assert(!file->is_dummy); + Style *style = view->style; + Font *font = style->font; + lowest_line = FLOOR32(wrap_y / font->height); + + real32 width = file->line_width[last_line]; + real32 max_width = view_compute_width(view); + i32 line_span = view_wrapped_line_span(width, max_width); + lowest_line += line_span - 1; + } + } + return lowest_line; +} + +internal void +view_measure_wraps(General_Memory *general, File_View *view){ + ProfileMomentFunction(); + Editing_File *file = view->file; + i32 line_count = file->line_count; + + if (view->line_max < line_count){ + i32 max = view->line_max = LargeRoundUp(line_count, Kbytes(1)); + if (view->line_wrap_y){ + view->line_wrap_y = (real32*) + general_memory_reallocate_nocopy(general, view->line_wrap_y, sizeof(real32)*max, BUBBLE_WRAPS); + } + else{ + view->line_wrap_y = (real32*) + general_memory_allocate(general, sizeof(real32)*max, BUBBLE_WRAPS); + } + } + + Font *font = view->style->font; + real32 line_height = (real32)font->height; + real32 max_width = view_compute_width(view); + real32 *line_widths = file->line_width; + real32 *line_wraps = view->line_wrap_y; + i32 y_pos = 0; + for (i32 i = 0; i < line_count; ++i){ + line_wraps[i] = y_pos*line_height; + i32 line_span = view_wrapped_line_span(line_widths[i], max_width); + y_pos += line_span; + } + + view->line_count = line_count; +} + +internal void +buffer_create_from_string(General_Memory *general, Editing_File *file, u8 *filename, Font *font, String val){ + i32 request_size = LargeRoundUp(1+val.size*2, Kbytes(256)); + u8 *data = (u8*)general_memory_allocate(general, request_size, BUBBLE_BUFFER); + + // TODO(allen): if we didn't get the memory what is going on? + // request_size too large? + // need to expand general memory allocator's base pool? + // need to release least recently used target? + Assert(data); + + *file = {}; + file->data = data; + file->size = val.size; + file->max_size = request_size; + + if (val.size > 0) memcpy(data, val.str, val.size); + data[val.size] = 0; + + buffer_synchronize_times(file, filename); + buffer_init_strings(file); + buffer_set_name(file, filename); + + buffer_measure_starts(general, file); + buffer_measure_widths(general, file, font); + file->font = font; + + if (!match(file->extension, make_lit_string("txt"))){ + file->tokens_exist = 1; + } +} + +internal bool32 +buffer_create(General_Memory *general, Editing_File *file, u8 *filename, Font *font){ + bool32 result = 0; + + File_Data raw_file = system_load_file(filename); + if (raw_file.data){ + result = 1; + String val = make_string((char*)raw_file.data, raw_file.size); + buffer_create_from_string(general, file, filename, font, val); + system_free_file(raw_file); + } + + return result; +} + +internal bool32 +buffer_create_empty(General_Memory *general, Editing_File *file, u8 *filename, Font *font){ + bool32 result = 1; + + String empty_str = {}; + buffer_create_from_string(general, file, filename, font, empty_str); + + return result; +} + +struct Get_File_Result{ + Editing_File *file; + i32 index; +}; + +internal Get_File_Result +working_set_get_available_file(Working_Set *working_set){ + Get_File_Result result = {}; + + for (i32 buffer_index = 1; buffer_index < working_set->file_max_count; ++buffer_index){ + if (buffer_index == working_set->file_index_count || + working_set->files[buffer_index].is_dummy){ + result.index = buffer_index; + result.file = working_set->files + buffer_index; + if (buffer_index == working_set->file_index_count){ + ++working_set->file_index_count; + } + break; + } + } + + Assert(result.file); + return result; +} + +internal void +buffer_close(General_Memory *general, Editing_File *file){ + if (file->still_lexing){ + system_cancel_job(BACKGROUND_THREADS, file->lex_job); + } + if (file->token_stack.tokens){ + general_memory_free(general, file->token_stack.tokens); + } + general_memory_free(general, file->data); + general_memory_free(general, file->line_starts); + general_memory_free(general, file->line_width); +} + +internal void +buffer_get_dummy(Editing_File *buffer){ + *buffer = {}; + buffer->data = (u8*)&buffer->size; + buffer->is_dummy = 1; +} + +enum Replace_Operation_Type{ + REP_UNKNOWN, + REP_REGULAR, + REP_WHITESPACE +}; + +struct Shift_Information{ + i32 start, end, amount; +}; + +internal +JOB_CALLBACK(job_full_lex){ + Editing_File *file = (Editing_File*)data[0]; + General_Memory *general = (General_Memory*)data[1]; + + Cpp_File cpp_file; + cpp_file.data = (char*)file->data; + cpp_file.size = file->size; + + Cpp_Token_Stack tokens; + tokens.tokens = (Cpp_Token*)memory->data; + tokens.max_count = memory->size / sizeof(Cpp_Token); + tokens.count = 0; + + Cpp_Lex_Data status; + status = cpp_lex_file_nonalloc(cpp_file, &tokens); + while (!status.complete){ + system_grow_thread_memory(memory); + tokens.tokens = (Cpp_Token*)memory->data; + tokens.max_count = memory->size / sizeof(Cpp_Token); + status = cpp_lex_file_nonalloc(cpp_file, &tokens, status); + } + + i32 new_max = LargeRoundUp(tokens.count, Kbytes(1)); + system_aquire_lock(FRAME_LOCK); + if (file->token_stack.tokens){ + file->token_stack.tokens = (Cpp_Token*) + general_memory_reallocate_nocopy(general, file->token_stack.tokens, new_max*sizeof(Cpp_Token), BUBBLE_TOKENS); + } + else{ + file->token_stack.tokens = (Cpp_Token*) + general_memory_allocate(general, new_max*sizeof(Cpp_Token), BUBBLE_TOKENS); + } + system_release_lock(FRAME_LOCK); + + i32 copy_amount = Kbytes(8); + i32 uncoppied = tokens.count*sizeof(Cpp_Token); + if (copy_amount > uncoppied) copy_amount = uncoppied; + + u8 *dest = (u8*)file->token_stack.tokens; + u8 *src = (u8*)tokens.tokens; + + while (uncoppied > 0){ + system_aquire_lock(FRAME_LOCK); + memcpy(dest, src, copy_amount); + system_release_lock(FRAME_LOCK); + dest += copy_amount; + src += copy_amount; + uncoppied -= copy_amount; + if (copy_amount > uncoppied) copy_amount = uncoppied; + } + + file->token_stack.count = tokens.count; + file->token_stack.max_count = new_max; + system_force_redraw(); + + // NOTE(allen): These are outside the locked section because I don't + // think getting these out of order will cause critical bugs, and I + // want to minimize what's done in locked sections. + file->tokens_complete = 1; + file->still_lexing = 0; +} + +internal void +buffer_kill_tokens(General_Memory *general, Editing_File *file){ + if (file->still_lexing){ + system_cancel_job(BACKGROUND_THREADS, file->lex_job); + } + if (file->token_stack.tokens){ + general_memory_free(general, file->token_stack.tokens); + } + file->tokens_exist = 0; + file->tokens_complete = 0; + file->token_stack = {}; +} + +internal void +buffer_first_lex_parallel(General_Memory *general, Editing_File *file){ + Assert(file->token_stack.tokens == 0); + + file->tokens_complete = 0; + file->tokens_exist = 1; + file->still_lexing = 1; + + Job_Data job; + job.callback = job_full_lex; + job.data[0] = file; + job.data[1] = general; + job.memory_request = Kbytes(64); + file->lex_job = system_post_job(BACKGROUND_THREADS, job); +} + +internal void +buffer_relex_parallel(Mem_Options *mem, Editing_File *file, + i32 start_i, i32 end_i, i32 amount){ + General_Memory *general = &mem->general; + Partition *part = &mem->part; + if (file->token_stack.tokens == 0){ + buffer_first_lex_parallel(general, file); + } + + else{ + bool32 inline_lex = !file->still_lexing; + if (inline_lex){ + Cpp_File cpp_file; + cpp_file.data = (char*)file->data; + cpp_file.size = file->size; + + Cpp_Token_Stack *stack = &file->token_stack; + + Cpp_Relex_State state = + cpp_relex_nonalloc_start(cpp_file, stack, + start_i, end_i, amount, 100); + + Temp_Memory temp = begin_temp_memory(part); + i32 relex_end; + Cpp_Token_Stack relex_space; + relex_space.count = 0; + relex_space.max_count = state.space_request; + relex_space.tokens = push_array(part, Cpp_Token, relex_space.max_count); + if (cpp_relex_nonalloc_main(&state, &relex_space, &relex_end)){ + inline_lex = 0; + } + else{ + i32 delete_amount = relex_end - state.start_token_i; + i32 shift_amount = relex_space.count - delete_amount; + + if (shift_amount != 0){ + int new_count = stack->count + shift_amount; + if (new_count > stack->max_count){ + int new_max = LargeRoundUp(new_count, Kbytes(1)); + stack->tokens = (Cpp_Token*) + general_memory_reallocate(general, stack->tokens, + stack->count*sizeof(Cpp_Token), + new_max*sizeof(Cpp_Token), BUBBLE_TOKENS); + stack->max_count = new_max; + } + + int shift_size = stack->count - relex_end; + if (shift_size > 0){ + Cpp_Token *old_base = stack->tokens + relex_end; + memmove(old_base + shift_amount, old_base, + sizeof(Cpp_Token)*shift_size); + } + + stack->count += shift_amount; + } + + memcpy(state.stack->tokens + state.start_token_i, relex_space.tokens, + sizeof(Cpp_Token)*relex_space.count); + } + + end_temp_memory(temp); + } + + if (!inline_lex){ + // TODO(allen): duplicated see REP_WHITESPACE + i32 end_token_i = cpp_get_end_token(&file->token_stack, end_i); + cpp_shift_token_starts(&file->token_stack, end_token_i, amount); + --end_token_i; + if (end_token_i >= 0){ + Cpp_Token *token = file->token_stack.tokens + end_token_i; + if (token->start < end_i && token->start + token->size > end_i){ + token->size += amount; + } + } + + file->still_lexing = 1; + + Job_Data job; + job.callback = job_full_lex; + job.data[0] = file; + job.data[1] = general; + job.memory_request = Kbytes(64); + file->lex_job = system_post_job(BACKGROUND_THREADS, job); + } + } +} + +internal bool32 +buffer_grow_as_needed(General_Memory *general, Editing_File *file, i32 additional_size){ + bool32 result = 1; + i32 target_size = file->size + additional_size; + if (target_size >= file->max_size){ + i32 request_size = LargeRoundUp(target_size*2, Kbytes(256)); + u8 *new_data = (u8*)general_memory_reallocate(general, file->data, file->size, request_size, BUBBLE_BUFFER); + if (new_data){ + new_data[file->size] = 0; + file->data = new_data; + file->max_size = request_size; + } + else{ + result = 0; + } + } + return result; +} + +// TODO(allen): Proper strings? +internal Shift_Information +buffer_replace_range(Mem_Options *mem, Editing_File *file, + i32 start, i32 end, u8 *str, i32 str_len, + Replace_Operation_Type op_type = REP_UNKNOWN){ + ProfileMomentFunction(); + General_Memory *general = &mem->general; + + Shift_Information shift = {}; + shift.start = start; + shift.end = end; + shift.amount = (str_len - (end - start)); + + // TODO(allen): Quickly figure out which op type is appropriate? + // Or just assume REGULAR, as that is always correct? + + buffer_grow_as_needed(general, file, shift.amount); + Assert(shift.amount + file->size < file->max_size); + + if (file->still_lexing){ + system_cancel_job(BACKGROUND_THREADS, file->lex_job); + } + + file->last_4ed_edit_time = system_get_now(); + + i32 size = file->size; + u8 *data = (u8*)file->data; + memmove(data + shift.end + shift.amount, data + shift.end, size - end); + file->size += shift.amount; + + memcpy(data + start, str, str_len); + + if (file->tokens_exist){ + switch (op_type){ + case REP_UNKNOWN: + case REP_REGULAR: + { + buffer_relex_parallel(mem, file, start, end, shift.amount); + }break; + + case REP_WHITESPACE: + { + // TODO(allen): duplicated see buffer_relex_parallel + i32 end_token_i = cpp_get_end_token(&file->token_stack, end); + cpp_shift_token_starts(&file->token_stack, end_token_i, shift.amount); + --end_token_i; + if (end_token_i >= 0){ + Cpp_Token *token = file->token_stack.tokens + end_token_i; + if (token->start < end && token->start + token->size > end){ + token->size += shift.amount; + } + } + }break; + } + } + + i32 line_start = buffer_get_line_index(file, shift.start); + i32 line_end = buffer_get_line_index(file, shift.end); + i32 replaced_line_count = line_end - line_start; + i32 new_line_count = buffer_count_newlines(file, shift.start, shift.start+str_len); + i32 line_shift = new_line_count - replaced_line_count; + + buffer_remeasure_starts(general, file, line_start, line_end, line_shift, shift.amount); + + // TODO(allen): Can we "remeasure" widths now!? + buffer_measure_widths(general, file, file->font); + + return shift; +} + +inline internal void +buffer_delete(Mem_Options *mem, Editing_File *file, i32 pos){ + buffer_replace_range(mem, file, pos, pos+1, 0, 0); +} + +inline internal void +buffer_delete_range(Mem_Options *mem, Editing_File *file, i32 smaller, i32 larger){ + buffer_replace_range(mem, file, smaller, larger, 0, 0); +} + +inline internal void +buffer_delete_range(Mem_Options *mem, Editing_File *file, Range range){ + buffer_replace_range(mem, file, range.smaller, range.larger, 0, 0); +} + +enum Endline_Convert_Type{ + ENDLINE_RN, + ENDLINE_N, + ENDLINE_R, + ENDLINE_ERASE, +}; + +enum Panel_Seek_Type{ + SEEK_POS, + SEEK_WRAPPED_XY, + SEEK_UNWRAPPED_XY, + SEEK_LINE_CHAR +}; + +struct Panel_Seek{ + Panel_Seek_Type type; + union{ + struct{ + i32 pos; + }; + struct{ + real32 x, y; + bool32 round_down; + }; + struct{ + i32 line, character; + }; + }; +}; + +inline internal Panel_Seek +seek_pos(i32 pos){ + Panel_Seek result = {}; + result.type = SEEK_POS; + result.pos = pos; + return result; +} + +inline internal Panel_Seek +seek_wrapped_xy(real32 x, real32 y, bool32 round_down){ + Panel_Seek result = {}; + result.type = SEEK_WRAPPED_XY; + result.x = x; + result.y = y; + result.round_down = round_down; + return result; +} + +inline internal Panel_Seek +seek_unwrapped_xy(real32 x, real32 y, bool32 round_down){ + Panel_Seek result = {}; + result.type = SEEK_UNWRAPPED_XY; + result.x = x; + result.y = y; + result.round_down = round_down; + return result; +} + +inline internal Panel_Seek +seek_line_char(i32 line, i32 character){ + Panel_Seek result = {}; + result.type = SEEK_LINE_CHAR; + result.line = line; + result.character = character; + return result; +} + +internal View_Cursor_Data +view_cursor_seek(u8 *data, i32 size, Panel_Seek seek, + Endline_Mode endline_mode, + real32 max_width, Font *font, + View_Cursor_Data hint = {}){ + View_Cursor_Data cursor = hint; + +#if 0 + if (size == 0 || (seek.type == SEEK_POS && seek.pos == cursor.pos) || + (seek.type == SEEK_WRAPPED_XY && seek.x == cursor.wrapped_x && seek.y == cursor.wrapped_y) || + (seek.type == SEEK_UNWRAPPED_XY && seek.x == cursor.unwrapped_x && seek.y == cursor.unwrapped_y) || + (seek.type == SEEK_LINE_CHAR && seek.line == cursor.line && seek.character == cursor.character)){ + if (endline_mode == ENDLINE_RN_COMBINED){ + cursor.pos = pos_adjust_to_self(cursor.pos, data, size); + } + return cursor; + } +#endif + + while (1){ + View_Cursor_Data prev_cursor = cursor; + bool32 do_newline = 0; + bool32 do_slashr = 0; + u8 c = data[cursor.pos]; + u8 next_c = (cursor.pos+1 < size)?data[cursor.pos+1]:0; + real32 cw = 0; + switch (c){ + case '\r': + { + switch (endline_mode){ + case ENDLINE_RN_COMBINED: + { + if (next_c != '\n'){ + do_slashr = 1; + } + }break; + case ENDLINE_RN_SEPARATE: + { + do_newline = 1; + }break; + case ENDLINE_RN_SHOWALLR: + { + do_slashr = 1; + }break; + } + }break; + + case '\n': + { + do_newline = 1; + }break; + + default: + { + ++cursor.character; + cw = font->chardata[c].xadvance; + }break; + } + + if (do_slashr){ + ++cursor.character; + cw = font->chardata['\\'].xadvance; + cw += font->chardata['r'].xadvance; + } + + if (cursor.wrapped_x+cw >= max_width){ + cursor.wrapped_y += font->height; + cursor.wrapped_x = 0; + prev_cursor = cursor; + } + + cursor.unwrapped_x += cw; + cursor.wrapped_x += cw; + + if (do_newline){ + ++cursor.line; + cursor.unwrapped_y += font->height; + cursor.wrapped_y += font->height; + cursor.character = 0; + cursor.unwrapped_x = 0; + cursor.wrapped_x = 0; + } + + ++cursor.pos; + + if (cursor.pos > size){ + cursor = prev_cursor; + break; + } + else{ + bool32 get_out = 0; + bool32 xy_seek = 0; + real32 x = 0, y = 0, px = 0; + switch (seek.type){ + case SEEK_POS: + if (cursor.pos > seek.pos){ + cursor = prev_cursor; + get_out = 1; + }break; + + case SEEK_WRAPPED_XY: + x = cursor.wrapped_x; px = prev_cursor.wrapped_x; + y = cursor.wrapped_y; xy_seek = 1; break; + + case SEEK_UNWRAPPED_XY: + x = cursor.unwrapped_x; px = prev_cursor.unwrapped_x; + y = cursor.unwrapped_y; xy_seek = 1; break; + + case SEEK_LINE_CHAR: + if (cursor.line == seek.line && cursor.character >= seek.character){ + get_out = 1; + } + else if (cursor.line > seek.line){ + cursor = prev_cursor; + get_out = 1; + }break; + } + if (xy_seek){ + if (seek.round_down){ + if (y > seek.y || + (y > seek.y - font->height && x > seek.x)){ + cursor = prev_cursor; + break; + } + } + else{ + if (y > seek.y){ + cursor = prev_cursor; + break; + } + else if (y > seek.y - font->height && x >= seek.x){ + real32 cur, prev; + cur = x - seek.x; + prev = seek.x - px; + if (prev < cur) cursor = prev_cursor; + break; + } + } + } + if (get_out) break; + } + } + + if (endline_mode == ENDLINE_RN_COMBINED){ + cursor.pos = pos_adjust_to_self(cursor.pos, data, size); + } + + return cursor; +} + +internal View_Cursor_Data +view_compute_cursor_from_pos(File_View *view, i32 pos){ + View_Cursor_Data result; + Editing_File *file = view->file; + Style *style = view->style; + Font *font = style->font; + + i32 *lines = file->line_starts; + + i32 line_index = buffer_get_line_index(file, pos); + + i32 line_start = lines[line_index]; + View_Cursor_Data hint; + hint.pos = line_start; + hint.line = line_index + 1; + hint.character = 1; + hint.unwrapped_y = (real32)line_index*font->height; + hint.unwrapped_x = 0; + hint.wrapped_y = view->line_wrap_y[line_index]; + hint.wrapped_x = 0; + + real32 max_width = view_compute_width(view); + result = view_cursor_seek(file->data, file->size, seek_pos(pos), + file->endline_mode, max_width, font, hint); + + return result; +} + +internal View_Cursor_Data +view_compute_cursor_from_unwrapped_xy(File_View *view, real32 seek_x, real32 seek_y, + bool32 round_down = 0){ + View_Cursor_Data result; + Editing_File *file = view->file; + Style *style = view->style; + Font *font = style->font; + + i32 line_index = FLOOR32(seek_y / font->height); + if (line_index >= file->line_count) line_index = file->line_count - 1; + + View_Cursor_Data hint; + hint.pos = file->line_starts[line_index]; + hint.line = line_index + 1; + hint.character = 1; + hint.unwrapped_y = (real32)line_index*font->height; + hint.unwrapped_x = 0; + hint.wrapped_y = view->line_wrap_y[line_index]; + hint.wrapped_x = 0; + + real32 max_width = view_compute_width(view); + result = view_cursor_seek(file->data, file->size, + seek_unwrapped_xy(seek_x, seek_y, round_down), + file->endline_mode, max_width, font, hint); + + return result; +} + +internal View_Cursor_Data +view_compute_cursor_from_wrapped_xy(File_View *view, real32 seek_x, real32 seek_y, + bool32 round_down = 0){ + View_Cursor_Data result; + Editing_File *file = view->file; + Style *style = view->style; + Font *font = style->font; + real32 line_height = (real32)font->height; + + real32 *line_wrap = view->line_wrap_y; + i32 line_index; + // NOTE(allen): binary search lines on wrapped y position + { + i32 start = 0; + i32 end = view->line_count; + while (1){ + i32 i = (start + end) / 2; + if (line_wrap[i]+line_height <= seek_y){ + start = i; + } + else if (line_wrap[i] > seek_y){ + end = i; + } + else{ + line_index = i; + break; + } + if (start >= end - 1){ + line_index = start; + break; + } + } + } + + View_Cursor_Data hint; + hint.pos = file->line_starts[line_index]; + hint.line = line_index + 1; + hint.character = 1; + hint.unwrapped_y = line_index*line_height; + hint.unwrapped_x = 0; + hint.wrapped_y = view->line_wrap_y[line_index]; + hint.wrapped_x = 0; + + real32 max_width = view_compute_width(view); + + result = view_cursor_seek(file->data, file->size, + seek_wrapped_xy(seek_x, seek_y, round_down), + file->endline_mode, max_width, font, hint); + + return result; +} + +inline View_Cursor_Data +view_compute_cursor_from_xy(File_View *view, real32 seek_x, real32 seek_y){ + View_Cursor_Data result; + if (view->unwrapped_lines){ + result = view_compute_cursor_from_unwrapped_xy(view, seek_x, seek_y); + } + else{ + result = view_compute_cursor_from_wrapped_xy(view, seek_x, seek_y); + } + return result; +} + +inline void +view_set_temp_highlight(File_View *view, i32 pos, i32 end_pos){ + view->temp_highlight = view_compute_cursor_from_pos(view, pos); + view->temp_highlight_end_pos = end_pos; + view->show_temp_highlight = 1; +} + +inline i32 +view_get_cursor_pos(File_View *view){ + i32 result; + if (view->show_temp_highlight){ + result = view->temp_highlight.pos; + } + else{ + result = view->cursor.pos; + } + return result; +} + +inline real32 +view_get_cursor_x(File_View *view){ + real32 result; + View_Cursor_Data *cursor; + if (view->show_temp_highlight){ + cursor = &view->temp_highlight; + } + else{ + cursor = &view->cursor; + } + if (view->unwrapped_lines){ + result = cursor->unwrapped_x; + } + else{ + result = cursor->wrapped_x; + } + return result; +} + +inline real32 +view_get_cursor_y(File_View *view){ + real32 result; + View_Cursor_Data *cursor; + if (view->show_temp_highlight){ + cursor = &view->temp_highlight; + } + else{ + cursor = &view->cursor; + } + if (view->unwrapped_lines){ + result = cursor->unwrapped_y; + } + else{ + result = cursor->wrapped_y; + } + return result; +} + +internal void +view_set_file(File_View *view, Editing_File *file, Style *style){ + Panel *panel = view->view_base.panel; + view->file = file; + + General_Memory *general = view->view_base.general; + Font *font = style->font; + view->style = style; + view->font_advance = font->advance; + view->font_height = font->height; + + view_measure_wraps(general, view); + + view->cursor = {}; + view->cursor = view_compute_cursor_from_pos(view, file->cursor.pos); + + real32 cursor_x, cursor_y; + real32 w, h; + real32 target_x, target_y; + + cursor_x = view_get_cursor_x(view); + cursor_y = view_get_cursor_y(view); + + w = (real32)(panel->inner.x1 - panel->inner.x0); + h = (real32)(panel->inner.y1 - panel->inner.y0); + + target_x = 0; + if (cursor_x < target_x){ + target_x = (real32)Max(0, cursor_x - w*.5f); + } + else if (cursor_x >= target_x + w){ + target_x = (real32)(cursor_x - w*.5f); + } + + target_y = (real32)FLOOR32(cursor_y - h*.5f); + if (target_y < 0) target_y = 0; + + view->target_x = target_x; + view->target_y = target_y; + view->scroll_x = target_x; + view->scroll_y = target_y; + + view->vel_y = 1.f; + view->vel_x = 1.f; +} + +struct Relative_Scrolling{ + real32 scroll_x, scroll_y; + real32 target_x, target_y; +}; + +internal Relative_Scrolling +view_get_relative_scrolling(File_View *view){ + Relative_Scrolling result; + real32 cursor_x, cursor_y; + cursor_x = view_get_cursor_x(view); + cursor_y = view_get_cursor_y(view); + result.scroll_x = cursor_x - view->scroll_x; + result.scroll_y = cursor_y - view->scroll_y; + result.target_x = cursor_x - view->target_x; + result.target_y = cursor_y - view->target_y; + return result; +} + +internal void +view_set_relative_scrolling(File_View *view, Relative_Scrolling scrolling){ + real32 cursor_x, cursor_y; + cursor_x = view_get_cursor_x(view); + cursor_y = view_get_cursor_y(view); + view->scroll_y = cursor_y - scrolling.scroll_y; + view->target_y = cursor_y - scrolling.target_y; + if (view->scroll_y < 0) view->scroll_y = 0; + if (view->target_y < 0) view->target_y = 0; +} + +inline void +view_cursor_move(File_View *view, View_Cursor_Data cursor){ + view->cursor = cursor; + view->preferred_x = view_get_cursor_x(view); + view->file->cursor.pos = view->cursor.pos; + view->show_temp_highlight = 0; +} + +inline void +view_cursor_move(File_View *view, i32 pos){ + view->cursor = view_compute_cursor_from_pos(view, pos); + view->preferred_x = view_get_cursor_x(view); + view->file->cursor.pos = view->cursor.pos; + view->show_temp_highlight = 0; +} + +inline void +view_cursor_move(File_View *view, real32 x, real32 y, bool32 round_down = 0){ + if (view->unwrapped_lines){ + view->cursor = view_compute_cursor_from_unwrapped_xy(view, x, y, round_down); + } + else{ + view->cursor = view_compute_cursor_from_wrapped_xy(view, x, y, round_down); + } + view->preferred_x = view_get_cursor_x(view); + view->file->cursor.pos = view->cursor.pos; + view->show_temp_highlight = 0; +} + +// TODO(allen): should these still be view operations? +internal i32 +view_find_end_of_line(File_View *view, i32 pos){ + Editing_File *file = view->file; + u8 *data = (u8*)file->data; + while (pos < file->size && + !starts_new_line(data[pos], file->endline_mode)){ + ++pos; + } + if (pos >= file->size){ + pos = file->size; + } + return pos; +} + +internal i32 +view_find_beginning_of_line(File_View *view, i32 pos){ + Editing_File *file = view->file; + u8 *data = (u8*)file->data; + if (pos > 0){ + --pos; + while (pos > 0 && !starts_new_line(data[pos], file->endline_mode)){ + --pos; + } + if (pos != 0){ + ++pos; + } + } + return pos; +} + +internal i32 +view_find_beginning_of_next_line(File_View *view, i32 pos){ + Editing_File *file = view->file; + u8 *data = (u8*)file->data; + while (pos < file->size && + !starts_new_line(data[pos], file->endline_mode)){ + ++pos; + } + if (pos < file->size){ + ++pos; + } + return pos; +} + +internal String* +working_set_next_clipboard_string(General_Memory *general, Working_Set *working, i32 str_size){ + String *result = 0; + 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; + } + } + result = &working->clipboards[clipboard_current]; + working->clipboard_current = clipboard_current; + working->clipboard_rolling = clipboard_current; + char *new_str; + if (result->str){ + new_str = (char*)general_memory_reallocate(general, result->str, result->size, str_size); + } + else{ + new_str = (char*)general_memory_allocate(general, str_size+1); + } + // TODO(allen): What if new_str == 0? + *result = make_string(new_str, 0, str_size); + return result; +} + +internal String* +working_set_clipboard_head(Working_Set *working){ + String *result = 0; + if (working->clipboard_size > 0){ + i32 clipboard_index = working->clipboard_current; + working->clipboard_rolling = clipboard_index; + result = &working->clipboards[clipboard_index]; + } + return result; +} + +internal String* +working_set_clipboard_roll_down(Working_Set *working){ + String *result = 0; + if (working->clipboard_size > 0){ + i32 clipboard_index = working->clipboard_rolling; + --clipboard_index; + if (clipboard_index < 0){ + clipboard_index = working->clipboard_size-1; + } + working->clipboard_rolling = clipboard_index; + result = &working->clipboards[clipboard_index]; + } + return result; +} + +inline Editing_File* +working_set_contains(Working_Set *working, String filename){ + Editing_File *result = 0; + i32 index; + if (table_find(&working->table, filename, &index)){ + result = working->files + index; + } + return result; +} + +// TODO(allen): Find a way to choose an ordering for these so it picks better first options. +internal Editing_File* +working_set_lookup_file(Working_Set *working_set, String string){ + Editing_File *file = working_set_contains(working_set, string); + + if (!file){ + i32 file_i; + i32 end = working_set->file_index_count; + file = working_set->files; + for (file_i = 0; file_i < end; ++file_i, ++file){ + if (file->live_name.str && (string.size == 0 || has_substr(file->live_name, string))){ + break; + } + } + if (file_i == end) file = 0; + } + + return file; +} + +internal void +clipboard_copy(General_Memory *general, Working_Set *working, u8 *data, Range range){ + i32 size = range.larger - range.smaller; + String *dest = working_set_next_clipboard_string(general, working, size); + copy(dest, make_string((char*)data + range.smaller, size)); + system_post_clipboard(*dest); +} + +internal void +view_endline_convert(Mem_Options *mem, File_View *view, + Endline_Convert_Type rn_to, + Endline_Convert_Type r_to, + Endline_Convert_Type n_to){ + // TODO(allen): Switch over to String + struct StrSize{ + u8 *str; + i32 size; + }; + + persist StrSize eol_strings[] = { + {(u8*)"\r\n", 2}, + {(u8*)"\n", 1}, + {(u8*)"\r", 1}, + {(u8*)0, 0} + }; + + i32 cursor, mark; + cursor = view->cursor.pos; + mark = view->mark; + + Editing_File *file = view->file; + u8 *data = (u8*)file->data; + for (int i = 0; i < file->size; ++i){ + i32 which = 0; + if (data[i] == '\r'){ + if (i+1 < file->size && data[i+1] == '\n'){ + which = 1; + } + else{ + which = 2; + } + } + else if (data[i] == '\n'){ + which = 3; + } + + // TODO(allen): Replace duplication. + switch (which){ + case 1: + { + if (rn_to != ENDLINE_RN){ + buffer_replace_range(mem, file, i, i+2, + eol_strings[rn_to].str, + eol_strings[rn_to].size, + REP_WHITESPACE); + i32 shift = eol_strings[rn_to].size - 2; + if (cursor >= i){ + cursor += shift; + } + if (mark >= i){ + mark += shift; + } + i += shift; + } + ++i; + }break; + + case 2: + { + if (r_to != ENDLINE_R){ + buffer_replace_range(mem, file, i, i+1, + eol_strings[r_to].str, + eol_strings[r_to].size, + REP_WHITESPACE); + i32 shift = eol_strings[r_to].size - 1; + if (cursor >= i){ + cursor += shift; + } + if (mark >= i){ + mark += shift; + } + i += shift; + } + }break; + + case 3: + { + if (n_to != ENDLINE_N){ + buffer_replace_range(mem, file, i, i+1, + eol_strings[n_to].str, + eol_strings[n_to].size, + REP_WHITESPACE); + i32 shift = eol_strings[n_to].size - 1; + if (cursor >= i){ + cursor += shift; + } + if (mark >= i){ + mark += shift; + } + i += shift; + } + }break; + } + } + + view->cursor = view_compute_cursor_from_pos(view, cursor); + view->mark = mark; +} + +struct Indent_Definition{ + i32 tabs, spaces; +}; + +struct Line_Hard_Start{ + bool32 line_is_blank; + i32 first_character; +}; + +internal Line_Hard_Start +buffer_find_hard_start(Editing_File *file, i32 line_start){ + Line_Hard_Start result = {}; + result.line_is_blank = 1; + + u8 *data = file->data; + + for (i32 scan = line_start; scan < file->size; ++scan){ + if (data[scan] == '\n'){ + if (scan > 0 && data[scan-1] == '\r'){ + scan -= 1; + } + if (scan < line_start){ + // TODO(allen): This seems wrong. + scan = line_start; + } + result.first_character = scan; + break; + } + else if (!char_is_whitespace(data[scan])){ + result.line_is_blank = 0; + result.first_character = scan; + break; + } + } + + return result; +} + +// NOTE(allen): Assumes that whitespace_buffer has at least +// indent.tabs*4 + indent.spaces bytes available. +internal Shift_Information +buffer_set_indent_whitespace(Mem_Options *mem, Editing_File *file, + Indent_Definition indent, + u8 *whitespace_buffer, + i32 line_start){ + i32 i = 0; + while (i < indent.tabs){ + for (i32 j = 0; j < 4; ++j) whitespace_buffer[i++] = ' '; + } + while (i < indent.tabs + indent.spaces){ + whitespace_buffer[i++] = ' '; + } + + Range leading_white; + leading_white.smaller = line_start; + Line_Hard_Start hard_start; + hard_start = buffer_find_hard_start(file, line_start); + leading_white.larger = hard_start.first_character; + + Shift_Information shift = {}; + if (leading_white.smaller < leading_white.larger){ + shift = buffer_replace_range(mem, file, + leading_white.smaller, leading_white.larger, + whitespace_buffer, i, REP_WHITESPACE); + } + + return shift; +} + +inline Indent_Definition +indent_by_width(i32 width, i32 tab_width){ + Indent_Definition indent; + indent.tabs = 0; + indent.spaces = width; + return indent; +} + +struct Nest_Level{ + i32 brace_level, paren_level, bracket_level; +}; + +struct Nest_Level_Hint{ + Nest_Level start_level; + i32 start_pos; +}; + +internal Nest_Level +buffer_compute_nest_level_raw(Editing_File *file, i32 pos, Nest_Level_Hint hint = {}){ + Nest_Level result = hint.start_level; + for (i32 i = hint.start_pos; i < pos; ++i){ + if (file->data[i] == '/' && + i+1 < file->size && file->data[i+1] == '*'){ + Seek_Result seek = seek_block_comment_end((char*)file->data, file->size, pos); + pos = seek.pos; + } + else if (file->data[i] == '/' && + i+1 < file->size && file->data[i+1] == '/'){ + Seek_Result seek = seek_unescaped_eol((char*)file->data, file->size, pos); + pos = seek.pos; + } + else if (file->data[i] == '"'){ + Seek_Result seek = seek_unescaped_delim((char*)file->data, file->size, pos, '"'); + pos = seek.pos; + } + else if (file->data[i] == '\''){ + Seek_Result seek = seek_unescaped_delim((char*)file->data, file->size, pos, '\''); + pos = seek.pos; + } + switch (file->data[i]){ + case '{': + ++result.brace_level; + break; + case '}': + if (result.brace_level > 0){ + --result.brace_level; + } + break; + case '(': + ++result.paren_level; + break; + case ')': + if (result.paren_level > 0){ + --result.paren_level; + } + break; + case '[': + ++result.bracket_level; + break; + case ']': + if (result.bracket_level > 0){ + --result.bracket_level; + } + break; + } + } + return result; +} + +internal Nest_Level +buffer_compute_nest_level_tokens(Editing_File *file, i32 pos, Nest_Level_Hint hint = {}){ + Nest_Level result = hint.start_level; + Cpp_Token_Stack token_stack = file->token_stack; + i32 start_token; + { + Cpp_Get_Token_Result get_result = cpp_get_token(&file->token_stack, hint.start_pos); + start_token = get_result.token_index; + if (get_result.in_whitespace){ + ++start_token; + } + } + for (i32 i = start_token; i < token_stack.count && token_stack.tokens[i].start < pos; ++i){ + if (token_stack.tokens[i].flags & CPP_TFLAG_PP_BODY){ + continue; + } + switch (token_stack.tokens[i].type){ + case CPP_TOKEN_BRACE_OPEN: + ++result.brace_level; + break; + case CPP_TOKEN_BRACE_CLOSE: + if (result.brace_level > 0){ + --result.brace_level; + } + break; + case CPP_TOKEN_PARENTHESE_OPEN: + ++result.paren_level; + break; + case CPP_TOKEN_PARENTHESE_CLOSE: + if (result.paren_level > 0){ + --result.paren_level; + } + break; + case CPP_TOKEN_BRACKET_OPEN: + ++result.bracket_level; + break; + case CPP_TOKEN_BRACKET_CLOSE: + if (result.bracket_level > 0){ + --result.bracket_level; + } + break; + } + } + return result; +} + +internal void +view_auto_tab(Mem_Options *mem, File_View *view, i32 start, i32 end){ + Editing_File *file = view->file; + u8 *data = (u8*)file->data; + + start = view_find_beginning_of_line(view, start); + end = view_find_end_of_line(view, end); + + i32 cursor = view->cursor.pos; + i32 mark = view->mark; + + Nest_Level_Hint hint = {}; + while (start < end){ + Line_Hard_Start hard_start; + hard_start = buffer_find_hard_start(file, start); + + i32 x_pos = 0; + + i32 read_until = hard_start.first_character; + u8 first_character = file->data[read_until]; + if (first_character == '}' || + first_character == ')' || + first_character == ']'){ + ++read_until; + } + + Nest_Level nesting; + bool32 is_label = 0; + bool32 is_continuation = 0; + bool32 is_preprocessor = 0; + bool32 is_string_continuation = 0; + AllowLocal(is_string_continuation); + // TODO(allen): Better system for finding out what extra data types a file has? + if (file->tokens_complete){ + nesting = buffer_compute_nest_level_tokens(file, read_until, hint); + + i32 colon_expect_level = 1; + + Cpp_Get_Token_Result token_get = + cpp_get_token(&file->token_stack, hard_start.first_character); + + i32 token_i; + + { + if (token_get.in_whitespace){ + token_i = -1; + } + else{ + token_i = token_get.token_index; + } + } + + while (colon_expect_level != 0){ + Cpp_Token_Type type = CPP_TOKEN_JUNK; + if (token_i >= 0 && token_i < file->token_stack.count){ + type = file->token_stack.tokens[token_i].type; + } + + if (is_keyword(type)){ + Cpp_Token *token = file->token_stack.tokens + token_i; + String lexeme = make_string((char*)file->data + token->start, token->size); + if (colon_expect_level == 1){ + if (match("case", lexeme)){ + colon_expect_level = 2; + } + else if (match("public", lexeme)){ + colon_expect_level = 3; + } + else if (match("private", lexeme)){ + colon_expect_level = 3; + } + else if (match("protected", lexeme)){ + colon_expect_level = 3; + } + } + else{ + colon_expect_level = 0; + } + } + else{ + switch (type){ + case CPP_TOKEN_IDENTIFIER: + { + colon_expect_level = 3; + }break; + + case CPP_TOKEN_COLON: + { + if (colon_expect_level == 3){ + is_label = 1; + colon_expect_level = 0; + } + }break; + + default: + { + colon_expect_level = 0; + }break; + } + } + + ++token_i; + } + + if (!token_get.in_whitespace){ + Cpp_Token *token = file->token_stack.tokens + token_get.token_index; + if (token->type == CPP_TOKEN_STRING_CONSTANT || + token->type == CPP_TOKEN_CHARACTER_CONSTANT){ + is_string_continuation = 1; + } + } + + if (!is_string_continuation){ + token_i = token_get.token_index; + bool32 in_whitespace = token_get.in_whitespace; + if (token_i >= 0){ + Cpp_Token *token = file->token_stack.tokens + token_i; + + bool32 never_continuation = 0; + if (!in_whitespace){ + switch (token->type){ + case CPP_TOKEN_BRACE_CLOSE: + case CPP_TOKEN_BRACE_OPEN: + case CPP_TOKEN_PARENTHESE_OPEN: + case CPP_TOKEN_PARENTHESE_CLOSE: + case CPP_TOKEN_COMMENT: + never_continuation = 1; + break; + } + if (token->flags & CPP_TFLAG_PP_DIRECTIVE || + token->flags & CPP_TFLAG_PP_BODY){ + never_continuation = 1; + } + } + else{ + ++token_i; + } + + if (!never_continuation){ + --token_i; + token = file->token_stack.tokens + token_i; + bool32 keep_looping = 1; + is_continuation = 1; + while (token_i >= 0 && keep_looping){ + --token_i; + keep_looping = 0; + if (token->flags & CPP_TFLAG_PP_DIRECTIVE || + token->flags & CPP_TFLAG_PP_BODY){ + keep_looping = 1; + } + else{ + switch (token->type){ + case CPP_TOKEN_BRACE_CLOSE: + case CPP_TOKEN_BRACE_OPEN: + case CPP_TOKEN_PARENTHESE_OPEN: + case CPP_TOKEN_SEMICOLON: + case CPP_TOKEN_COLON: + case CPP_TOKEN_COMMA: + is_continuation = 0; + break; + + case CPP_TOKEN_COMMENT: + case CPP_TOKEN_JUNK: + keep_looping = 1; + break; + } + } + } + if (token_i == -1){ + is_continuation = 0; + } + } + } + } + + token_i = token_get.token_index; + if (token_i >= 0){ + Cpp_Token *token = file->token_stack.tokens + token_i; + if (!token_get.in_whitespace){ + if (token->flags & CPP_TFLAG_PP_DIRECTIVE || + token->flags & CPP_TFLAG_PP_BODY){ + is_preprocessor = 1; + } + } + } + } + + else{ + nesting = buffer_compute_nest_level_raw(file, read_until, hint); + } + + hint.start_level = nesting; + hint.start_pos = read_until; + + if (is_preprocessor || is_string_continuation){ + x_pos = 0; + } + else{ + x_pos = 4*(nesting.brace_level + nesting.bracket_level + nesting.paren_level); + if (is_continuation){ + x_pos += 4; + } + if (is_label){ + x_pos -= 4; + } + if (x_pos < 0){ + x_pos = 0; + } + } + + // TODO(allen): Need big transient memory for this operation. + // NOTE(allen): This is temporary. IRL it should probably come + // from transient memory space. + u8 whitespace[200]; + + Indent_Definition indent; + // TODO(allen): Revist all this. + indent = indent_by_width(x_pos, 4); + + TentativeAssert(indent.tabs + indent.spaces < 200); + + Shift_Information shift; + shift = buffer_set_indent_whitespace(mem, file, indent, whitespace, start); + + if (hint.start_pos >= shift.start){ + hint.start_pos += shift.amount; + } + + if (cursor >= shift.start && cursor <= shift.end){ + Line_Hard_Start hard_start; + hard_start = buffer_find_hard_start(file, shift.start); + cursor = hard_start.first_character; + } + else if (cursor > shift.end){ + cursor += shift.amount; + } + if (mark >= shift.start && mark <= shift.end){ + Line_Hard_Start hard_start; + hard_start = buffer_find_hard_start(file, shift.start); + mark = hard_start.first_character; + } + else if (mark > shift.end){ + mark += shift.amount; + } + + start = view_find_beginning_of_next_line(view, start); + end += shift.amount; + } + + view_cursor_move(view, cursor); + view->mark = pos_adjust_to_self(mark, data, file->size); +} + +internal u32* +style_get_color(Style *style, Cpp_Token token){ + u32 *result; + if (token.flags & CPP_TFLAG_IS_KEYWORD){ + if (token.type == CPP_TOKEN_BOOLEAN_CONSTANT){ + result = &style->main.bool_constant_color; + } + else{ + result = &style->main.keyword_color; + } + } + else if(token.flags & CPP_TFLAG_PP_DIRECTIVE){ + result = &style->main.preproc_color; + } + else{ + switch (token.type){ + case CPP_TOKEN_COMMENT: + result = &style->main.comment_color; + break; + + case CPP_TOKEN_STRING_CONSTANT: + result = &style->main.str_constant_color; + break; + + case CPP_TOKEN_CHARACTER_CONSTANT: + result = &style->main.char_constant_color; + break; + + case CPP_TOKEN_INTEGER_CONSTANT: + result = &style->main.int_constant_color; + break; + + case CPP_TOKEN_FLOATING_CONSTANT: + result = &style->main.float_constant_color; + break; + + case CPP_TOKEN_INCLUDE_FILE: + result = &style->main.include_color; + break; + + default: + result = &style->main.default_color; + } + } + return result; +} + +internal bool32 +smooth_camera_step(real32 *target, real32 *current, real32 *vel, real32 S, real32 T){ + bool32 result = 0; + real32 targ = *target; + real32 curr = *current; + real32 v = *vel; + if (curr != targ){ + if (curr > targ - .1f && curr < targ + .1f){ + curr = targ; + v = 1.f; + } + else{ + real32 L = lerp(curr, T, targ); + + i32 sign = (targ > curr) - (targ < curr); + real32 V = curr + sign*v; + + if (sign > 0) curr = Min(L, V); + else curr = Max(L, V); + + if (curr == V){ + v *= S; + } + } + + *target = targ; + *current = curr; + *vel = v; + result = 1; + } + return result; +} + +inline real32 +view_compute_max_target_y(i32 lowest_line, i32 line_height, real32 view_height){ + real32 max_target_y = ((lowest_line+.5f)*line_height) - view_height*.5f; + return max_target_y; +} + +internal real32 +view_compute_max_target_y(File_View *view){ + i32 lowest_line = view_compute_lowest_line(view); + i32 line_height = view->style->font->height; + real32 view_height = view_compute_height(view); + real32 max_target_y = view_compute_max_target_y( + lowest_line, line_height, view_height); + return max_target_y; +} + +internal void +remeasure_file_view(View *view_, i32_Rect rect){ + File_View *view = (File_View*)view_; + Relative_Scrolling relative = view_get_relative_scrolling(view); + view_measure_wraps(view->view_base.general, view); + view_cursor_move(view, view->cursor.pos); + view->preferred_x = view_get_cursor_x(view); + view_set_relative_scrolling(view, relative); +} + +internal i32 +step_file_view(Thread_Context *thread, View *view_, i32_Rect rect, + bool32 is_active, Input_Summary *user_input){ + i32 result = 0; + File_View *view = (File_View*)view_; + Style *style = view->style; + Font *font = style->font; + + real32 line_height = (real32)font->height; + real32 cursor_y = view_get_cursor_y(view); + real32 target_y = view->target_y; + real32 max_y = view_compute_height(view) - line_height*2; + i32 lowest_line = view_compute_lowest_line(view); + real32 max_target_y = view_compute_max_target_y(lowest_line, font->height, max_y); + real32 delta_y = 3.f*line_height; + + if (user_input->mouse.wheel_used){ + real32 wheel_multiplier = 3.f; + real32 delta_target_y = delta_y*user_input->mouse.wheel_amount*wheel_multiplier; + target_y += delta_target_y; + + if (target_y < 0) target_y = 0; + if (target_y > max_target_y) target_y = max_target_y; + + real32 old_cursor_y = cursor_y; + if (cursor_y >= target_y + max_y) cursor_y = target_y + max_y; + if (cursor_y < target_y + line_height) cursor_y = target_y + line_height; + + if (cursor_y != old_cursor_y){ + view->cursor = + view_compute_cursor_from_xy(view, + view->preferred_x, + cursor_y); + } + + result = 1; + } + + while (cursor_y > target_y + max_y){ + target_y += delta_y; + } + while (cursor_y < target_y){ + target_y -= delta_y; + } + + if (target_y > max_target_y) target_y = max_target_y; + if (target_y < 0) target_y = 0; + view->target_y = target_y; + + real32 cursor_x = view_get_cursor_x(view); + real32 target_x = view->target_x; + real32 max_x = view_compute_width(view); + if (cursor_x < target_x){ + target_x = (real32)Max(0, cursor_x - max_x/2); + } + else if (cursor_x >= target_x + max_x){ + target_x = (real32)(cursor_x - max_x/2); + } + + view->target_x = target_x; + + if (smooth_camera_step(&view->target_y, &view->scroll_y, &view->vel_y, 40.f, 1.f/4.f)){ + result = 1; + } + if (smooth_camera_step(&view->target_x, &view->scroll_x, &view->vel_x, 40.f, 1.f/4.f)){ + result = 1; + } + if (view->paste_effect.tick_down > 0){ + --view->paste_effect.tick_down; + result = 1; + } + + if (is_active && user_input->mouse.press_l){ + rect.y0 += font->height + 2; + + real32 max_y = view_compute_height(view); + real32 rx = (real32)(user_input->mouse.mx - rect.x0); + real32 ry = (real32)(user_input->mouse.my - rect.y0); + + if (rx >= 0 && rx < max_x && ry >= 0 && ry < max_y){ + view_cursor_move(view, rx + view->scroll_x, ry + view->scroll_y, 1); + view->mode = {}; + } + result = 1; + } + + if (!is_active && view->state != FVIEW_STATE_EDIT){ + view->state = FVIEW_STATE_EDIT; + } + + return result; +} + +internal void +file_view_intbar(Thread_Context *thread, Render_Target *target, + Interactive_Bar *bar, File_View *view, + Editing_File *file, Style *style){ + i32 w, h; + w = bar->rect.x1 - bar->rect.x0; + h = bar->rect.y1 - bar->rect.y0; + u32 back_color = bar->style.bar_color; + draw_rectangle(target, bar->rect, back_color); + + u32 base_color = bar->style.base_color; + intbar_draw_string(target, bar, file->live_name, base_color); + intbar_draw_string(target, bar, make_lit_string(" - "), base_color); + + char line_number_space[30]; + String line_number = make_string(line_number_space, 0, 30); + append(&line_number, "L#"); + append_int_to_str(view->cursor.line, &line_number); + + intbar_draw_string(target, bar, line_number, base_color); + + switch (view->state){ + case FVIEW_STATE_EDIT: + { + if (file->last_4ed_write_time != file->last_sys_write_time){ + persist String out_of_sync = make_lit_string(" BEHIND OS"); + intbar_draw_string(target, bar, out_of_sync, bar->style.pop2_color); + } + else if (file->last_4ed_edit_time > file->last_sys_write_time){ + persist String out_of_sync = make_lit_string(" *"); + intbar_draw_string(target, bar, out_of_sync, bar->style.pop2_color); + } + }break; + + case FVIEW_STATE_SEARCH: + { + persist String search_str = make_lit_string(" I-Search: "); + persist String rsearch_str = make_lit_string(" Reverse-I-Search: "); + if (view->isearch.reverse){ + intbar_draw_string(target, bar, rsearch_str, bar->style.pop1_color); + } + else{ + intbar_draw_string(target, bar, search_str, bar->style.pop1_color); + } + intbar_draw_string(target, bar, view->isearch.str, bar->style.base_color); + }break; + + case FVIEW_STATE_GOTO_LINE: + { + persist String gotoline_str = make_lit_string(" Goto Line: "); + intbar_draw_string(target, bar, gotoline_str, bar->style.pop1_color); + intbar_draw_string(target, bar, view->isearch.str, bar->style.base_color); + }break; + } +} + +internal i32 +draw_file_view(Thread_Context *thread, View *view_, i32_Rect rect, bool32 is_active, + Render_Target *target){ + view_->mouse_cursor_type = APP_MOUSE_CURSOR_IBEAM; + File_View *view = (File_View*)view_; + Editing_File *file = view->file; + Style *style = view->style; + Font *font = style->font; + + Interactive_Bar bar; + bar.style = style->main.file_info_style; + bar.font = style->font; + bar.pos_x = (real32)rect.x0; + bar.pos_y = (real32)rect.y0; + bar.text_shift_y = 2; + bar.text_shift_x = 0; + bar.rect = rect; + bar.rect.y1 = bar.rect.y0 + font->height + 2; + rect.y0 += font->height + 2; + + i32 max_x = rect.x1 - rect.x0; + i32 max_y = rect.y1 - rect.y0 + font->height; + + if (!file || !file->data || file->is_dummy){ + Assert(!"Displaying this file is no longer allowed - 26.08.2015"); + } + + else{ + i32 size = (i32)file->size; + u8 *data = (u8*)file->data; + + View_Cursor_Data start_cursor; + start_cursor = view_compute_cursor_from_xy(view, 0, view->scroll_y); + view->scroll_y_cursor = start_cursor; + + i32 start_character = start_cursor.pos; + + real32 shift_x = rect.x0 - view->scroll_x; + real32 shift_y = rect.y0 - view->scroll_y; + if (view->unwrapped_lines){ + shift_y += start_cursor.unwrapped_y; + } + else{ + shift_y += start_cursor.wrapped_y; + } + + real32 pos_x = 0; + real32 pos_y = 0; + + Cpp_Token_Stack token_stack = file->token_stack; + u32 highlight_color = 0; + u32 main_color = style->main.default_color; + i32 token_i = 0; + bool32 tokens_use = file->tokens_complete && (file->token_stack.count > 0); + + if (tokens_use){ + Cpp_Get_Token_Result result = cpp_get_token(&token_stack, start_character); + token_i = result.token_index; + + main_color = *style_get_color(style, token_stack.tokens[token_i]); + ++token_i; + } + + for (i32 i = start_character; i <= size; ++i){ + u8 to_render, next_to_render; + if (i < size){ + to_render = data[i]; + } + else{ + to_render = 0; + } + if (i+1 < size){ + next_to_render = data[i+1]; + } + else{ + next_to_render = 0; + } + + if (!view->unwrapped_lines && pos_x + font_get_glyph_width(font, to_render) > max_x){ + pos_x = 0; + pos_y += font->height; + } + + u32 fade_color = 0xFFFF00FF; + real32 fade_amount = 0.f; + if (view->paste_effect.tick_down > 0 && + view->paste_effect.start <= i && i < view->paste_effect.end){ + fade_color = style->main.paste_color; + fade_amount = (real32)(view->paste_effect.tick_down) / view->paste_effect.tick_max; + } + + highlight_color = 0; + if (tokens_use){ + Cpp_Token current_token = token_stack.tokens[token_i-1]; + + if (token_i < token_stack.count){ + if (i >= token_stack.tokens[token_i].start){ + main_color = + *style_get_color(style, token_stack.tokens[token_i]); + current_token = token_stack.tokens[token_i]; + ++token_i; + } + else if (i >= current_token.start + current_token.size){ + main_color = 0xFFFFFFFF; + } + } + + if (current_token.type == CPP_TOKEN_JUNK && + i >= current_token.start && i <= current_token.start + current_token.size){ + highlight_color = style->main.highlight_junk_color; + } + } + if (highlight_color == 0 && view->show_whitespace && char_is_whitespace(data[i])){ + highlight_color = style->main.highlight_white_color; + } + + i32 cursor_mode = 0; + if (view->show_temp_highlight){ + if (view->temp_highlight.pos <= i && i < view->temp_highlight_end_pos){ + cursor_mode = 2; + } + } + else{ + if (view->cursor.pos == i){ + cursor_mode = 1; + } + } + + real32 to_render_width = font_get_glyph_width(font, to_render); + real32_Rect cursor_rect = + real32XYWH(shift_x + pos_x, shift_y + pos_y, + 1 + to_render_width, (real32)font->height); + + if (to_render == '\t' || to_render == 0){ + cursor_rect.x1 = cursor_rect.x0 + font->chardata[' '].xadvance; + } + + if (highlight_color != 0){ + if (file->endline_mode == ENDLINE_RN_COMBINED && + to_render == '\r' && next_to_render == '\n'){ + // DO NOTHING + // NOTE(allen): relevant durring whitespace highlighting + } + else{ + real32_Rect highlight_rect = cursor_rect; + highlight_rect.x0 += 1; + draw_rectangle(target, highlight_rect, highlight_color); + } + } + + u32 cursor_color = 0; + switch (cursor_mode){ + case 1: + cursor_color = style->main.cursor_color; break; + case 2: + cursor_color = style->main.highlight_color; break; + } + + if (is_active){ + if (cursor_color & 0xFF000000) draw_rectangle(target, cursor_rect, cursor_color); + } + else{ + if (cursor_color & 0xFF000000)draw_rectangle_outline(target, cursor_rect, cursor_color); + } + if (i == view->mark){ + draw_rectangle_outline(target, cursor_rect, style->main.mark_color); + } + + u32 char_color = main_color; + u32 special_char_color = main_color; + if (to_render == '\r'){ + special_char_color = char_color = style->main.special_character_color; + } + if (is_active){ + switch (cursor_mode){ + case 1: + char_color = style->main.at_cursor_color; break; + case 2: + special_char_color = char_color = style->main.at_highlight_color; break; + } + } + + char_color = color_blend(char_color, fade_amount, fade_color); + special_char_color = color_blend(special_char_color, fade_amount, fade_color); + + if (to_render == '\r'){ + bool32 show_slashr = 0; + switch (file->endline_mode){ + case ENDLINE_RN_COMBINED: + { + if (next_to_render != '\n'){ + show_slashr = 1; + } + }break; + + case ENDLINE_RN_SEPARATE: + { + pos_x = 0; + pos_y += font->height; + }break; + + case ENDLINE_RN_SHOWALLR: + { + show_slashr = 1; + }break; + } + + if (show_slashr){ + font_draw_glyph(target, font, '\\', + shift_x + pos_x, + shift_y + pos_y, + char_color); + pos_x += font_get_glyph_width(font, '\\'); + + real32 cw = font_get_glyph_width(font, 'r'); + draw_rectangle(target, + real32XYWH(shift_x + pos_x, + shift_y + pos_y, + cw, (real32)font->height), + highlight_color); + font_draw_glyph(target, font, 'r', + shift_x + pos_x, + shift_y + pos_y, + special_char_color); + pos_x += cw; + } + } + + else if (to_render == '\n'){ + pos_x = 0; + pos_y += font->height; + } + + else if (font->glyphs[to_render].exists){ + font_draw_glyph(target, font, to_render, + shift_x + pos_x, + shift_y + pos_y, + char_color); + pos_x += to_render_width; + } + + else{ + pos_x += to_render_width; + } + + if (pos_y > max_y){ + break; + } + } + } + + file_view_intbar(thread, target, &bar, view, file, style); + return 0; +} + +// Hot Directory + +struct Hot_Directory{ + String string; + File_List file_list; +}; + +internal void +hot_directory_init(Hot_Directory *hot_directory, String base){ + hot_directory->string = base; + hot_directory->string.str[255] = 0; + i32 dir_size = system_get_working_directory((u8*)hot_directory->string.str, + hot_directory->string.memory_size); + if (dir_size <= 0){ + dir_size = system_get_easy_directory((u8*)hot_directory->string.str); + } + hot_directory->string.size = dir_size; + append(&hot_directory->string, "\\"); +} + +internal void +hot_directory_clean_end(Hot_Directory *hot_directory){ + String *str = &hot_directory->string; + if (str->size != 0 && str->str[str->size-1] != '\\'){ + str->size = reverse_seek_slash(*str) + 1; + str->str[str->size] = 0; + } +} + +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){ + i32 comp = 0; + comp = p->folder - a->folder; + if (comp == 0) comp = compare(a->filename, p->filename); + if (comp < 0){ + Swap(*a, infos[start]); + ++start; + } + } + Swap(*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); +} + +inline void +hot_directory_fixup(Hot_Directory *hot_directory, Working_Set *working_set){ + File_List *files = &hot_directory->file_list; + if (files->count >= 2) + hot_directory_quick_sort(files->infos, 0, files->count - 1); +} + +inline void +hot_directory_set(Hot_Directory *hot_directory, String str, Working_Set *working_set){ + bool32 success = copy_checked(&hot_directory->string, str); + terminate_with_null(&hot_directory->string); + if (success){ + system_free_file_list(hot_directory->file_list); + hot_directory->file_list = system_get_files(str); + } + hot_directory_fixup(hot_directory, working_set); +} + +inline void +hot_directory_reload(Hot_Directory *hot_directory, Working_Set *working_set){ + if (hot_directory->file_list.block){ + system_free_file_list(hot_directory->file_list); + } + hot_directory->file_list = system_get_files(hot_directory->string); + hot_directory_fixup(hot_directory, working_set); +} + +struct Hot_Directory_Match{ + String filename; + bool32 is_folder; +}; + +internal bool32 +filename_match(String query, Absolutes *absolutes, String filename){ + bool32 result; + result = (query.size == 0); + if (!result) result = wildcard_match(absolutes, filename); + return result; +} + +internal Hot_Directory_Match +hot_directory_first_match(Hot_Directory *hot_directory, + String str, + bool32 include_files, + bool32 exact_match){ + Hot_Directory_Match result = {}; + + Absolutes absolutes; + if (!exact_match) + get_absolutes(str, &absolutes, 1, 1); + + File_List *files = &hot_directory->file_list; + File_Info *info, *end; + end = files->infos + files->count; + for (info = files->infos; info != end; ++info){ + String filename = info->filename; + bool32 is_match = 0; + if (exact_match){ + if (match(filename, str)) is_match = 1; + } + else{ + if (filename_match(str, &absolutes, filename)) is_match = 1; + } + + if (is_match){ + result.is_folder = info->folder; + result.filename = filename; + break; + } + } + + return result; +} + +struct Single_Line_Input_Step{ + bool32 hit_newline; + bool32 hit_ctrl_newline; + bool32 hit_a_character; + bool32 hit_backspace; + bool32 hit_esc; + bool32 made_a_change; + bool32 did_command; +}; + +enum Single_Line_Input_Type{ + SINGLE_LINE_STRING, + SINGLE_LINE_FILE +}; + +struct Single_Line_Mode{ + Single_Line_Input_Type type; + String *string; + Hot_Directory *hot_directory; + bool32 fast_folder_select; +}; + +internal Single_Line_Input_Step +app_single_line_input_core(Key_Codes *codes, Working_Set *working_set, + Key_Single key, Single_Line_Mode mode){ + Single_Line_Input_Step result = {}; + + if (key.key.keycode == codes->back){ + result.hit_backspace = 1; + if (mode.string->size > 0){ + result.made_a_change = 1; + --mode.string->size; + switch (mode.type){ + case SINGLE_LINE_STRING: + mode.string->str[mode.string->size] = 0; break; + + case SINGLE_LINE_FILE: + { + char end_character = mode.string->str[mode.string->size]; + if (char_is_slash(end_character)){ + mode.string->size = reverse_seek_slash(*mode.string) + 1; + mode.string->str[mode.string->size] = 0; + hot_directory_set(mode.hot_directory, *mode.string, working_set); + } + else{ + mode.string->str[mode.string->size] = 0; + } + }break; + } + } + } + + else if (key.key.character == '\n' || key.key.character == '\t'){ + result.made_a_change = 1; + if (key.modifiers[CONTROL_KEY_CONTROL] || + key.modifiers[CONTROL_KEY_ALT]){ + result.hit_ctrl_newline = 1; + } + else{ + result.hit_newline = 1; + if (mode.fast_folder_select){ + char front_name_space[256]; + String front_name = + make_string(front_name_space, 0, ArrayCount(front_name_space)); + get_front_of_directory(&front_name, *mode.string); + Hot_Directory_Match match; + match = hot_directory_first_match(mode.hot_directory, front_name, 1, 1); + if (!match.filename.str){ + match = hot_directory_first_match(mode.hot_directory, front_name, 1, 0); + } + if (match.filename.str){ + if (match.is_folder){ + set_last_folder(mode.string, match.filename); + hot_directory_set(mode.hot_directory, *mode.string, working_set); + result.hit_newline = 0; + } + else{ + mode.string->size = reverse_seek_slash(*mode.string) + 1; + append(mode.string, match.filename); + result.hit_newline = 1; + } + } + } + } + } + + else if (key.key.keycode == codes->esc){ + result.hit_esc = 1; + result.made_a_change = 1; + } + + else if (key.key.character){ + result.hit_a_character = 1; + if (!key.modifiers[CONTROL_KEY_CONTROL] && + !key.modifiers[CONTROL_KEY_ALT]){ + if (mode.string->size+1 < mode.string->memory_size){ + u8 new_character = (u8)key.key.character; + mode.string->str[mode.string->size] = new_character; + mode.string->size++; + mode.string->str[mode.string->size] = 0; + if (mode.type == SINGLE_LINE_FILE && char_is_slash(new_character)){ + hot_directory_set(mode.hot_directory, *mode.string, working_set); + } + result.made_a_change = 1; + } + } + else{ + result.did_command = 1; + result.made_a_change = 1; + } + } + + return result; +} + +inline Single_Line_Input_Step +app_single_line_input_step(Key_Codes *codes, Key_Single key, String *string){ + Single_Line_Mode mode = {}; + mode.type = SINGLE_LINE_STRING; + mode.string = string; + return app_single_line_input_core(codes, 0, key, mode); +} + +inline Single_Line_Input_Step +app_single_file_input_step(Key_Codes *codes, Working_Set *working_set, Key_Single key, + String *string, Hot_Directory *hot_directory, + bool32 fast_folder_select){ + Single_Line_Mode mode = {}; + mode.type = SINGLE_LINE_FILE; + mode.string = string; + mode.hot_directory = hot_directory; + mode.fast_folder_select = fast_folder_select; + return app_single_line_input_core(codes, working_set, key, mode); +} + +inline Single_Line_Input_Step +app_single_number_input_step(Key_Codes *codes, Key_Single key, String *string){ + Single_Line_Input_Step result = {}; + Single_Line_Mode mode = {}; + mode.type = SINGLE_LINE_STRING; + mode.string = string; + + char c = (char)key.key.character; + if (c == 0 || c == '\n' || char_is_numeric(c)) + result = app_single_line_input_core(codes, 0, key, mode); + return result; +} + +internal void +kill_file(General_Memory *general, Editing_File *file, Live_Views *live_set, Editing_Layout *layout){ + i32 panel_count = layout->panel_count; + Panel *panels = layout->panels, *panel; + panel = panels; + for (i32 i = 0; i < panel_count; ++i){ + View *view = panel->view; + if (view){ + View *to_kill = view; + if (view->is_minor) to_kill = view->major; + + File_View *fview = view_to_file_view(to_kill); + if (fview && fview->file == file){ + live_set_free_view(live_set, &fview->view_base); + if (to_kill == view) panel->view = 0; + else view->major = 0; + } + } + ++panel; + } + buffer_close(general, file); + buffer_get_dummy(file); +} + +internal void +command_search(Command_Data*,Command_Binding); +internal void +command_rsearch(Command_Data*,Command_Binding); + +internal +HANDLE_COMMAND_SIG(handle_command_file_view){ + File_View *file_view = (File_View*)(view); + switch (file_view->state){ + case FVIEW_STATE_EDIT: + { + file_view->next_mode = {}; + if (binding.function) binding.function(command, binding); + file_view->mode = file_view->next_mode; + }break; + + case FVIEW_STATE_SEARCH: + { + String *string = &file_view->isearch.str; + Single_Line_Input_Step result = + app_single_line_input_step(codes, key, string); + + if (result.made_a_change || + binding.function == command_search || + binding.function == command_rsearch){ + Editing_File *file = file_view->file; + bool32 step_forward = 0; + bool32 step_backward = 0; + + if (binding.function == command_search){ + step_forward = 1; + } + if (binding.function == command_rsearch){ + step_backward = 1; + } + + i32 start_pos = file_view->isearch.pos; + if (step_forward){ + if (file_view->isearch.reverse){ + start_pos = file_view->temp_highlight.pos - 1; + file_view->isearch.pos = start_pos; + file_view->isearch.reverse = 0; + } + } + if (step_backward){ + if (!file_view->isearch.reverse){ + start_pos = file_view->temp_highlight.pos + 1; + file_view->isearch.pos = start_pos; + file_view->isearch.reverse = 1; + } + } + + String file_string = make_string((char*)file->data, file->size); + i32 pos; + if (file_view->isearch.reverse){ + if (result.hit_backspace){ + start_pos = file_view->temp_highlight.pos + 1; + file_view->isearch.pos = start_pos; + } + else{ + pos = rfind_substr(file_string, start_pos - 1, *string); + if (pos >= 0){ + if (step_backward){ // TODO(allen): this if and it's mirror are suspicious + file_view->isearch.pos = pos; + start_pos = pos; + pos = rfind_substr(file_string, start_pos - 1, *string); + if (pos == -1){ + pos = start_pos; + } + } + view_set_temp_highlight(file_view, pos, pos+string->size); + } + } + } + + else{ + if (result.hit_backspace){ + start_pos = file_view->temp_highlight.pos - 1; + file_view->isearch.pos = start_pos; + } + else{ + pos = find_substr(file_string, start_pos + 1, *string); + if (pos < file->size){ + if (step_forward){ + file_view->isearch.pos = pos; + start_pos = pos; + pos = find_substr(file_string, start_pos + 1, *string); + if (pos == file->size){ + pos = start_pos; + } + } + view_set_temp_highlight(file_view, pos, pos+string->size); + } + } + } + } + + if (result.hit_newline || result.hit_ctrl_newline){ + view_cursor_move(file_view, file_view->temp_highlight); + file_view->state = FVIEW_STATE_EDIT; + } + + if (result.hit_esc){ + file_view->show_temp_highlight = 0; + file_view->state = FVIEW_STATE_EDIT; + } + }break; + + case FVIEW_STATE_GOTO_LINE: + { + String *string = &file_view->gotoline.str; + Single_Line_Input_Step result = + app_single_number_input_step(codes, key, string); + + if (result.hit_newline || result.hit_ctrl_newline){ + i32 line_number = str_to_int(*string); + Font *font = file_view->style->font; + if (line_number < 1) line_number = 1; + file_view->cursor = + view_compute_cursor_from_unwrapped_xy(file_view, 0, (real32)(line_number-1)*font->height); + file_view->preferred_x = view_get_cursor_x(file_view); + + file_view->state = FVIEW_STATE_EDIT; + } + + if (result.hit_esc){ + file_view->state = FVIEW_STATE_EDIT; + } + }break; + } +} + +internal void +free_file_view(View *view_){ + File_View *view = (File_View*)view_; + general_memory_free(view_->general, view->line_wrap_y); +} + +inline +DO_VIEW_SIG(do_file_view){ + i32 result = 0; + switch (message){ + case VMSG_RESIZE: + case VMSG_STYLE_CHANGE: + { + remeasure_file_view(view, rect); + }break; + case VMSG_DRAW: + { + result = draw_file_view(thread, view, rect, (view == active), target); + }break; + case VMSG_STEP: + { + result = step_file_view(thread, view, rect, (view == active), user_input); + }break; + case VMSG_FREE: + { + free_file_view(view); + }break; + } + return result; +} + +internal File_View* +file_view_init(View *view){ + File_View *result = (File_View*)view; + view->type = VIEW_TYPE_FILE; + view->do_view = do_file_view; + view->handle_command = handle_command_file_view; + return result; +} + +// BOTTOM + diff --git a/4ed_interactive_view.cpp b/4ed_interactive_view.cpp new file mode 100644 index 00000000..36892744 --- /dev/null +++ b/4ed_interactive_view.cpp @@ -0,0 +1,187 @@ +/* + * Mr. 4th Dimention - Allen Webster + * + * 19.09.2015 + * + * File editing view for 4coder + * + */ + +// TOP + +enum Action_Type{ + DACT_OPEN, + DACT_SAVE_AS, + DACT_NEW, + DACT_SWITCH, + DACT_KILL, + DACT_CLOSE_MINOR, + DACT_CLOSE_MAJOR, + DACT_THEME_OPTIONS +}; + +struct Delayed_Action{ + Action_Type type; + String string; + Panel *panel; +}; + +struct Delay{ + Delayed_Action acts[8]; + i32 count, max; +}; + +inline void +delayed_action(Delay *delay, Action_Type type, + String string, Panel *panel){ + Assert(delay->count < delay->max); + Delayed_Action action; + action.type = type; + action.string = string; + action.panel = panel; + delay->acts[delay->count++] = action; +} + +enum Interactive_View_Action{ + INTV_OPEN, + INTV_SAVE_AS, + INTV_NEW, + INTV_SWITCH, + INTV_KILL, +}; + +enum Interactive_View_Interaction{ + INTV_SYS_FILE_LIST, + INTV_LIVE_FILE_LIST, +}; + +struct Interactive_View{ + View view_base; + Hot_Directory *hot_directory; + Style *style; + Working_Set *working_set; + Delay *delay; + UI_State state; + Interactive_View_Interaction interaction; + Interactive_View_Action action; + char query_[256]; + String query; + char dest_[256]; + String dest; +}; + +inline Interactive_View* +view_to_interactive_view(View *view){ + Interactive_View *result = 0; + if (view->type == VIEW_TYPE_INTERACTIVE) + result = (Interactive_View*)view; + return result; +} + +internal void +interactive_view_complete(Interactive_View *view){ + Panel *panel = view->view_base.panel; + switch (view->action){ + case INTV_OPEN: + delayed_action(view->delay, DACT_OPEN, + view->hot_directory->string, panel); + break; + + case INTV_SAVE_AS: + delayed_action(view->delay, DACT_SAVE_AS, view->hot_directory->string, panel); + delayed_action(view->delay, DACT_CLOSE_MINOR, {}, panel); + break; + + case INTV_NEW: + delayed_action(view->delay, DACT_NEW, view->hot_directory->string, panel); + break; + + case INTV_SWITCH: + delayed_action(view->delay, DACT_SWITCH, view->dest, panel); + break; + + case INTV_KILL: + delayed_action(view->delay, DACT_KILL, view->dest, panel); + delayed_action(view->delay, DACT_CLOSE_MINOR, {}, panel); + break; + } +} + +internal i32 +step_draw_int_view(Interactive_View *view, Render_Target *target, i32_Rect rect, + Input_Summary *user_input, bool32 input_stage){ + i32 result = 0; + + UI_State state = + ui_state_init(&view->state, target, user_input, view->style, view->working_set, input_stage); + + UI_Layout layout; + begin_layout(&layout, rect); + + bool32 new_dir = 0; + bool32 file_selected = 0; + + terminate_with_null(&view->query); + do_label(&state, &layout, view->query.str, 1.f); + + switch (view->interaction){ + case INTV_SYS_FILE_LIST: + if (do_file_list_box(&state, &layout, view->hot_directory, 0, + &new_dir, &file_selected, 0)){ + result = 1; + } + if (new_dir){ + hot_directory_reload(view->hot_directory, view->working_set); + } + break; + + case INTV_LIVE_FILE_LIST: + if (do_live_file_list_box(&state, &layout, view->working_set, &view->dest, &file_selected)){ + result = 1; + } + break; + } + + if (file_selected){ + interactive_view_complete(view); + } + + if (ui_finish_frame(&view->state, &state, &layout, rect, 0, 0)){ + result = 1; + } + + return result; +} + +DO_VIEW_SIG(do_interactive_view){ + i32 result = 0; + + Interactive_View *int_view = (Interactive_View*)view; + switch (message){ + case VMSG_STEP: case VMSG_DRAW: + result = step_draw_int_view(int_view, target, rect, user_input, (message == VMSG_STEP)); + break; + } + + return result; +} + +internal Interactive_View* +interactive_view_init(View *view, Hot_Directory *hot_dir, Style *style, + Working_Set *working_set, Delay *delay){ + Interactive_View *result = (Interactive_View*)view; + view->type = VIEW_TYPE_INTERACTIVE; + view->do_view = do_interactive_view; + result->hot_directory = hot_dir; + hot_directory_clean_end(hot_dir); + hot_directory_reload(hot_dir, working_set); + result->query = make_fixed_width_string(result->query_); + result->dest = make_fixed_width_string(result->dest_); + result->style = style; + result->working_set = working_set; + result->delay = delay; + return result; +} + +// BOTTOM + diff --git a/4ed_internal.h b/4ed_internal.h new file mode 100644 index 00000000..9319d92f --- /dev/null +++ b/4ed_internal.h @@ -0,0 +1,188 @@ +/* + * Mr. 4th Dimention - Allen Webster + * + * 16.05.2015 + * + * Fascilities available for development but not intended for shipping. + * + */ + +/* + * Profiling + */ + +#if FRED_INTERNAL == 1 +enum Debug_Event_Type{ + DBGEV_START, + DBGEV_END, + DBGEV_MOMENT, + // never below this + DBGEV_COUNT +}; + +struct Debug_Event{ + i64 time; + char *name; + Debug_Event_Type type; + i32 which_hit; + i32 event_index; + i32 thread_index; +}; + +struct Debug_Event_Array{ + volatile u32 count; + Debug_Event e[512]; +}; + +struct Profile_Frame{ + Debug_Event_Array events; + i32 dbg_procing_start; + i32 dbg_procing_end; + i32 index; + i32 first_key; +}; + +Profile_Frame profile_frame; + +#define PAST_PROFILE_COUNT 30 +Profile_Frame past_frames[PAST_PROFILE_COUNT]; + +extern const i32 INTERNAL_event_index_count; +extern u32 INTERNAL_event_hits[]; +i64 INTERNAL_frame_start_time; + +bool32 INTERNAL_collecting_events; + +inline u32 +post_debug_event(char *name, Debug_Event_Type type, i32 event_index, i32 thread_index, u32 which_hit){ + u32 result = 0; + if (INTERNAL_collecting_events){ + u32 index = + InterlockedIncrement(&profile_frame.events.count); + --index; + + Assert(index < ArrayCount(profile_frame.events.e)); + + Debug_Event ev; + ev.time = system_time() - INTERNAL_frame_start_time; + ev.name = name; + ev.type = type; + ev.event_index = event_index; + ev.thread_index = thread_index; + + if (type == DBGEV_END){ + ev.which_hit = which_hit; + } + else{ + ev.which_hit = InterlockedIncrement(INTERNAL_event_hits + event_index) - 1; + } + + profile_frame.events.e[index] = ev; + result = ev.which_hit; + } + + return result; +} + +internal u32 +quick_partition(Debug_Event *es, u32 start, u32 pivot){ + Debug_Event *p = es + pivot; + + i32 m = (start + pivot) >> 1; + Swap(*p, es[m]); + + i32 pn = p->thread_index; + i32 pe = p->event_index; + i32 ph = p->which_hit; + i32 pt = p->type; + + for (u32 i = start; i < pivot; ++i){ + Debug_Event *e = es + i; + + bool32 smaller = 0; + + if (e->thread_index < pn) smaller = 1; + else if (e->thread_index == pn){ + if (e->type != DBGEV_MOMENT && pt == DBGEV_MOMENT) smaller = 1; + else if (e->type != DBGEV_MOMENT){ + if (e->event_index < pe) smaller = 1; + else if (e->event_index == pe){ + if (e->which_hit < ph) smaller = 1; + else if (e->which_hit == ph){ + if (e->type < pt) smaller = 1; + } + } + } + else if (pt == DBGEV_MOMENT){ + if (e->time < p->time) smaller = 1; + } + } + + if (smaller){ + Swap(*e, es[start]); + ++start; + } + } + Swap(*p, es[start]); + + return start; +} + +internal void +quick_sort(Debug_Event *e, u32 start, u32 pivot){ + u32 mid = quick_partition(e, start, pivot); + if (start + 1 < mid) quick_sort(e, start, mid - 1); + if (mid + 1 < pivot) quick_sort(e, mid + 1, pivot); +} + +inline void +sort(Debug_Event_Array *events){ + quick_sort(events->e, 0, events->count - 1); +} + +globalvar i32 INTERNAL_frame_index; +globalvar bool32 INTERNAL_updating_profile; + +#define ProfileStart_(name, start, counter, hit, thread, n, c)\ + name = n; counter = c; start = system_time(); hit = post_debug_event(n, DBGEV_START, counter, thread, 0) +#define ProfileEnd_(name, start, counter, hit, thread) post_debug_event(name, DBGEV_END, counter, thread, hit) +#define ProfileMoment_(name, counter, thread) post_debug_event(name, DBGEV_MOMENT, counter, thread, 0) + +struct INTERNAL_Profile_Block{ + char *name; + i64 start; + i32 counter; + i32 thread; + i32 hit; + + INTERNAL_Profile_Block(char *n, i32 c, i32 t){ + ProfileStart_(name, start, counter, hit, t, n, c); + thread = t; + } + + ~INTERNAL_Profile_Block(){ + ProfileEnd_(name, start, counter, hit, thread); + } +}; + +#define ProfileBlock(name, thread) INTERNAL_Profile_Block name(#name, __COUNTER__, thread) +#define ProfileBlockFunction() INTERNAL_Profile_Block name(__FUNCTION__, __COUNTER__, 0) + +#define ProfileStart(name) char *_pname_##name; i64 _pstart_##name; i32 _pcounter_##name; u32 _phit_##name; \ + ProfileStart_(_pname_##name, _pstart_##name, _pcounter_##name, _phit_##name, system_thread_get_id(thread), #name, __COUNTER__) + +#define ProfileEnd(name) ProfileEnd_(_pname_##name, _pstart_##name, _pcounter_##name, _phit_##name, system_thread_get_id(thread)) + +#define ProfileMoment(name, thread) ProfileMoment_(#name, __COUNTER__, thread) +#define ProfileMomentFunction() ProfileMoment_(__FUNCTION__, __COUNTER__, 0) + +#else + +#define ProfileBlock(name) +#define ProfileStart(name) +#define ProfileEnd(name) +#define ProfileMoment(name) +#define ProfileMomentFunction() + +#endif + diff --git a/4ed_keyboard.cpp b/4ed_keyboard.cpp new file mode 100644 index 00000000..90921d97 --- /dev/null +++ b/4ed_keyboard.cpp @@ -0,0 +1,163 @@ +/* + * Mr. 4th Dimention - Allen Webster + * + * 12.17.2014 + * + * Win32-US Keyboard layer for project codename "4ed" + * + */ + +// TOP + +globalvar u16 keycode_lookup_table[255]; +globalvar u16 loose_keycode_lookup_table[255]; + +internal void +keycode_init(Key_Codes *codes, Key_Codes *loose_codes){ + // NOTE(allen): Assign values to the global keycodes. + // Skip over the ascii characters that are used as codes. + u16 code = 1; + u16 *codes_array = (u16*)codes; + for (i32 i = 0; i < sizeof(Key_Codes)/2;){ + switch (code){ + case '\n': code++; break; + case '\t': code++; break; + case 0x20: code = 0x7F; break; + + default: + codes_array[i++] = code++; + } + } + + code = 1; + codes_array = (u16*)loose_codes; + for (i32 i = 0; i < sizeof(Key_Codes)/2; ++i){ + codes_array[i] = code++; + } + + // NOTE(allen): lookup table for conversion from + // win32 vk values to fred_keycode values. + for (u8 i = 0; i < 255; ++i){ + if ((i >= '0' && i <= '9') || + (i >= 'A' && i <= 'Z')){ + keycode_lookup_table[i] = i; + loose_keycode_lookup_table[i] = 0; + } + else{ + + u16 code, loose = 0; + switch (i){ + case VK_SPACE: code = ' '; break; + case VK_BACK: code = loose = codes->back; break; + case VK_OEM_MINUS: code = '-'; break; + case VK_OEM_PLUS: code = '='; break; + case VK_SUBTRACT: code = '-'; break; + case VK_ADD: code = '+'; break; + case VK_MULTIPLY: code = '*'; break; + case VK_DIVIDE: code = '/'; break; + + case VK_OEM_3: code = '`'; break; + case VK_OEM_5: code = '\\'; break; + case VK_OEM_4: code = '['; break; + case VK_OEM_6: code = ']'; break; + case VK_TAB: code = '\t'; break; + case VK_RETURN: code = '\n'; break; + case VK_OEM_7: code = '\''; break; + + case VK_OEM_1: code = ';'; break; + case VK_OEM_2: code = '/'; break; + case VK_OEM_PERIOD: code = '.'; break; + case VK_OEM_COMMA: code = ','; break; + case VK_UP: code = loose = codes->up; break; + case VK_DOWN: code = loose = codes->down; break; + case VK_LEFT: code = loose = codes->left; break; + case VK_RIGHT: code = loose = codes->right; break; + case VK_DELETE: code = loose = codes->del; break; + + case VK_INSERT: code = loose = codes->insert; break; + case VK_HOME: code = loose = codes->home; break; + case VK_END: code = loose = codes->end; break; + case VK_PRIOR: code = loose = codes->page_up; break; + case VK_NEXT: code = loose = codes->page_down; break; + case VK_ESCAPE: code = loose = codes->esc; break; + + case VK_NUMPAD0: + case VK_NUMPAD1: case VK_NUMPAD2: case VK_NUMPAD3: + case VK_NUMPAD4: case VK_NUMPAD5: case VK_NUMPAD6: + case VK_NUMPAD7: case VK_NUMPAD8: case VK_NUMPAD9: + code = (i - VK_NUMPAD0) + '0'; break; + + default: code = 0; break; + } + + keycode_lookup_table[i] = code; + loose_keycode_lookup_table[i] = loose; + } + } +} + +inline u16 +keycode_lookup(u8 virtual_keycode){ + return keycode_lookup_table[virtual_keycode]; +} + +inline u16 +loose_keycode_lookup(u8 virtual_keycode){ + return loose_keycode_lookup_table[virtual_keycode]; +} + +inline bool32 +keycode_has_ascii(u16 keycode){ + return (keycode >= 0x20 && keycode < 0x7F) || keycode == '\n' || keycode == '\t'; +} + +internal u8 +keycode_to_character_ascii(Key_Codes *codes, + u16 keycode, + bool32 shift, + bool32 caps_lock){ + u8 character = 0; + if (keycode >= 'A' && keycode <= 'Z'){ + if (caps_lock) shift = !shift; + if (!shift) character = char_to_lower((char)keycode); + else character = (u8)keycode; + } + else if (keycode >= '0' && keycode <= '9'){ + persist u8 shift_number_table[10] = { + ')', '!', '@', '#', '$', '%', '^', '&', '*', '(' + //0 1 2 3 4 5 6 7 8 9 + }; + if (shift){ + character = shift_number_table[keycode - '0']; + } + else{ + character = (u8)keycode; + } + } + else{ + if (keycode_has_ascii(keycode)){ + character = (u8)keycode; + u8 shift_character = character; + switch (keycode){ + case '-': character = '-'; shift_character = '_'; break; + case '=': character = '='; shift_character = '+'; break; + case '`': character = '`'; shift_character = '~'; break; + case '\\': character = '\\'; shift_character = '|'; break; + case '[': character = '['; shift_character = '{'; break; + case ']': character = ']'; shift_character = '}'; break; + case '\'': character = '\''; shift_character = '"'; break; + case ';': character = ';'; shift_character = ':'; break; + case '/': character = '/'; shift_character = '?'; break; + case '.': character = '.'; shift_character = '>'; break; + case ',': character = ','; shift_character = '<'; break; + case ' ': character = ' '; shift_character = ' '; break; + case '\n': character = '\n'; shift_character = '\n'; break; + case '\t': character = '\t'; shift_character = '\t'; break; + } + if (shift) character = shift_character; + } + } + return character; +} + +// BOTTOM diff --git a/4ed_layout.cpp b/4ed_layout.cpp new file mode 100644 index 00000000..d96f6c40 --- /dev/null +++ b/4ed_layout.cpp @@ -0,0 +1,516 @@ +/* + * Mr. 4th Dimention - Allen Webster + * + * 19.08.2015 + * + * Panel layout and general view functions for 4coder + * + */ + +// TOP +// TODO(allen): +// +// BUGS +// + +struct Interactive_Style{ + u32 bar_color; + u32 bar_active_color; + u32 base_color; + u32 pop1_color; + u32 pop2_color; +}; + +struct Interactive_Bar{ + Interactive_Style style; + real32 pos_x, pos_y; + real32 text_shift_x, text_shift_y; + i32_Rect rect; + Font *font; +}; + +enum View_Message{ + VMSG_STEP, + VMSG_DRAW, + VMSG_RESIZE, + VMSG_STYLE_CHANGE, + VMSG_FREE +}; + +struct View; +#define DO_VIEW_SIG(name)\ + i32 (name)(Thread_Context *thread, View *view, i32_Rect rect, View *active,\ + View_Message message, Render_Target *target, Input_Summary *user_input, Input_Summary *active_input) +typedef DO_VIEW_SIG(Do_View_Function); + +#define HANDLE_COMMAND_SIG(name)\ + void (name)(View *view, Command_Data *command, Command_Binding binding,\ + Key_Single key, Key_Codes *codes) +typedef HANDLE_COMMAND_SIG(Handle_Command_Function); + +// TODO(allen): this shouldn't exist +enum View_Type{ + VIEW_TYPE_NONE, + VIEW_TYPE_FILE, + VIEW_TYPE_COLOR, + VIEW_TYPE_DEBUG, + VIEW_TYPE_INTERACTIVE, + VIEW_TYPE_MENU +}; + +struct Panel; +struct View{ + union{ + View *next_free; + View *major; + View *minor; + }; + Panel *panel; + Command_Map *map; + Do_View_Function *do_view; + Handle_Command_Function *handle_command; + General_Memory *general; + i32 type; + i32 block_size; + Application_Mouse_Cursor mouse_cursor_type; + bool32 is_active; + bool32 is_minor; +}; + +struct Live_Views{ + void *views; + View *free_view; + i32 count, max; + i32 stride; +}; + +struct Panel_Divider{ + Panel_Divider *next_free; + i32 parent; + i32 which_child; + i32 child1, child2; + bool32 v_divider; + i32 pos; +}; + +struct Screen_Region{ + i32_Rect full; + i32_Rect inner; + i32_Rect prev_inner; + i32 l_margin, r_margin; + i32 t_margin, b_margin; +}; + +struct Panel{ + View *view; + i32 parent; + i32 which_child; + union{ + struct{ + i32_Rect full; + i32_Rect inner; + i32_Rect prev_inner; + i32 l_margin, r_margin; + i32 t_margin, b_margin; + }; + Screen_Region screen_region; + }; +}; + +struct Editing_Layout{ + Panel *panels; + Panel_Divider *dividers; + Panel_Divider *free_divider; + i32 panel_count, panel_max_count; + i32 root; + i32 active_panel; + i32 full_width, full_height; +}; + +internal void +intbar_draw_string(Render_Target *target, Interactive_Bar *bar, + u8 *str, u32 char_color){ + Font *font = bar->font; + for (i32 i = 0; str[i]; ++i){ + char c = str[i]; + font_draw_glyph(target, font, c, + bar->pos_x + bar->text_shift_x, + bar->pos_y + bar->text_shift_y, + char_color); + bar->pos_x += font_get_glyph_width(font, c); + } +} + +internal void +intbar_draw_string(Render_Target *target, + Interactive_Bar *bar, String str, + u32 char_color){ + Font *font = bar->font; + for (i32 i = 0; i < str.size; ++i){ + char c = str.str[i]; + font_draw_glyph(target, font, c, + bar->pos_x + bar->text_shift_x, + bar->pos_y + bar->text_shift_y, + char_color); + bar->pos_x += font_get_glyph_width(font, c); + } +} + +internal void +panel_init(Panel *panel){ + *panel = {}; + panel->parent = -1; + panel->l_margin = 3; + panel->r_margin = 3; + panel->t_margin = 3; + panel->b_margin = 3; +} + +internal View* +live_set_get_view(Live_Views *live_set, i32 i){ + void *result = ((char*)live_set->views + i*live_set->stride); + return (View*)result; +} + +internal View* +live_set_alloc_view(Live_Views *live_set, General_Memory *general){ + Assert(live_set->count < live_set->max); + View *result = 0; + result = live_set->free_view; + live_set->free_view = result->next_free; + memset(result, 0, live_set->stride); + ++live_set->count; + result->is_active = 1; + result->general = general; + return result; +} + +inline void +live_set_free_view(Live_Views *live_set, View *view){ + Assert(live_set->count > 0); + view->do_view(0, view, {}, 0, VMSG_FREE, 0, {}, 0); + view->next_free = live_set->free_view; + live_set->free_view = view; + --live_set->count; + view->is_active = 0; +} + +inline void +view_replace_major(View *new_view, Panel *panel, Live_Views *live_set){ + View *view = panel->view; + if (view){ + if (view->is_minor && view->major){ + live_set_free_view(live_set, view->major); + } + live_set_free_view(live_set, view); + } + new_view->panel = panel; + new_view->minor = 0; + panel->view = new_view; +} + +inline void +view_replace_minor(View *new_view, Panel *panel, Live_Views *live_set){ + View *view = panel->view; + new_view->is_minor = 1; + if (view){ + if (view->is_minor){ + new_view->major = view->major; + live_set_free_view(live_set, view); + } + else{ + new_view->major = view; + view->is_active = 0; + } + } + else{ + new_view->major = 0; + } + new_view->panel = panel; + panel->view = new_view; +} + +inline void +view_remove_major(Panel *panel, Live_Views *live_set){ + View *view = panel->view; + if (view){ + if (view->is_minor && view->major){ + live_set_free_view(live_set, view->major); + } + live_set_free_view(live_set, view); + } + panel->view = 0; +} + +inline void +view_remove_major_leave_minor(Panel *panel, Live_Views *live_set){ + View *view = panel->view; + if (view){ + if (view->is_minor && view->major){ + live_set_free_view(live_set, view->major); + view->major = 0; + } + else{ + live_set_free_view(live_set, view); + panel->view = 0; + } + } +} + +inline void +view_remove_minor(Panel *panel, Live_Views *live_set){ + View *view = panel->view; + View *major = 0; + if (view){ + if (view->is_minor){ + major = view->major; + live_set_free_view(live_set, view); + } + } + panel->view = major; + if (major) major->is_active = 1; +} + +inline void +view_remove(Panel *panel, Live_Views *live_set){ + View *view = panel->view; + if (view->is_minor) view_remove_minor(panel, live_set); + else view_remove_major(panel, live_set); +} + +struct Divider_And_ID{ + Panel_Divider* divider; + i32 id; +}; + +internal Divider_And_ID +layout_alloc_divider(Editing_Layout *layout){ + Assert(layout->free_divider); + Divider_And_ID result; + result.divider = layout->free_divider; + layout->free_divider = result.divider->next_free; + *result.divider = {}; + result.divider->parent = -1; + result.divider->child1 = -1; + result.divider->child2 = -1; + result.id = (i32)(result.divider - layout->dividers); + if (layout->panel_count == 1){ + layout->root = result.id; + } + return result; +} + +internal Divider_And_ID +layout_get_divider(Editing_Layout *layout, i32 id){ + Assert(id >= 0 && id < layout->panel_max_count-1); + Divider_And_ID result; + result.id = id; + result.divider = layout->dividers + id; + return result; +} + +internal Panel* +layout_alloc_panel(Editing_Layout *layout){ + Assert(layout->panel_count < layout->panel_max_count); + Panel *result = layout->panels + layout->panel_count; + *result = {}; + ++layout->panel_count; + return result; +} + +internal void +layout_free_divider(Editing_Layout *layout, Panel_Divider *divider){ + divider->next_free = layout->free_divider; + layout->free_divider = divider; +} + +internal void +layout_free_panel(Editing_Layout *layout, Panel *panel){ + Panel *panels = layout->panels; + i32 panel_count = --layout->panel_count; + i32 panel_i = (i32)(panel - layout->panels); + for (i32 i = panel_i; i < panel_count; ++i){ + Panel *p = panels + i; + *p = panels[i+1]; + if (p->view){ + p->view->panel = p; + } + } +} + +internal Divider_And_ID +layout_calc_divider_id(Editing_Layout *layout, Panel_Divider *divider){ + Divider_And_ID result; + result.divider = divider; + result.id = (i32)(divider - layout->dividers); + return result; +} + +struct Split_Result{ + Panel_Divider *divider; + Panel *panel; +}; + +internal Split_Result +layout_split_panel(Editing_Layout *layout, Panel *panel, bool32 vertical){ + Divider_And_ID div = layout_alloc_divider(layout); + if (panel->parent != -1){ + Divider_And_ID pdiv = layout_get_divider(layout, panel->parent); + if (panel->which_child == -1){ + pdiv.divider->child1 = div.id; + } + else{ + pdiv.divider->child2 = div.id; + } + } + + div.divider->parent = panel->parent; + div.divider->which_child = panel->which_child; + if (vertical){ + div.divider->v_divider = 1; + div.divider->pos = (panel->full.x0 + panel->full.x1) / 2; + } + else{ + div.divider->v_divider = 0; + div.divider->pos = (panel->full.y0 + panel->full.y1) / 2; + } + + Panel *new_panel = layout_alloc_panel(layout); + panel->parent = div.id; + panel->which_child = -1; + new_panel->parent = div.id; + new_panel->which_child = 1; + + Split_Result result; + result.divider = div.divider; + result.panel = new_panel; + return result; +} + +internal void +panel_fix_internal_area(Panel *panel){ + i32 left, right, top, bottom; + left = panel->l_margin; + right = panel->r_margin; + top = panel->t_margin; + bottom = panel->b_margin; + + panel->inner.x0 = panel->full.x0 + left; + panel->inner.x1 = panel->full.x1 - right; + panel->inner.y0 = panel->full.y0 + top; + panel->inner.y1 = panel->full.y1 - bottom; +} + +internal void +layout_fix_all_panels(Editing_Layout *layout){ + Panel *panels = layout->panels; + if (layout->panel_count > 1){ + Panel_Divider *dividers = layout->dividers; + int panel_count = layout->panel_count; + + Panel *panel = panels; + for (i32 i = 0; i < panel_count; ++i){ + i32 x0, x1, y0, y1; + x0 = 0; + x1 = x0 + layout->full_width; + y0 = 0; + y1 = y0 + layout->full_height; + + i32 pos; + i32 which_child = panel->which_child; + Divider_And_ID div; + div.id = panel->parent; + + for (;;){ + div.divider = dividers + div.id; + pos = div.divider->pos; + div.divider = dividers + div.id; + // NOTE(allen): sorry if this is hard to read through, there are + // two binary conditionals that combine into four possible cases. + // Why am I appologizing to you? IF YOU CANT HANDLE MY CODE GET OUT! + i32 action = (div.divider->v_divider << 1) | (which_child > 0); + switch (action){ + case 0: // v_divider : 0, which_child : -1 + if (pos < y1) y1 = pos; + break; + case 1: // v_divider : 0, which_child : 1 + if (pos > y0) y0 = pos; + break; + case 2: // v_divider : 1, which_child : -1 + if (pos < x1) x1 = pos; + break; + case 3: // v_divider : 1, which_child : 1 + if (pos > x0) x0 = pos; + break; + } + + if (div.id != layout->root){ + div.id = div.divider->parent; + which_child = div.divider->which_child; + } + else{ + break; + } + } + + panel->full.x0 = x0; + panel->full.y0 = y0; + panel->full.x1 = x1; + panel->full.y1 = y1; + panel_fix_internal_area(panel); + ++panel; + } + } + + else{ + panels[0].full.x0 = 0; + panels[0].full.y0 = 0; + panels[0].full.x1 = layout->full_width; + panels[0].full.y1 = layout->full_height; + panel_fix_internal_area(panels); + } +} + +internal void +layout_refit(Editing_Layout *layout, + i32 prev_x_off, i32 prev_y_off, + i32 prev_width, i32 prev_height){ + Panel_Divider *dividers = layout->dividers; + i32 divider_max_count = layout->panel_max_count - 1; + + i32 x_off = 0; + i32 y_off = 0; + + real32 h_ratio = ((real32)layout->full_width) / prev_width; + real32 v_ratio = ((real32)layout->full_height) / prev_height; + + for (i32 divider_id = 0; divider_id < divider_max_count; ++divider_id){ + Panel_Divider *divider = dividers + divider_id; + if (divider->v_divider){ + divider->pos = x_off + ROUND32((divider->pos - prev_x_off) * h_ratio); + } + else{ + divider->pos = y_off + ROUND32((divider->pos - prev_y_off) * v_ratio); + } + } + + layout_fix_all_panels(layout); +} + +inline real32 +view_base_compute_width(View *view){ + Panel *panel = view->panel; + return (real32)(panel->inner.x1 - panel->inner.x0); +} + +inline real32 +view_base_compute_height(View *view){ + Panel *panel = view->panel; + return (real32)(panel->inner.y1 - panel->inner.y0); +} + +#define view_compute_width(view) (view_base_compute_width(&(view)->view_base)) +#define view_compute_height(view) (view_base_compute_height(&(view)->view_base)) + +// BOTTOM + diff --git a/4ed_math.cpp b/4ed_math.cpp new file mode 100644 index 00000000..b8d67e8c --- /dev/null +++ b/4ed_math.cpp @@ -0,0 +1,749 @@ +/* + * Mr. 4th Dimention - Allen Webster + * + * 15.05.2015 + * + * Math functions for 4coder + * + */ + +// TOP + +/* + * Scalar operators + */ + +#define C_MATH 1 + +#define DEG_TO_RAD 0.0174533f + +#if C_MATH +#include +#endif + +inline real32 +ABS(real32 x){ +#if C_MATH + return abs(x); +#endif +} + +inline real32 +MOD(real32 x, i32 m){ +#if C_MATH + real32 whole, frac; + frac = modf(x, &whole); + return ((i32)(whole) % m) + frac; +#endif +} + +inline real32 +SQRT(real32 x){ +#if C_MATH + return sqrt(x); +#endif +} + +inline real32 +SIN(real32 x_degrees){ +#if C_MATH + return sinf(x_degrees * DEG_TO_RAD); +#endif +} + +inline real32 +COS(real32 x_degrees){ +#if C_MATH + return cosf(x_degrees * DEG_TO_RAD); +#endif +} + +/* + * Rounding + */ + +inline i32 +TRUNC32(real32 x) { return (i32)x; } + +inline i32 +FLOOR32(real32 x) { return (i32)(x)-((x!=(i32)(x) && x<0)?1:0); } + +inline i32 +CEIL32(real32 x) { return (i32)(x)+((x!=(i32)(x) && x>0)?1:0); } + +inline i32 +ROUND32(real32 x) { return FLOOR32(x + .5f); } + +inline i32 +DIVCEIL32(i32 n, i32 d) { + i32 q = (n/d); + return q + (q*d < n); +} + +inline real32 +FRACPART32(real32 x) { return x - (i32)x; } + +/* + * Rectangles + */ + +struct i32_Rect{ + i32 x0, y0; + i32 x1, y1; +}; + +struct real32_Rect{ + real32 x0, y0; + real32 x1, y1; +}; + +inline i32_Rect +i32R(i32 l, i32 t, i32 r, i32 b){ + i32_Rect rect; + rect.x0 = l; rect.y0 = t; + rect.x1 = r; rect.y1 = b; + return rect; +} + +inline i32_Rect +i32R(real32_Rect r){ + i32_Rect rect; + rect.x0 = (i32)r.x0; + rect.y0 = (i32)r.y0; + rect.x1 = (i32)r.x1; + rect.y1 = (i32)r.y1; + return rect; +} + +inline i32_Rect +i32XYWH(i32 x, i32 y, i32 w, i32 h){ + i32_Rect rect; + rect.x0 = x; rect.y0 = y; + rect.x1 = x+w; rect.y1 = y+h; + return rect; +} + +inline real32_Rect +real32R(real32 l, real32 t, real32 r, real32 b){ + real32_Rect rect; + rect.x0 = l; rect.y0 = t; + rect.x1 = r; rect.y1 = b; + return rect; +} + +inline real32_Rect +real32R(i32_Rect r){ + real32_Rect rect; + rect.x0 = (real32)r.x0; + rect.y0 = (real32)r.y0; + rect.x1 = (real32)r.x1; + rect.y1 = (real32)r.y1; + return rect; +} + +inline real32_Rect +real32XYWH(real32 x, real32 y, real32 w, real32 h){ + real32_Rect rect; + rect.x0 = x; rect.y0 = y; + rect.x1 = x+w; rect.y1 = y+h; + return rect; +} + +inline bool32 +hit_check(i32 x, i32 y, i32 x0, i32 y0, i32 x1, i32 y1){ + return (x >= x0 && x < x1 && y >= y0 && y < y1); +} + +inline bool32 +hit_check(i32 x, i32 y, i32_Rect rect){ + return (hit_check(x, y, rect.x0, rect.y0, rect.x1, rect.y1)); +} + +inline bool32 +hit_check(i32 x, i32 y, real32 x0, real32 y0, real32 x1, real32 y1){ + return (x >= x0 && x < x1 && y >= y0 && y < y1); +} + +inline bool32 +hit_check(i32 x, i32 y, real32_Rect rect){ + return (hit_check(x, y, rect.x0, rect.y0, rect.x1, rect.y1)); +} + +inline bool32 +positive_area(i32_Rect rect){ + return (rect.x0 < rect.x1 && rect.y0 < rect.y1); +} + +inline i32_Rect +get_inner_rect(i32_Rect outer, i32 margin){ + i32_Rect r; + r.x0 = outer.x0 + margin; + r.y0 = outer.y0 + margin; + r.x1 = outer.x1 - margin; + r.y1 = outer.y1 - margin; + return r; +} + +inline bool32 +fits_inside(i32_Rect rect, i32_Rect outer){ + return (rect.x0 >= outer.x0 && rect.x1 <= outer.x1 && + rect.y0 >= outer.y0 && rect.y1 <= outer.y1); +} + +/* + * Vectors + */ + +struct Vec2{ + union{ + struct{ + real32 x, y; + }; + struct{ + real32 v[2]; + }; + }; +}; + +struct Vec3{ + union{ + struct{ + real32 x, y, z; + }; + struct{ + real32 r, g, b; + }; + struct{ + Vec2 xy; + real32 _z; + }; + struct{ + real32 _x; + Vec2 yz; + }; + struct{ + real32 v[3]; + }; + }; +}; + +struct Vec4{ + union{ + struct{ + real32 r, g, b, a; + }; + + struct{ + real32 h, s, l, __a; + }; + struct{ + real32 x, y, z, w; + }; + struct{ + Vec3 rgb; + real32 _a; + }; + struct{ + Vec3 xyz; + real32 _w; + }; + struct{ + real32 _x; + Vec3 yzw; + }; + struct{ + real32 v[4]; + }; + }; +}; + +inline internal Vec2 +V2(real32 x, real32 y){ + Vec2 result; + result.x = x; + result.y = y; + return result; +} + +inline internal Vec3 +V3(real32 x, real32 y, real32 z){ + Vec3 result; + result.x = x; + result.y = y; + result.z = z; + return result; +} + +inline internal Vec4 +V4(real32 x, real32 y, real32 z, real32 w){ + Vec4 result; + result.x = x; + result.y = y; + result.z = z; + result.w = w; + return result; +} + +inline internal Vec2 +operator+(Vec2 a, Vec2 b){ + Vec2 result; + result.x = a.x + b.x; + result.y = a.y + b.y; + return result; +} + +inline internal Vec3 +operator+(Vec3 a, Vec3 b){ + Vec3 result; + result.x = a.x + b.x; + result.y = a.y + b.y; + result.z = a.z + b.z; + return result; +} + +inline internal Vec4 +operator+(Vec4 a, Vec4 b){ + Vec4 result; + result.x = a.x + b.x; + result.y = a.y + b.y; + result.z = a.z + b.z; + result.w = a.w + b.w; + return result; +} + +inline internal Vec2 +operator-(Vec2 a, Vec2 b){ + Vec2 result; + result.x = a.x - b.x; + result.y = a.y - b.y; + return result; +} + +inline internal Vec3 +operator-(Vec3 a, Vec3 b){ + Vec3 result; + result.x = a.x - b.x; + result.y = a.y - b.y; + result.z = a.z - b.z; + return result; +} + +inline internal Vec4 +operator-(Vec4 a, Vec4 b){ + Vec4 result; + result.x = a.x - b.x; + result.y = a.y - b.y; + result.z = a.z - b.z; + result.w = a.w - b.w; + return result; +} + +inline internal Vec2 +operator*(Vec2 a, real32 k){ + Vec2 result; + result.x = a.x * k; + result.y = a.y * k; + return result; +} + +inline internal Vec3 +operator*(Vec3 a, real32 k){ + Vec3 result; + result.x = a.x * k; + result.y = a.y * k; + result.z = a.z * k; + return result; +} + +inline internal Vec4 +operator*(Vec4 a, real32 k){ + Vec4 result; + result.x = a.x * k; + result.y = a.y * k; + result.z = a.z * k; + result.w = a.w * k; + return result; +} + +inline internal Vec2 +operator*(real32 k, Vec2 a){ + Vec2 result; + result.x = a.x * k; + result.y = a.y * k; + return result; +} + +inline internal Vec3 +operator*(real32 k, Vec3 a){ + Vec3 result; + result.x = a.x * k; + result.y = a.y * k; + result.z = a.z * k; + return result; +} + +inline internal Vec4 +operator*(real32 k, Vec4 a){ + Vec4 result; + result.x = a.x * k; + result.y = a.y * k; + result.z = a.z * k; + result.w = a.w * k; + return result; +} + +inline internal Vec2& +operator+=(Vec2 &a, Vec2 b){ + a = (a + b); + return a; +} + +inline internal Vec3& +operator+=(Vec3 &a, Vec3 b){ + a = (a + b); + return a; +} + +inline internal Vec4& +operator+=(Vec4 &a, Vec4 b){ + a = (a + b); + return a; +} + +inline internal Vec2& +operator-=(Vec2 &a, Vec2 b){ + a = (a - b); + return a; +} + +inline internal Vec3& +operator-=(Vec3 &a, Vec3 b){ + a = (a - b); + return a; +} + +inline internal Vec4& +operator-=(Vec4 &a, Vec4 b){ + a = (a - b); + return a; +} + +inline internal Vec2& +operator*=(Vec2 &a, real32 k){ + a = (a * k); + return a; +} + +inline internal Vec3& +operator*=(Vec3 &a, real32 k){ + a = (a * k); + return a; +} + +inline internal Vec4& +operator*=(Vec4 &a, real32 k){ + a = (a * k); + return a; +} + +inline internal real32 +dot(Vec2 a, Vec2 b){ + real32 result; + result = a.x*b.x + a.y*b.y; + return result; +} + +inline internal real32 +dot(Vec3 a, Vec3 b){ + real32 result; + result = a.x*b.x + a.y*b.y + a.z*b.z; + return result; +} + +inline internal real32 +dot(Vec4 a, Vec4 b){ + real32 result; + result = a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w; + return result; +} + +inline internal Vec3 +cross(Vec3 a, Vec3 b){ + Vec3 result; + result.x = a.y*b.z - b.y*a.z; + result.y = a.z*b.x - b.z*a.x; + result.z = a.x*b.y - b.x*a.y; + return result; +} + +inline internal Vec2 +hadamard(Vec2 a, Vec2 b){ + Vec2 result; + result.x = a.x*b.x; + result.y = a.y*b.y; + return result; +} + +inline internal Vec3 +hadamard(Vec3 a, Vec3 b){ + Vec3 result; + result.x = a.x*b.x; + result.y = a.y*b.y; + result.z = a.z*b.z; + return result; +} + +inline internal Vec4 +hadamard(Vec4 a, Vec4 b){ + Vec4 result; + result.x = a.x*b.x; + result.y = a.y*b.y; + result.z = a.z*b.z; + result.w = a.w*b.w; + return result; +} + +inline internal Vec2 +perp(Vec2 v){ + Vec2 result; + result.x = -v.y; + result.y = v.x; + return result; +} + +inline Vec2 +polar_to_cartesian(real32 theta_degrees, real32 length){ + Vec2 result; + result.x = COS(theta_degrees)*length; + result.y = SIN(theta_degrees)*length; + return result; +} + +inline Vec2 +rotate(Vec2 v, real32 theta_degrees){ + Vec2 result; + real32 c, s; + c = COS(theta_degrees); + s = SIN(theta_degrees); + result.x = v.x*c - v.y*s; + result.y = v.x*s + v.y*c; + return result; +} + +/* + * Coordinates + */ + +struct Matrix2{ + Vec2 x_axis; + Vec2 y_axis; +}; + +internal Matrix2 +invert(Vec2 x_axis, Vec2 y_axis){ + Matrix2 result = {}; + real32 det = 1.f / (x_axis.x*y_axis.y - x_axis.y*y_axis.x); + result.x_axis.x = y_axis.y*det; + result.y_axis.x = -y_axis.x*det; + result.x_axis.y = -x_axis.y*det; + result.y_axis.y = x_axis.x*det; + return result; +} + +internal Matrix2 +invert(Matrix2 m){ + Matrix2 result = {}; + real32 det = 1.f / (m.x_axis.x*m.y_axis.y - m.x_axis.y*m.y_axis.x); + result.x_axis.x = m.y_axis.y*det; + result.y_axis.x = -m.y_axis.x*det; + result.x_axis.y = -m.x_axis.y*det; + result.y_axis.y = m.x_axis.x*det; + return result; +} + +/* + * Lerps, Clamps, Thresholds, Etc + */ + +inline real32 +lerp(real32 a, real32 t, real32 b){ + return a + (b-a)*t; +} + +inline Vec2 +lerp(Vec2 a, real32 t, Vec2 b){ + return a + (b-a)*t; +} + +inline Vec3 +lerp(Vec3 a, real32 t, Vec3 b){ + return a + (b-a)*t; +} + +inline Vec4 +lerp(Vec4 a, real32 t, Vec4 b){ + return a + (b-a)*t; +} + +inline real32 +unlerp(real32 a, real32 x, real32 b){ + return (x - a) / (b - a); +} + +inline real32 +clamp(real32 a, real32 n, real32 z){ + return (nz)?(z):n); +} + +/* + * Color + */ + +// TODO(allen): Convert colors to Vec4 +inline internal u32 +color_blend(u32 a, real32 t, u32 b){ + union{ + u8 byte[4]; + u32 comp; + } A, B, R; + + A.comp = a; + B.comp = b; + + R.byte[0] = (u8)lerp(A.byte[0], t, B.byte[0]); + R.byte[1] = (u8)lerp(A.byte[1], t, B.byte[1]); + R.byte[2] = (u8)lerp(A.byte[2], t, B.byte[2]); + R.byte[3] = (u8)lerp(A.byte[3], t, B.byte[3]); + + return R.comp; +} + +internal Vec3 +unpack_color3(u32 color){ + Vec3 result; + result.r = ((color >> 16) & 0xFF) / 255.f; + result.g = ((color >> 8) & 0xFF) / 255.f; + result.b = ((color >> 0) & 0xFF) / 255.f; + return result; +} + +internal Vec4 +unpack_color4(u32 color){ + Vec4 result; + result.a = ((color >> 24) & 0xFF) / 255.f; + result.r = ((color >> 16) & 0xFF) / 255.f; + result.g = ((color >> 8) & 0xFF) / 255.f; + result.b = ((color >> 0) & 0xFF) / 255.f; + return result; +} + +internal u32 +pack_color4(Vec4 color){ + u32 result = + ((u8)(color.a * 255) << 24) | + ((u8)(color.r * 255) << 16) | + ((u8)(color.g * 255) << 8) | + ((u8)(color.b * 255) << 0); + return result; +} + +internal Vec4 +rgba_to_hsla(Vec4 rgba){ + Vec4 hsla = {}; + real32 max, min, delta; + i32 maxc; + hsla.a = rgba.a; + max = rgba.r; min = rgba.r; + maxc = 0; + if (rgba.r < rgba.g){ + max = rgba.g; + maxc = 1; + } + if (rgba.b > max){ + max = rgba.b; + maxc = 2; + } + if (rgba.r > rgba.g){ + min = rgba.g; + } + if (rgba.b < min){ + min = rgba.b; + } + delta = max - min; + + hsla.z = (max + min) * .5f; + if (delta == 0){ + hsla.x = 0.f; + hsla.y = 0.f; + } + else{ + switch (maxc){ + case 0: + { + hsla.x = (rgba.g - rgba.b) / delta; + hsla.x += (rgba.g < rgba.b) * 6.f; + }break; + + case 1: + { + hsla.x = (rgba.b - rgba.r) / delta; + hsla.x += 2.f; + }break; + + case 2: + { + hsla.x = (rgba.r - rgba.g) / delta; + hsla.x += 4.f; + }break; + } + hsla.x *= (1/6.f); // * 60 / 360 + hsla.y = delta / (1.f - ABS(2.f*hsla.z - 1.f)); + } + + return hsla; +} + +internal Vec4 +hsla_to_rgba(Vec4 hsla){ + if (hsla.h >= 1.f) hsla.h = 0.f; + Vec4 rgba = {}; + real32 C, X, m; + i32 H; + rgba.a = hsla.a; + C = (1.f - ABS(2*hsla.z - 1.f)) * hsla.y; + X = C * (1.f-ABS(MOD(hsla.x*6.f, 2)-1.f)); + m = hsla.z - C*.5f; + H = FLOOR32(hsla.x * 6.f); + switch (H){ + case 0: + rgba.r = C; rgba.g = X; rgba.b = 0; + break; + + case 1: + rgba.r = X; rgba.g = C; rgba.b = 0; + break; + + case 2: + rgba.r = 0; rgba.g = C; rgba.b = X; + break; + + case 3: + rgba.r = 0; rgba.g = X; rgba.b = C; + break; + + case 4: + rgba.r = X; rgba.g = 0; rgba.b = C; + break; + + case 5: + rgba.r = C; rgba.g = 0; rgba.b = X; + break; + } + rgba.r += m; + rgba.g += m; + rgba.b += m; + return rgba; +} + +// BOTTOM + diff --git a/4ed_menu_view.cpp b/4ed_menu_view.cpp new file mode 100644 index 00000000..0f0d8a04 --- /dev/null +++ b/4ed_menu_view.cpp @@ -0,0 +1,84 @@ +/* + * Mr. 4th Dimention - Allen Webster + * + * 26.09.2015 + * + * File editing view for 4coder + * + */ + +// TOP + +struct Menu_View{ + View view_base; + Style *style; + Working_Set *working_set; + Delay *delay; + UI_State state; +}; + +inline Menu_View* +view_to_menu_view(View *view){ + Menu_View *result = 0; + if (view->type == VIEW_TYPE_MENU){ + result = (Menu_View*)view; + } + return result; +} + +internal i32 +step_draw_menu_view(Menu_View *view, Render_Target *target, i32_Rect rect, + Input_Summary *user_input, bool32 input_stage){ + i32 result = 0; + + UI_State state = + ui_state_init(&view->state, target, user_input, + view->style, view->working_set, input_stage); + + UI_Layout layout; + begin_layout(&layout, rect); + + i32 id = 0; + + do_label(&state, &layout, "Menu", 2.f); + + if (do_list_option_lit(++id, &state, &layout, "Theme Options")){ + delayed_action(view->delay, DACT_THEME_OPTIONS, {}, view->view_base.panel); + } + + if (ui_finish_frame(&view->state, &state, &layout, rect, 0, 0)){ + result = 1; + } + + return result; +} + +DO_VIEW_SIG(do_menu_view){ + i32 result = 0; + + Menu_View *menu_view = (Menu_View*)view; + switch (message){ + case VMSG_STEP: case VMSG_DRAW: + result = step_draw_menu_view(menu_view, target, rect, user_input, (message == VMSG_STEP)); + break; + } + + return result; +} + +internal Menu_View* +menu_view_init(View *view, Style *style, Working_Set *working_set, Delay *delay){ + view->type = VIEW_TYPE_INTERACTIVE; + view->do_view = do_menu_view; + + Menu_View *result; + result = (Menu_View*)view; + result->style = style; + result->working_set = working_set; + result->delay = delay; + return result; +} + +// BOTTOM + + diff --git a/4ed_meta.h b/4ed_meta.h new file mode 100644 index 00000000..f6905dde --- /dev/null +++ b/4ed_meta.h @@ -0,0 +1,182 @@ +/* + * Mr. 4th Dimention - Allen Webster + * + * 12.12.2014 + * + * Meta setup for project codename "4ed" + * + */ + +#ifndef FRED_META_H +#define FRED_META_H + +#include +#include + +typedef uint8_t u8; +typedef uint64_t u64; +typedef uint32_t u32; +typedef uint16_t u16; + +typedef int8_t i8; +typedef int64_t i64; +typedef int32_t i32; +typedef int16_t i16; + +typedef i32 bool32; +typedef i8 bool8; + +typedef float real32; +typedef double real64; + +#define internal static +#define globalvar static +#define persist static + +#define globalconst static const + +inline i32 +raw_ptr_dif(void *a, void *b) { return (i32)((u8*)a - (u8*)b); } + +#define COMP_ID_(a,b,c,d) (d << 24) | (c << 16) | (b << 8) | a +#define COMPOSE_ID(a,b,c,d) (COMP_ID_((a),(b),(c),(d))) + +#define S(X) #X +#define S_(X) S(X) +#define S__LINE__ S_(__LINE__) + +#if FRED_PRINT_DEBUG == 1 +internal void +_OutDbgStr(u8*); +# include +# if FRED_PRINT_DEBUG_FILE_LINE +# define FredDbg(con, size, ...) {_OutDbgStr((u8*)("FILE:"__FILE__"LINE:"S__LINE__"\n")); char msg[size]; sprintf(msg, __VA_ARGS__); _OutDbgStr((u8*)msg);} +# else +# define FredDbg(con, size, ...) {char msg[size]; sprintf(msg, __VA_ARGS__); _OutDbgStr((u8*)msg);} +# endif +#elif FRED_PRINT_DEBUG == 2 +# include +# if FRED_PRINT_DEBUG_FILE_LINE +# define FredDbg(con, size, ...) {fprintf((con)->log, ("FILE:"__FILE__"LINE:"S__LINE__"\n")); fprintf(__VA_ARGS__);} +# else +# define FredDbg(con, size, ...) {fprintf((con)->log, __VA_ARGS__);} +# endif +#else +# define FredDbg(con, size, ...) +#endif + +#if FRED_INTERNAL && FRED_FULL_ERRORS +# include +# define FatalErrorFormat(alt, size, ...) {char msg[size]; sprintf(msg, __VA_ARGS__); FatalError(msg);} +#else +# define FatalErrorFormat(alt, size, ...) {FatalError(alt);} +#endif + +#if FRED_SLOW +# define Assert(c) if(!(c)){*(int*)0 = 0;} +#else +# define Assert(c) +#endif + +#define TentativeAssert(c) Assert(c) + +#define FatalError(message) system_fatal_error((u8*)message) + +#define AllowLocal(name) (void)name +#define ArrayCount(array) (sizeof(array)/sizeof(array[0])) + +#define Swap(a,b) {auto t = a; a = b; b = t;} + +#define Min(a,b) (((a)<(b))?(a):(b)) +#define Max(a,b) (((a)>(b))?(a):(b)) + +#define TMax(t,v) globalconst t max_##t = v +TMax(u8, 255); +TMax(u16, 65535); +TMax(u32, 4294967295); +TMax(u64, 18446744073709551615); + +TMax(i8, 127); +TMax(i16, 32767); +TMax(i32, 2147483647); +TMax(i64, 9223372036854775807); +#undef TMax + +#define TMin(t) globalconst t min_##t = 0 +TMin(u8); +TMin(u16); +TMin(u32); +TMin(u64); +#undef TMin + +#define TMin(t,v) globalconst t min_##t = ((t)v) +TMin(i8, -0xF0); +TMin(i16, -0xF000); +TMin(i32, -0xF00000); +TMin(i64, -0xF0000000LL); +#undef TMin + +internal i32 +LargeRoundUp(i32 x, i32 granularity){ + i32 original_x = x; + x /= granularity; + x *= granularity; + if (x < original_x){ + x += granularity; + } + return x; +} + +#define Bit_0 (1 << 0) +#define Bit_1 (1 << 1) +#define Bit_2 (1 << 2) +#define Bit_3 (1 << 3) +#define Bit_4 (1 << 4) +#define Bit_5 (1 << 5) +#define Bit_6 (1 << 6) +#define Bit_7 (1 << 7) + +#define Bit_8 (1 << 8) +#define Bit_9 (1 << 9) +#define Bit_10 (1 << 10) +#define Bit_11 (1 << 11) +#define Bit_12 (1 << 12) +#define Bit_13 (1 << 13) +#define Bit_14 (1 << 14) +#define Bit_15 (1 << 15) + +#define Bit_16 (1 << 16) +#define Bit_17 (1 << 17) +#define Bit_18 (1 << 18) +#define Bit_19 (1 << 19) +#define Bit_20 (1 << 20) +#define Bit_21 (1 << 21) +#define Bit_22 (1 << 22) +#define Bit_23 (1 << 23) + +#define Bit_24 (1 << 24) +#define Bit_25 (1 << 25) +#define Bit_26 (1 << 26) +#define Bit_27 (1 << 27) +#define Bit_28 (1 << 28) +#define Bit_29 (1 << 29) +#define Bit_30 (1 << 30) +#define Bit_31 (1 << 31) + +#define Byte_0 (0xFFU) +#define Byte_1 (0xFFU << 8) +#define Byte_2 (0xFFU << 16) +#define Byte_3 (0xFFU << 24) +#define Byte_4 (0xFFU << 32) +#define Byte_5 (0xFFU << 40) +#define Byte_6 (0xFFU << 48) +#define Byte_7 (0xFFU << 56) + +#define bytes(n) (n) +#define Kbytes(n) (bytes(n) * 1024) +#define Mbytes(n) (Kbytes(n) * 1024) +#define Gbytes(n) (Mbytes((u64)n) * 1024) +#define Tbytes(n) (Gbytes((u64)n) * 1024) + +#endif + diff --git a/4ed_rendering.cpp b/4ed_rendering.cpp new file mode 100644 index 00000000..4fd93310 --- /dev/null +++ b/4ed_rendering.cpp @@ -0,0 +1,898 @@ + /* + * Mr. 4th Dimention - Allen Webster + * + * 12.17.2014 + * + * Rendering layer for project codename "4ed" + * + */ + +// TOP + +internal i32_Rect +rect_clamp_to_rect(i32_Rect rect, i32_Rect clamp_box){ + if (rect.x0 < clamp_box.x0) rect.x0 = clamp_box.x0; + if (rect.y0 < clamp_box.y0) rect.y0 = clamp_box.y0; + if (rect.x1 > clamp_box.x1) rect.x1 = clamp_box.x1; + if (rect.y1 > clamp_box.y1) rect.y1 = clamp_box.y1; + + return rect; +} + +inline i32_Rect +rect_clamp_to_rect(i32 left, i32 top, i32 right, i32 bottom, i32_Rect clamp_box){ + return rect_clamp_to_rect(i32R(left, top, right, bottom), clamp_box); +} + +inline i32_Rect +rect_from_target(Render_Target *target){ + return i32R(0, 0, target->width, target->height); +} + +#if SOFTWARE_RENDER +internal void +draw_clear(Render_Target *target, u32 color){ + u8 *pixel_line = (u8*)target->pixel_data; + for (i32 pixel_y = 0; pixel_y < target->height; ++pixel_y){ + u32 *pixel = (u32*)pixel_line; + for (i32 pixel_x = 0; pixel_x < target->width; ++pixel_x){ + *pixel++ = color; + } + pixel_line += target->pitch; + } +} + +internal void +draw_vertical_line(Render_Target *target, + i32 x, i32 top, i32 bottom, + u32 color){ + if (x >= 0 && x < target->width){ + if (top < 0){ + top = 0; + } + if (bottom >= target->height){ + bottom = target->height - 1; + } + + if (top <= bottom){ + i32 y_range = bottom - top; + u8 *pixel_line = (u8*)target->pixel_data + top*target->pitch; + for (i32 pixel_y = 0; pixel_y <= y_range; ++pixel_y){ + u32 *pixel = (u32*)pixel_line + x; + *pixel = color; + pixel_line += target->pitch; + } + } + } +} + +internal void +draw_horizontal_line(Render_Target *target, + i32 y, i32 left, i32 right, + u32 color){ + if (y >= 0 && y < target->height){ + if (left < 0){ + left = 0; + } + if (right >= target->width){ + right = target->width - 1; + } + + if (left <= right){ + i32 x_range = right - left; + u8 *pixel_line = (u8*)target->pixel_data + y*target->pitch; + u32 *pixel = (u32*)pixel_line + left; + for (i32 pixel_x = 0; pixel_x <= x_range; ++pixel_x){ + *pixel = color; + ++pixel; + } + } + } +} + +internal void +draw_rectangle_blend_2corner_unclamped(Render_Target *target, + Blit_Rect rect, u32 color){ + if (rect.x_start < rect.x_end && rect.y_start < rect.y_end){ + i32 y_range = rect.y_end - rect.y_start; + i32 x_range = rect.x_end - rect.x_start; + + u8 a,r,g,b; + a = (u8)(color >> 24); + r = (u8)(color >> 16); + g = (u8)(color >> 8); + b = (u8)(color >> 0); + + real32 blend = (a/255.f); + real32 pr, pg, pb; + pr = r*blend; + pg = g*blend; + pb = b*blend; + + real32 inv_blend = 1.f - blend; + + u8 *pixel_line = (u8*)target->pixel_data + rect.y_start*target->pitch; + for (i32 pixel_y = 0; pixel_y < y_range; ++pixel_y){ + u32 *pixel = (u32*)pixel_line + rect.x_start; + for (i32 pixel_x = 0; pixel_x < x_range; ++pixel_x){ + u8 dr,dg,db; + dr = (u8)(*pixel >> 16); + dg = (u8)(*pixel >> 8); + db = (u8)(*pixel >> 0); + + dr = (u8)(dr*inv_blend + pr); + dg = (u8)(dg*inv_blend + pg); + db = (u8)(db*inv_blend + pb); + + *pixel = (dr << 16) | (dg << 8) | (db); + ++pixel; + } + pixel_line += target->pitch; + } + } +} + +internal void +draw_rectangle_noblend_2corner_unclamped(Render_Target *target, + Blit_Rect rect, u32 color){ + if (rect.x_start < rect.x_end && rect.y_start < rect.y_end){ + i32 y_range = rect.y_end - rect.y_start; + i32 x_range = rect.x_end - rect.x_start; + + u8 *pixel_line = (u8*)target->pixel_data + rect.y_start*target->pitch; + for (i32 pixel_y = 0; pixel_y < y_range; ++pixel_y){ + u32 *pixel = (u32*)pixel_line + rect.x_start; + for (i32 pixel_x = 0; pixel_x < x_range; ++pixel_x){ + *pixel++ = color; + } + pixel_line += target->pitch; + } + } +} + +// NOTE(allen): uses of this can be replaced with draw_rectangle_2corner_unclamped +// if it is guaranteed that clip_box will be within the target area. +inline void +draw_rectangle_2corner_clipped(Render_Target *target, + i32 left, i32 top, i32 right, i32 bottom, + u32 color, Blit_Rect clip_box){ + clip_box = rect_clamp_to_rect(clip_box, rect_from_target(target)); + Blit_Rect rect = rect_clamp_to_rect(left, top, right, bottom, clip_box); + draw_rectangle_noblend_2corner_unclamped(target, rect, color); +} + +inline void +draw_rectangle_2corner(Render_Target *target, + i32 left, i32 top, i32 right, i32 bottom, + u32 color){ + Blit_Rect rect = rect_clamp_to_rect(left, top, right, bottom, rect_from_target(target)); + draw_rectangle_noblend_2corner_unclamped(target, rect, color); +} + +inline void +draw_rectangle_clipped(Render_Target *target, + i32 x, i32 y, i32 w, i32 h, + u32 color, Blit_Rect clip_box){ + draw_rectangle_2corner_clipped(target, x, y, x+w, y+h, color, clip_box); +} + +inline void +draw_rectangle(Render_Target *target, + i32 x, i32 y, i32 w, i32 h, + u32 color){ + draw_rectangle_2corner(target, x, y, x+w, y+h, color); +} + +internal void +draw_rectangle_outline_unclamped(Render_Target *target, + i32 x, i32 y, i32 w, i32 h, + u32 color, Blit_Rect rect){ + if (rect.x_start <= rect.x_end && rect.y_start <= rect.y_end){ + if (rect.y_start == y){ + draw_horizontal_line(target, + rect.y_start, rect.x_start, rect.x_end-1, + color); + } + + if (rect.y_end == y+h){ + draw_horizontal_line(target, + rect.y_end-1, rect.x_start, rect.x_end-1, + color); + } + + if (rect.x_start == x){ + draw_vertical_line(target, + rect.x_start, rect.y_start, rect.y_end-1, + color); + } + + if (rect.x_end == x+w){ + draw_vertical_line(target, + rect.x_end-1, rect.y_start, rect.y_end-1, + color); + } + } +} + +internal void +draw_rectangle_outline_clipped(Render_Target *target, + i32 x, i32 y, i32 w, i32 h, + u32 color, Blit_Rect clip_box){ + clip_box = rect_clamp_to_rect(clip_box, rect_from_target(target)); + Blit_Rect rect = rect_clamp_to_rect(x, y, x+w, y+h, clip_box); + draw_rectangle_outline_unclamped(target, x, y, w, h, color, rect); +} + +internal void +draw_rectangle_outline(Render_Target *target, + i32 x, i32 y, i32 w, i32 h, + u32 color){ + Blit_Rect rect = rect_clamp_to_rect(x, y, x+w, y+h, rect_from_target(target)); + draw_rectangle_outline_unclamped(target, x, y, w, h, color, rect); +} + +// TODO(allen): eliminate this? +internal i32 +font_init(){ + return 1; +} + +inline internal i32 +font_predict_size(i32 pt_size){ + return pt_size*pt_size*128; +} + +internal i32 +font_load(Font *font_out, i32 pt_size, + void *font_block, i32 font_block_size, + i32 *memory_used_out){ + i32 result = 1; + File_Data file; + file = system_load_file((u8*)"liberation-mono.ttf"); + + if (!file.data){ + result = 0; + } + + else{ + stbtt_fontinfo font; + if (!stbtt_InitFont(&font, (u8*)file.data, 0)){ + result = 0; + } + else{ + i32 ascent, descent, line_gap; + real32 scale; + + stbtt_GetFontVMetrics(&font, &ascent, &descent, &line_gap); + scale = stbtt_ScaleForPixelHeight(&font, (real32)pt_size); + + real32 scaled_ascent, scaled_descent, scaled_line_gap; + + scaled_ascent = scale*ascent; + scaled_descent = scale*descent; + scaled_line_gap = scale*line_gap; + + font_out->height = (i32)(scaled_ascent - scaled_descent + scaled_line_gap); + font_out->ascent = (i32)(scaled_ascent); + font_out->descent = (i32)(scaled_descent); + font_out->line_skip = (i32)(scaled_line_gap); + + i32 max_advance = 0; + + bool32 block_full = 0, block_overfull = 0; + u8 *memory_cursor = (u8*)font_block; + for (u16 code_point = 0; code_point < 128; ++code_point){ + i32 glyph_index; + + if (block_full){ + block_overfull = 1; + font_out->glyphs[code_point] = {}; + continue; + } + + else{ + glyph_index = stbtt_FindGlyphIndex(&font, code_point); + if (glyph_index != 0){ + font_out->glyphs[code_point].exists = 1; + + i32 left, right, top, bottom; + stbtt_GetGlyphBitmapBox(&font, glyph_index, scale, scale, &left, &top, &right, &bottom); + + i32 glyph_width, glyph_height; + i32 data_width, data_height; + glyph_width = right - left; + glyph_height = bottom - top; + data_width = glyph_width + 2; + data_height = glyph_height + 2; + + font_out->glyphs[code_point].minx = left; + font_out->glyphs[code_point].maxx = right; + font_out->glyphs[code_point].miny = top; + font_out->glyphs[code_point].maxy = bottom; + font_out->glyphs[code_point].width = data_width; + font_out->glyphs[code_point].height = data_height; + + i32 advance, left_bearing; + stbtt_GetGlyphHMetrics(&font, glyph_index, &advance, &left_bearing); + + font_out->glyphs[code_point].left_shift = (i32)(scale*left_bearing); + + if (advance > max_advance){ + max_advance = advance; + } + + u8 *data = memory_cursor; + memory_cursor = memory_cursor + data_width*data_height; + + i32 size_after_glyph = (i32)((u8*)memory_cursor - (u8*)font_block); + if (size_after_glyph >= font_block_size){ + block_full = 1; + if (size_after_glyph > font_block_size){ + block_overfull = 1; + } + } + else{ + font_out->glyphs[code_point].data = data; + u8 *data_start = data + data_width + 1; + stbtt_MakeGlyphBitmap(&font, data_start, + glyph_width, glyph_height, data_width, + scale, scale, + glyph_index); + } + } + else{ + font_out->glyphs[code_point] = {}; + } + } + } + + font_out->advance = Ceil(max_advance*scale); + } + system_free_file(file); + } + + return result; +} + +internal void +font_draw_glyph_subpixel(Render_Target *target, + Font *font, u16 character, + real32 x, real32 y, u32 color, + Blit_Rect clip){ + if (clip.x_start <= clip.x_end && clip.y_start <= clip.y_end){ + Glyph_Data glyph = font->glyphs[character]; + + Vec2 s_origin = V2(x - 1.f, y - 1.f); + + i32 left_most = (i32)(x); + i32 top_most = (i32)(y); + + real32 xe = x + glyph.width - 2; + real32 ye = y + glyph.height - 2; + i32 right_most = (i32)(xe)+(xe>0)*((i32)xe != xe); + i32 bottom_most = (i32)(ye)+(ye>0)*((i32)ye != ye); + + if (left_most < clip.x_start){ + left_most = clip.x_start; + } + + if (top_most < clip.y_start){ + top_most = clip.y_start; + } + + if (right_most >= clip.x_end){ + right_most = clip.x_end - 1; + } + + if (bottom_most >= clip.y_end){ + bottom_most = clip.y_end - 1; + } + + u8 *dest_data = (u8*)target->pixel_data; + u8 *src_data = (u8*)glyph.data; + + i32 width = glyph.width; + i32 target_pitch = target->pitch; + + real32 inv_255 = 1.f / 255.f; + + real32 cr,cg,cb,ca; + cb = (real32)((color) & 0xFF); + cg = (real32)((color >> 8) & 0xFF); + cr = (real32)((color >> 16) & 0xFF); + ca = (real32)((color >> 24) & 0xFF); + + i32 lox_start = (i32)(left_most - s_origin.x); + i32 loy_start = (i32)(top_most - s_origin.y); + + real32 lX = left_most - s_origin.x - lox_start; + real32 lY = top_most - s_origin.y - loy_start; + + real32 inv_lX = 1.f - lX; + real32 inv_lY = 1.f - lY; + + i32 loy = loy_start * width; + + u8 *dest_line = (dest_data + top_most*target_pitch); + for (i32 y = top_most; y <= bottom_most; ++y){ + u8 src_a[4]; + + i32 lox_loy = lox_start + loy; + src_a[0] = *(src_data + (lox_loy)); + src_a[2] = *(src_data + (lox_loy + width)); + + u32 *dest_pixel = (u32*)(dest_line) + left_most; + + for (i32 x = left_most; x <= right_most; ++x){ + src_a[1] = *(src_data + (lox_loy + 1)); + src_a[3] = *(src_data + (lox_loy + 1 + width)); + + real32 alpha = (src_a[0]*(inv_lX) + src_a[1]*(lX))*(inv_lY) + + (src_a[2]*(inv_lX) + src_a[3]*(lX))*(lY); + alpha *= inv_255; + + u32 dp = *dest_pixel; + + real32 dr,dg,db,da; + db = (real32)((dp) & 0xFF); + dg = (real32)((dp >> 8) & 0xFF); + dr = (real32)((dp >> 16) & 0xFF); + da = (real32)((dp >> 24) & 0xFF); + + db = db + (cb - db)*alpha; + dg = dg + (cg - dg)*alpha; + dr = dr + (cr - dr)*alpha; + da = da + (ca - da)*alpha; + + *dest_pixel = ((u8)db) | (((u8)dg) << 8) | (((u8)dr) << 16) | (((u8)da) << 24); + + ++dest_pixel; + ++lox_loy; + + src_a[0] = src_a[1]; + src_a[2] = src_a[3]; + } + loy += width; + dest_line += target_pitch; + } + } +} + +internal void +font_draw_glyph(Render_Target *target, Font *font, u16 character, + real32 x, real32 y, u32 color){ + Glyph_Data glyph = font->glyphs[character]; + + i32 left = glyph.minx; + i32 right = glyph.maxx; + i32 width = right - left; + + real32 x_shift, y_shift; + x_shift = glyph.left_shift + (real32)width / font->advance; + y_shift = (real32)font->ascent + glyph.miny; + + x += x_shift; + y += y_shift; + + i32 xi, yi; + xi = (i32)(x); + yi = (i32)(y); + + Blit_Rect rect = rect_clamp_to_rect(xi, yi, xi+glyph.width-1, yi+glyph.height-1, rect_from_target(target)); + font_draw_glyph_subpixel(target, font, character, x, y, color, rect); +} + +internal void +font_draw_glyph_clipped(Render_Target *target, + Font *font, u16 character, + real32 x, real32 y, u32 color, + Blit_Rect clip_box){ + Glyph_Data glyph = font->glyphs[character]; + + i32 left = glyph.minx; + i32 right = glyph.maxx; + i32 width = right - left; + + real32 x_shift, y_shift; + x_shift = glyph.left_shift + (real32)width / font->advance; + y_shift = (real32)font->ascent + glyph.miny; + + x += x_shift; + y += y_shift; + + i32 xi, yi; + xi = (i32)(x); + yi = (i32)(y); + + clip_box = rect_clamp_to_rect(clip_box, rect_from_target(target)); + Blit_Rect rect = rect_clamp_to_rect(xi, yi, xi+glyph.width-1, yi+glyph.height-1, clip_box); + font_draw_glyph_subpixel(target, font, character, x, y, color, rect); +} + +#else + +inline void +draw_set_clip(Render_Target *target, i32_Rect clip_box){ + glScissor(clip_box.x0, + target->height - clip_box.y1, + clip_box.x1 - clip_box.x0, + clip_box.y1 - clip_box.y0); +} + +inline void +draw_push_clip(Render_Target *target, i32_Rect clip_box){ + Assert(target->clip_top == -1 || + fits_inside(clip_box, target->clip_boxes[target->clip_top])); + Assert(target->clip_top+1 < ArrayCount(target->clip_boxes)); + target->clip_boxes[++target->clip_top] = clip_box; + draw_set_clip(target, clip_box); +} + +inline void +draw_pop_clip(Render_Target *target){ + Assert(target->clip_top > 0); + --target->clip_top; + draw_set_clip(target, target->clip_boxes[target->clip_top]); +} + +inline void +draw_bind_texture(Render_Target *target, i32 texid){ + if (target->bound_texture != texid){ + glBindTexture(GL_TEXTURE_2D, texid); + target->bound_texture = texid; + } +} + +internal void +draw_set_color(Render_Target *target, u32 color){ + if (target->color != color){ + target->color = color; + Vec4 c = unpack_color4(color); + glColor4f(c.r, c.g, c.b, c.a); + } +} + +internal void +draw_rectangle(Render_Target *target, i32_Rect rect, u32 color){ + draw_set_color(target, color); + draw_bind_texture(target, 0); + glBegin(GL_QUADS); + { + glVertex2i(rect.x0, rect.y0); + glVertex2i(rect.x0, rect.y1); + glVertex2i(rect.x1, rect.y1); + glVertex2i(rect.x1, rect.y0); + } + glEnd(); +} + +internal void +draw_rectangle(Render_Target *target, real32_Rect rect, u32 color){ + draw_set_color(target, color); + draw_bind_texture(target, 0); + glBegin(GL_QUADS); + { + glVertex2f(rect.x0, rect.y0); + glVertex2f(rect.x0, rect.y1); + glVertex2f(rect.x1, rect.y1); + glVertex2f(rect.x1, rect.y0); + } + glEnd(); +} + +internal void +draw_triangle_3corner(Render_Target *target, + real32 x0, real32 y0, + real32 x1, real32 y1, + real32 x2, real32 y2, + u32 color){ + draw_set_color(target, color); + draw_bind_texture(target, 0); + glBegin(GL_TRIANGLES); + { + glVertex2f(x0, y0); + glVertex2f(x1, y1); + glVertex2f(x2, y2); + } + glEnd(); +} + +internal void +draw_gradient_2corner_clipped(Render_Target *target, real32_Rect rect, + Vec4 color_left, Vec4 color_right){ + Vec4 cl = color_left; + Vec4 cr = color_right; + + draw_bind_texture(target, 0); + glBegin(GL_QUADS); + { + glColor4f(cl.r, cl.g, cl.b, cl.a); + glVertex2f(rect.x0, rect.y0); + glVertex2f(rect.x0, rect.y1); + + glColor4f(cr.r, cr.g, cr.b, cr.a); + glVertex2f(rect.x1, rect.y1); + glVertex2f(rect.x1, rect.y0); + } + glEnd(); +} + +inline void +draw_gradient_2corner_clipped(Render_Target *target, real32 l, real32 t, real32 r, real32 b, + Vec4 color_left, Vec4 color_right){ + draw_gradient_2corner_clipped(target, real32R(l,t,r,b), color_left, color_right); +} + +internal void +draw_rectangle_outline(Render_Target *target, real32_Rect rect, u32 color){ + real32_Rect r; + r.x0 = rect.x0 + .5f; + r.y0 = rect.y0 + .5f; + r.x1 = rect.x1 - .5f; + r.y1 = rect.y1 - .5f; + + draw_set_color(target, color); + draw_bind_texture(target, 0); + glBegin(GL_LINE_STRIP); + { + glVertex2f(r.x0, r.y0); + glVertex2f(r.x1, r.y0); + glVertex2f(r.x1, r.y1); + glVertex2f(r.x0, r.y1); + glVertex2f(r.x0, r.y0); + } + glEnd(); +} + +inline void +draw_rectangle_outline(Render_Target *target, i32_Rect rect, u32 color){ + draw_rectangle_outline(target, real32R(rect), color); +} + +internal void +draw_margin(Render_Target *target, i32_Rect outer, i32_Rect inner, u32 color){ + draw_rectangle(target, i32R(outer.x0, outer.y0, outer.x1, inner.y0), color); + draw_rectangle(target, i32R(outer.x0, inner.y1, outer.x1, outer.y1), color); + draw_rectangle(target, i32R(outer.x0, inner.y0, inner.x0, inner.y1), color); + draw_rectangle(target, i32R(inner.x1, inner.y0, outer.x1, inner.y1), color); +} + +// TODO(allen): eliminate this? +internal i32 +font_init(){ + return 1; +} + +inline internal i32 +font_predict_size(i32 pt_size){ + return pt_size*pt_size*128; +} + +internal i32 +font_load(Font *font_out, char *filename, i32 pt_size, + void *font_block, i32 font_block_size, + i32 *memory_used_out, i32 tab_width){ + i32 result = 1; + File_Data file; + file = system_load_file((u8*)filename); + + if (!file.data){ + result = 0; + } + + else{ + stbtt_fontinfo font; + if (!stbtt_InitFont(&font, (u8*)file.data, 0)){ + result = 0; + } + else{ + i32 ascent, descent, line_gap; + real32 scale; + + stbtt_GetFontVMetrics(&font, &ascent, &descent, &line_gap); + scale = stbtt_ScaleForPixelHeight(&font, (real32)pt_size); + + real32 scaled_ascent, scaled_descent, scaled_line_gap; + + scaled_ascent = scale*ascent; + scaled_descent = scale*descent; + scaled_line_gap = scale*line_gap; + + font_out->height = (i32)(scaled_ascent - scaled_descent + scaled_line_gap); + font_out->ascent = (i32)(scaled_ascent); + font_out->descent = (i32)(scaled_descent); + font_out->line_skip = (i32)(scaled_line_gap); + + u8 *memory_cursor = (u8*)font_block; + Assert(pt_size*pt_size*128 <= font_block_size); + + i32 tex_width, tex_height; + tex_width = pt_size*128; + tex_height = pt_size*2; + + font_out->tex_width = tex_width; + font_out->tex_height = tex_height; + + if (stbtt_BakeFontBitmap((u8*)file.data, 0, (real32)pt_size, + memory_cursor, tex_width, tex_height, 0, 128, font_out->chardata) <= 0){ + result = 0; + } + + else{ + GLuint font_tex; + glGenTextures(1, &font_tex); + glBindTexture(GL_TEXTURE_2D, font_tex); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, tex_width, tex_height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, memory_cursor); + + font_out->tex = font_tex; + glBindTexture(GL_TEXTURE_2D, 0); + } + + font_out->chardata['\r'] = font_out->chardata[' ']; + font_out->chardata['\n'] = font_out->chardata[' ']; + font_out->chardata['\t'] = font_out->chardata[' ']; + font_out->chardata['\t'].xadvance *= tab_width; + + i32 max_advance = 0; + for (u16 code_point = 0; code_point < 128; ++code_point){ + if (stbtt_FindGlyphIndex(&font, code_point) != 0){ + font_out->glyphs[code_point].exists = 1; + i32 advance = CEIL32(font_out->chardata[code_point].xadvance); + if (max_advance < advance){ + max_advance = advance; + } + } + } + font_out->advance = max_advance - 1; + } + system_free_file(file); + } + + return result; +} + +internal void +font_set_tabwidth(Font *font, i32 tab_width){ + font->chardata['\t'].xadvance *= font->chardata[' '].xadvance * tab_width; +} + +internal void +font_draw_glyph_mono(Render_Target *target, Font *font, u16 character, + real32 x, real32 y, real32 advance, u32 color){ + real32 x_shift, y_shift; + i32 left = font->chardata[character].x0; + i32 right = font->chardata[character].x1; + i32 width = (right - left); + x_shift = (real32)(advance - width) * .5f - font->chardata[character].xoff; + y_shift = (real32)font->ascent; + + x += x_shift; + y += y_shift; + + stbtt_aligned_quad q; + stbtt_GetBakedQuadUnrounded(font->chardata, font->tex_width, font->tex_height, character, &x, &y, &q, 1); + + draw_set_color(target, color); + draw_bind_texture(target, font->tex); + glBegin(GL_QUADS); + { + glTexCoord2f(q.s0, q.t1); glVertex2f(q.x0, q.y1); + glTexCoord2f(q.s1, q.t1); glVertex2f(q.x1, q.y1); + glTexCoord2f(q.s1, q.t0); glVertex2f(q.x1, q.y0); + glTexCoord2f(q.s0, q.t0); glVertex2f(q.x0, q.y0); + } + glEnd(); +} + +inline void +font_draw_glyph_mono(Render_Target *target, Font *font, u16 character, + real32 x, real32 y, u32 color){ + font_draw_glyph_mono(target, font, character, x, y, (real32)font->advance, color); +} + +internal void +font_draw_glyph(Render_Target *target, Font *font, u16 character, + real32 x, real32 y, u32 color){ + real32 x_shift, y_shift; + x_shift = 0; + y_shift = (real32)font->ascent; + + x += x_shift; + y += y_shift; + + stbtt_aligned_quad q; + stbtt_GetBakedQuadUnrounded(font->chardata, font->tex_width, font->tex_height, character, &x, &y, &q, 1); + + draw_set_color(target, color); + draw_bind_texture(target, font->tex); + glBegin(GL_QUADS); + { + glTexCoord2f(q.s0, q.t1); glVertex2f(q.x0, q.y1); + glTexCoord2f(q.s1, q.t1); glVertex2f(q.x1, q.y1); + glTexCoord2f(q.s1, q.t0); glVertex2f(q.x1, q.y0); + glTexCoord2f(q.s0, q.t0); glVertex2f(q.x0, q.y0); + } + glEnd(); +} + +inline real32 +font_get_glyph_width(Font *font, u16 character){ + return font->chardata[character].xadvance; +} + +internal i32 +draw_string(Render_Target *target, Font *font, char *str, + i32 x_, i32 y, u32 color){ + real32 x = (real32)x_; + for (i32 i = 0; str[i]; ++i){ + char c = str[i]; + font_draw_glyph(target, font, c, + x, (real32)y, color); + x += font_get_glyph_width(font, c); + } + return CEIL32(x); +} + +internal real32 +draw_string_mono(Render_Target *target, Font *font, char *str, + real32 x, real32 y, real32 advance, u32 color){ + for (i32 i = 0; str[i]; ++i){ + font_draw_glyph_mono(target, font, str[i], + x, y, advance, color); + x += advance; + } + return x; +} + +internal i32 +draw_string(Render_Target *target, Font *font, String str, + i32 x_, i32 y, u32 color){ + real32 x = (real32)x_; + for (i32 i = 0; i < str.size; ++i){ + char c = str.str[i]; + font_draw_glyph(target, font, c, + x, (real32)y, color); + x += font_get_glyph_width(font, c); + } + return CEIL32(x); +} + +internal real32 +draw_string_mono(Render_Target *target, Font *font, String str, + real32 x, real32 y, real32 advance, u32 color){ + for (i32 i = 0; i < str.size; ++i){ + font_draw_glyph_mono(target, font, str.str[i], + x, y, advance, color); + x += advance; + } + return x; +} + +internal real32 +font_get_max_width(Font *font, char *characters){ + stbtt_bakedchar *chardata = font->chardata; + real32 cx, x = 0; + for (i32 i = 0; characters[i]; ++i){ + cx = chardata[characters[i]].xadvance; + if (x < cx) x = cx; + } + return x; +} + +internal real32 +font_get_string_width(Font *font, String string){ + real32 result = 0; + for (i32 i = 0; i < string.size; ++i){ + font_get_glyph_width(font, string.str[i]); + } + return result; +} + +#endif + +// BOTTOM diff --git a/4ed_rendering.h b/4ed_rendering.h new file mode 100644 index 00000000..5ed6ae5b --- /dev/null +++ b/4ed_rendering.h @@ -0,0 +1,54 @@ +/* + * Mr. 4th Dimention - Allen Webster + * + * 12.17.2014 + * + * Rendering layer for project codename "4ed" + * + */ + +#ifndef FRED_RENDERING_H +#define FRED_RENDERING_H + +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" + +#if SOFTWARE_RENDER +struct Glyph_Data{ + void *data; + i32 width, height; + i32 minx, maxx, miny, maxy; + i32 left_shift; + bool32 exists; +}; + +struct Font{ + Glyph_Data glyphs[128]; + i32 height, ascent, descent, line_skip; + i32 advance; +}; +#else +struct Glyph_Data{ +#if 0 + i32 width, height; + i32 minx, maxx, miny, maxy; + i32 left_shift; +#endif + bool32 exists; +}; + +struct Font{ + char name_[24]; + String name; + bool32 loaded; + + Glyph_Data glyphs[128]; + stbtt_bakedchar chardata[128]; + i32 height, ascent, descent, line_skip; + i32 advance; + u32 tex; + i32 tex_width, tex_height; +}; +#endif + +#endif diff --git a/4ed_style.cpp b/4ed_style.cpp new file mode 100644 index 00000000..9ea3f79d --- /dev/null +++ b/4ed_style.cpp @@ -0,0 +1,391 @@ +/* + * Mr. 4th Dimention - Allen Webster + * + * 28.08.2015 + * + * Styles for 4coder + * + */ + +// TOP + +struct P4C_Page_Header{ + i32 size; + u32 id; +}; + +#define P4C_STYLE_ID COMPOSE_ID('s', 't', 'y', 'l') + +struct Style_Page_Header{ + i32 version; + i32 count; +}; + +struct Style_Main_Data_v1{ + u32 back_color; + u32 margin_color; + u32 margin_active_color; + u32 cursor_color; + u32 at_cursor_color; + u32 highlight_color; + u32 at_highlight_color; + u32 mark_color; + u32 default_color; + u32 comment_color; + u32 keyword_color; + u32 constant_color; + u32 special_character_color; + u32 highlight_junk_color; + u32 highlight_white_color; + u32 paste_color; + Interactive_Style file_info_style; +}; + +struct Style_File_Format_v1{ + i32 name_size; + char name[24]; + i32 font_name_size; + char font_name[24]; + Style_Main_Data_v1 main; +}; + +struct Style_Main_Data_v2{ + u32 back_color; + u32 margin_color; + u32 margin_active_color; + u32 cursor_color; + u32 at_cursor_color; + u32 highlight_color; + u32 at_highlight_color; + u32 mark_color; + u32 default_color; + u32 comment_color; + u32 keyword_color; + u32 str_constant_color; + u32 char_constant_color; + u32 int_constant_color; + u32 float_constant_color; + u32 bool_constant_color; + u32 preproc_color; + u32 include_color; + u32 special_character_color; + u32 highlight_junk_color; + u32 highlight_white_color; + u32 paste_color; + Interactive_Style file_info_style; +}; + +struct Style_File_Format_v2{ + i32 name_size; + char name[24]; + i32 font_name_size; + char font_name[24]; + Style_Main_Data_v2 main; +}; + +struct Style_Main_Data_v3{ + u32 back_color; + u32 margin_color; + u32 margin_hover_color; + u32 margin_active_color; + u32 cursor_color; + u32 at_cursor_color; + u32 highlight_color; + u32 at_highlight_color; + u32 mark_color; + u32 default_color; + u32 comment_color; + u32 keyword_color; + u32 str_constant_color; + u32 char_constant_color; + u32 int_constant_color; + u32 float_constant_color; + u32 bool_constant_color; + u32 preproc_color; + u32 include_color; + u32 special_character_color; + u32 highlight_junk_color; + u32 highlight_white_color; + u32 paste_color; + Interactive_Style file_info_style; +}; + +struct Style_File_Format_v3{ + i32 name_size; + char name[24]; + i32 font_name_size; + char font_name[24]; + Style_Main_Data_v3 main; +}; + +struct Style{ + char name_[24]; + String name; + Font *font; + Style_Main_Data_v3 main; + bool32 font_changed; +}; + +struct Style_Library{ + Style styles[64]; + i32 count, max; +}; + +struct Font_Set{ + Font *fonts; + i32 count, max; +}; + +internal void +style_copy(Style *dst, Style *src){ + *dst = *src; + dst->name.str = dst->name_; +} + +internal void +style_set_name(Style *style, String name){ + i32 count = ArrayCount(style->name_); + style->name_[count - 1] = 0; + style->name = make_string(style->name_, 0, count - 1); + copy(&style->name, name); +} + +internal Font* +font_set_extract(Font_Set *fonts, char *name, i32 size){ + String n = make_string(name, size); + i32 count = fonts->count; + Font *result = 0; + Font *font = fonts->fonts; + for (i32 i = 0; i < count; ++i, ++font){ + if (match(n, font->name)){ + result = font; + break; + } + } + return result; +} + +internal void +style_form_convert(Style_File_Format_v2 *o, Style_File_Format_v1 *i){ + o->name_size = i->name_size; + memcpy(o->name, i->name, i->name_size); + o->font_name_size = i->font_name_size; + memcpy(o->font_name, i->font_name, i->font_name_size); + + o->main.back_color = i->main.back_color; + o->main.margin_color = i->main.margin_color; + o->main.margin_active_color = i->main.margin_active_color; + o->main.cursor_color = i->main.cursor_color; + o->main.at_cursor_color = i->main.at_cursor_color; + o->main.highlight_color = i->main.highlight_color; + o->main.at_highlight_color = i->main.at_highlight_color; + o->main.mark_color = i->main.mark_color; + o->main.default_color = i->main.default_color; + o->main.comment_color = i->main.comment_color; + o->main.keyword_color = i->main.keyword_color; + o->main.str_constant_color = i->main.constant_color; + o->main.char_constant_color = i->main.constant_color; + o->main.int_constant_color = i->main.constant_color; + o->main.float_constant_color = i->main.constant_color; + o->main.bool_constant_color = i->main.constant_color; + o->main.include_color = i->main.constant_color; + o->main.preproc_color = i->main.default_color; + o->main.special_character_color = i->main.special_character_color; + o->main.highlight_junk_color = i->main.highlight_junk_color; + o->main.highlight_white_color = i->main.highlight_white_color; + o->main.paste_color = i->main.paste_color; + o->main.file_info_style = i->main.file_info_style; +} + +internal void +style_form_convert(Style_File_Format_v3 *o, Style_File_Format_v2 *i){ + o->name_size = i->name_size; + memcpy(o->name, i->name, i->name_size); + o->font_name_size = i->font_name_size; + memcpy(o->font_name, i->font_name, i->font_name_size); + + o->main.back_color = i->main.back_color; + o->main.margin_color = i->main.margin_color; + o->main.margin_active_color = i->main.margin_active_color; + + o->main.margin_hover_color = color_blend(i->main.margin_color, .5f, i->main.margin_active_color); + + o->main.cursor_color = i->main.cursor_color; + o->main.at_cursor_color = i->main.at_cursor_color; + o->main.highlight_color = i->main.highlight_color; + o->main.at_highlight_color = i->main.at_highlight_color; + o->main.mark_color = i->main.mark_color; + o->main.default_color = i->main.default_color; + o->main.comment_color = i->main.comment_color; + o->main.keyword_color = i->main.keyword_color; + o->main.str_constant_color = i->main.str_constant_color; + o->main.char_constant_color = i->main.char_constant_color; + o->main.int_constant_color = i->main.int_constant_color; + o->main.float_constant_color = i->main.float_constant_color; + o->main.bool_constant_color = i->main.bool_constant_color; + o->main.include_color = i->main.include_color; + o->main.preproc_color = i->main.preproc_color; + o->main.special_character_color = i->main.special_character_color; + o->main.highlight_junk_color = i->main.highlight_junk_color; + o->main.highlight_white_color = i->main.highlight_white_color; + o->main.paste_color = i->main.paste_color; + o->main.file_info_style = i->main.file_info_style; +} + +typedef Style_Main_Data_v3 Style_Main_Data; +typedef Style_File_Format_v3 Style_File_Format; + +internal void +style_format_for_use(Font_Set *fonts, Style *out, Style_File_Format *style){ + out->name = make_string(out->name_, 0, ArrayCount(out->name_) - 1); + out->name_[ArrayCount(out->name_) - 1] = 0; + copy(&out->name, style->name); + out->font = font_set_extract(fonts, style->font_name, style->font_name_size); + out->main = style->main; +} + +inline void +style_format_for_use(Font_Set *fonts, Style *out, Style_File_Format_v1 *style){ + Style_File_Format_v2 form2; + Style_File_Format form; + style_form_convert(&form2, style); + style_form_convert(&form, &form2); + style_format_for_use(fonts, out, &form); +} + +inline void +style_format_for_use(Font_Set *fonts, Style *out, Style_File_Format_v2 *style){ + Style_File_Format form; + style_form_convert(&form, style); + style_format_for_use(fonts, out, &form); +} + +internal bool32 +style_library_import(u8 *filename, Font_Set *fonts, Style *out, i32 max, + i32 *count_opt, i32 *total_opt = 0){ + bool32 result = 1; + File_Data file = system_load_file(filename); + if (!file.data){ + result = 0; + } + else{ + void *cursor = file.data; + i32 to_read = 0; + + { + P4C_Page_Header *h = (P4C_Page_Header*)cursor; + if (h->id != P4C_STYLE_ID){ + result = 0; + goto early_exit; + } + cursor = h+1; + } + + Style_Page_Header *h = (Style_Page_Header*)cursor; + to_read = h->count; + cursor = h+1; + + if (total_opt) *total_opt = to_read; + if (to_read > max) to_read = max; + if (count_opt) *count_opt = to_read; + + switch (h->version){ + case 1: + { + Style_File_Format_v1 *in = (Style_File_Format_v1*)cursor; + for (i32 i = 0; i < to_read; ++i){ + style_format_for_use(fonts, out++, in++); + } + }break; + case 2: + { + Style_File_Format_v2 *in = (Style_File_Format_v2*)cursor; + for (i32 i = 0; i < to_read; ++i){ + style_format_for_use(fonts, out++, in++); + } + }break; + case 3: + { + Style_File_Format_v3 *in = (Style_File_Format_v3*)cursor; + for (i32 i = 0; i < to_read; ++i){ + style_format_for_use(fonts, out++, in++); + } + }break; + default: result = 0; break; + } + +early_exit: + system_free_file(file); + } + + return result; +} + +internal bool32 +style_library_add(Style_Library *library, Style *style){ + bool32 result = 0; + i32 count = library->count; + String my_name = style->name; + Style *ostyle = library->styles; + Style *out = 0; + // TODO(allen): hashtable for name lookup? + for (i32 i = 0; i < count; ++i, ++ostyle){ + if (match(my_name, ostyle->name)){ + out = ostyle; + break; + } + } + if (!out && count < library->max){ + out = library->styles + library->count++; + } + if (out){ + style_copy(out, style); + result = 1; + } + return result; +} + +internal Style_File_Format +style_format_for_file(Style *style){ + Style_File_Format result; + Font *font = style->font; + result.name_size = style->name.size; + memcpy(result.name, style->name.str, ArrayCount(result.name)); + result.font_name_size = font->name.size; + memcpy(result.font_name, font->name.str, ArrayCount(result.font_name)); + result.main = style->main; + return result; +} + +internal void +style_library_export(u8 *filename, Style **styles, i32 count){ + i32 size = count*sizeof(Style_File_Format) + + sizeof(P4C_Page_Header) + sizeof(Style_Page_Header); + void *data = system_get_memory(size); + void *cursor = data; + + { + P4C_Page_Header *h = (P4C_Page_Header*)cursor; + h->size = size - sizeof(P4C_Page_Header); + h->id = P4C_STYLE_ID; + cursor = h+1; + } + + { + Style_Page_Header *h = (Style_Page_Header*)cursor; + h->version = 1; + h->count = count; + cursor = h+1; + } + + Style_File_Format *out = (Style_File_Format*)cursor; + Style **in = styles; + for (i32 i = 0; i < count; ++i){ + *out++ = style_format_for_file(*in++); + } + system_save_file(filename, data, size); + system_free_memory(data); +} + +// BOTTOM + diff --git a/4ed_system.h b/4ed_system.h new file mode 100644 index 00000000..71f6b4d5 --- /dev/null +++ b/4ed_system.h @@ -0,0 +1,154 @@ +/* + * Mr. 4th Dimention - Allen Webster + * + * 21.01.2014 + * + * System functions for project codename "4ed" + * + */ + +// TODO(allen): This should either be a String or it should be improved +// to handle 64-bit sized files. Staying in this state, however, is unacceptable. +struct File_Data{ + void *data; + u32 size; +}; + +struct Time_Stamp{ + u64 time; + bool32 success; +}; + +internal File_Data +system_load_file(u8 *filename); + +internal bool32 +system_save_file(u8 *filename, void *data, i32 size); + +internal Time_Stamp +system_file_time_stamp(u8 *filename); + +internal u64 +system_get_now(); + +internal void +system_free_file(File_Data data); + +internal void +system_fatal_error(u8 *message); + +internal i32 +system_get_working_directory(u8 *destination, i32 max_size); + +internal i32 +system_get_easy_directory(u8 *destination); + +struct File_Info{ + String filename; + bool32 folder; + //bool32 loaded; +}; + +struct File_List{ + File_Info *infos; + i32 count; + + void *block; +}; + +internal File_List +system_get_files(String directory); + +internal void +system_free_file_list(File_List list); + +internal void* +system_get_memory_(i32 size, i32 line_number, char *file_name); + +#define system_get_memory(size) system_get_memory_(size, __LINE__, __FILE__) + +internal void +system_free_memory(void *block); + +internal void +system_post_clipboard(String str); + +internal i64 +system_time(); + +struct Thread_Context; + +struct Thread_Memory{ + void *data; + i32 size; + i32 id; +}; + +internal u32 +system_thread_get_id(Thread_Context *thread); + +internal u32 +system_thread_current_job_id(Thread_Context *thread); + +enum Thread_Group_ID{ + BACKGROUND_THREADS, + THREAD_GROUP_COUNT +}; + +#define JOB_CALLBACK(name) void name(Thread_Context *thread, Thread_Memory *memory, void *data[2]) +typedef JOB_CALLBACK(Job_Callback); + +struct Job_Data{ + Job_Callback *callback; + void *data[2]; + i32 memory_request; +}; + +internal u32 +system_post_job(Thread_Group_ID group_id, Job_Data job); + +internal void +system_cancel_job(Thread_Group_ID group_id, u32 job_id); + +internal bool32 +system_job_is_pending(Thread_Group_ID group_id, u32 job_id); + +enum Lock_ID{ + FRAME_LOCK, + CANCEL_LOCK0, + CANCEL_LOCK1, + CANCEL_LOCK2, + CANCEL_LOCK3, + CANCEL_LOCK4, + CANCEL_LOCK5, + CANCEL_LOCK6, + CANCEL_LOCK7, + LOCK_COUNT +}; + +internal void +system_aquire_lock(Lock_ID id); + +internal void +system_release_lock(Lock_ID id); + +internal void +system_aquire_lock(i32 id); + +internal void +system_release_lock(i32 id); + +internal void +system_grow_thread_memory(Thread_Memory *memory); + +internal void +system_force_redraw(); + +#if FRED_INTERNAL +internal Bubble* +INTERNAL_system_sentinel(); + +internal void +INTERNAL_get_thread_states(Thread_Group_ID id, bool8 *running, i32 *pending); +#endif + diff --git a/test/experiment.cpp b/test/experiment.cpp new file mode 100644 index 00000000..bd7e96bf --- /dev/null +++ b/test/experiment.cpp @@ -0,0 +1,255 @@ +/* + * Mr. 4th Dimention - Allen Webster + * + * 1.21.2015 + * + * Test for CPP lexer & parser layer for project codename "4ed" + * + */ + +// TOP + +// HOLY GRAIL +#if 0 +int main(){ + Parse_Context context; + Parse_Definitions definitions; + Cpp_Parse_Preprocessor_State state; + + cpp_default_context(&context, COMPILER_MSVC, PLATFORM_WIN32); + cpp_default_definitions(&definitions, COMPILER_MSVC, PLATFORM_WIN32); + cpp_set_target_file(&definitions, &state, "TARGET.cpp"); + + cpp_parse(&context, &definitions, &state); +} +#endif + +#include "../4ed_meta.h" + +#include "../4cpp_types.h" +#define FCPP_STRING_IMPLEMENTATION +#include "../4cpp_string.h" +#define FCPP_LEXER_IMPLEMENTATION +#include "../4cpp_lexer.h" + +#include +#include +#include + +#define FCPP_PREPROCESSOR_DBG_LEVEL 1 + +internal bool +system_is_absoute_path(char *path){ + bool is_absolute = 0; + char c = 1; + while (c){ + c = *path++; + if (c == ':'){ + is_absolute = 1; + break; + } + } + return is_absolute; +} + +#undef Assert +#undef TentativeAssert + +#define Assert assert +#define TentativeAssert assert + +#include "../4cpp_preprocessor.cpp" + +Cpp_File +quickie_file(char *filename){ + Cpp_File result; + + FILE *file = fopen(filename, "rb"); + TentativeAssert(file); + fseek(file, 0, SEEK_END); + result.size = ftell(file); + fseek(file, 0, SEEK_SET); + result.data = (char*)malloc(result.size); + fread(result.data, 1, result.size, file); + fclose(file); + + return result; +} + +inline Cpp_File +quickie_file(String filename){ + assert(filename.size < 511); + char buffer[512]; + memcpy(buffer, filename.str, filename.size); + buffer[filename.size] = 0; + return quickie_file(buffer); +} + +#define STRICT_MEM_TEST 1 + +#if 1 +int main(){ + char TEST_FILE[] = "parser_test6.cpp"; + + Cpp_File target_file; + target_file = quickie_file(TEST_FILE); + + Cpp_Token_Stack target_tokens = {}; + cpp_lex_file(target_file, &target_tokens); + + Cpp_Parse_Context context = {}; + Cpp_Parse_Definitions definitions = {}; + Cpp_Preproc_State state = {}; + + context.preserve_chunk_size = (200 << 10); + + definitions.table.size = 0; +#if STRICT_MEM_TEST + definitions.table.max_size = 16; +#else + definitions.table.max_size = 100; +#endif + definitions.table.table = (Table_Entry*)malloc(sizeof(Table_Entry)*definitions.table.max_size); + memset(definitions.table.table, 0, sizeof(Table_Entry)*definitions.table.max_size); + + definitions.count = 0; +#if STRICT_MEM_TEST + definitions.max = 16; +#else + definitions.max = 100; +#endif + definitions.slots = (Cpp_Def_Slot*)malloc(sizeof(Cpp_Def_Slot)*definitions.max); + + { + String string_filename = make_lit_string("~ string space"); + Cpp_File string_file; +#if STRICT_MEM_TEST + string_file.size = 100; +#else + string_file.size = (128 << 10); +#endif + string_file.data = (char*)malloc(string_file.size); + Cpp_Token_Stack string_tokens; + string_tokens.count = 0; +#if STRICT_MEM_TEST + string_tokens.max_count = 2; +#else + string_tokens.max_count = (128 << 10)/sizeof(Cpp_Token); +#endif + string_tokens.tokens = (Cpp_Token*)malloc(sizeof(Cpp_Token)*string_tokens.max_count); + + Cpp_Parse_File string_parse_file; + string_parse_file.file = string_file; + string_parse_file.tokens = string_tokens; + string_parse_file.filename = string_filename; + + int string_index = cpp_defs_add(&definitions, {}, CPP_DEFTYPE_FILE); + cpp_set_parse_file(&definitions, string_index, string_parse_file); + + definitions.string_file_index = string_index; + definitions.string_write_pos = 0; + + { + Cpp_Token eof_token = {}; + eof_token.type = CPP_TOKEN_EOF; + definitions.eof_token = cpp__preserve_token(&definitions, eof_token); + } + + { + String string_va_args = make_lit_string("__VA_ARGS__"); + Cpp_Token va_args_token; + va_args_token.type = CPP_TOKEN_IDENTIFIER; + va_args_token.start = definitions.string_write_pos; + va_args_token.size = string_va_args.size; + cpp__preserve_string(&definitions, string_va_args); + definitions.va_args_token = cpp__preserve_token(&definitions, va_args_token); + } + } + + state.tokens.count = 0; +#if STRICT_MEM_TEST + state.tokens.max = 5; +#else + state.tokens.max = 100; +#endif + state.tokens.tokens = (Cpp_Loose_Token*)malloc(sizeof(Cpp_Loose_Token)*state.tokens.max); + + state.spare_string_write_pos = 0; +#if STRICT_MEM_TEST + state.spare_string_size = 1; +#else + state.spare_string_size = (10 << 10); +#endif + state.spare_string = (char*)malloc(state.spare_string_size); + + String target_filename = make_lit_string(TEST_FILE); + cpp_set_target(&state, &definitions, target_file, target_tokens, target_filename); + + while (!state.finished){ + Cpp_Preproc_Result result; + result = cpp_preproc_step_nonalloc(&state, &definitions, &context); + + if (result.memory_request){ + Cpp_Memory_Request request = cpp_get_memory_request(&state, &definitions, result); + void *memory = malloc(request.size); + void *old_memory = cpp_provide_memory(request, memory); + free(old_memory); + } + + if (result.file_request){ + Cpp_File_Request request = cpp_get_file_request(&state, result); + for (; cpp_has_more_files(&request); cpp_get_next_file(&request)){ + if (!cpp_try_reuse_file(&request)){ + Cpp_File new_file = quickie_file(request.filename); + Cpp_Token_Stack new_tokens = {}; + cpp_lex_file(new_file, &new_tokens); + cpp_provide_file(&request, new_file, new_tokens); + } + } + } + + if (result.error_code){ + String error_message = cpp_get_error(result.error_code); + Cpp_Parse_File file = *cpp_get_parse_file(&definitions, result.file_index); + Cpp_Token token = file.tokens.tokens[result.token_index]; + bool terminate = cpp_recommend_termination(result.error_code); + + if (terminate){ + printf("FATAL "); + } + + printf("ERROR IN %.*s AT %.*s\n%.*s\n", + file.filename.size, file.filename.str, + token.size, file.file.data + token.start, + error_message.size, error_message.str); + + if (terminate){ + break; + } + } + + if (result.emit){ + Cpp_Parse_File file = *cpp_get_parse_file(&definitions, result.file_index); + Cpp_Token token = file.tokens.tokens[result.token_index]; + + if (result.from_macro){ + Cpp_Parse_File file = *cpp_get_parse_file(&definitions, result.invoking_file_index); + Cpp_Token token = file.tokens.tokens[result.invoking_token_index]; + + printf("EXPANDING %.*s => ", token.size, file.file.data + token.start); + } + + printf("TOKEN %.*s\n", token.size, file.file.data + token.start); + } + } + + assert(state.finished == 0 || state.expansion_level == 0); + assert(state.finished == 0 || state.param_info_used == 0); + assert(state.finished == 0 || state.state == 0); + + return 0; +} +#endif + +// BOTTOM + diff --git a/win32_4ed.cpp b/win32_4ed.cpp new file mode 100644 index 00000000..9c8cce70 --- /dev/null +++ b/win32_4ed.cpp @@ -0,0 +1,1326 @@ +/* + * Mr. 4th Dimention - Allen Webster + * + * 12.12.2014 + * + * Win32 layer for project codename "4ed" + * + */ + +// TOP +// TODO(allen): +// +// Fix the OwnDC thing. +// + +#define FRED_PRINT_DEBUG 1 +#define FRED_PRINT_DEBUG_FILE_LINE 0 +#define FRED_PROFILING 1 +#define FRED_PROFILING_OS 0 +#define FRED_FULL_ERRORS 0 + +#ifndef FRED_SLOW +#define FRED_SLOW 0 +#else +#undef FRED_SLOW +#define FRED_SLOW 1 +#endif + +#ifndef FRED_INTERNAL +#define FRED_INTERNAL 0 +#else +#undef FRED_INTERNAL +#define FRED_INTERNAL 1 +#endif + +#define SOFTWARE_RENDER 0 + +#if FRED_INTERNAL == 0 +#undef FRED_PRINT_DEBUG +#define FRED_PRINT_DEBUG 0 +#undef FRED_PROFILING +#define FRED_PROFILING 0 +#undef FRED_PROFILING_OS +#define FRED_PROFILING_OS 0 +#endif + +#if FRED_PRINT_DEBUG == 0 +#undef FRED_PRINT_DEBUG_FILE_LINE +#define FRED_PRINT_DEBUG_FILE_LINE 0 +#undef FRED_PRINT_DEBUG_FILE_LINE +#define FRED_PROFILING_OS 0 +#endif + +#define FPS 30 +#define FRAME_TIME (1000000 / FPS) + +#include "4ed_meta.h" + +#define FCPP_FORBID_MALLOC + +#include "4cpp_types.h" +#define FCPP_STRING_IMPLEMENTATION +#include "4cpp_string.h" +#define FCPP_LEXER_IMPLEMENTATION +#include "4cpp_lexer.h" +#include "4ed_math.cpp" +#include "4coder_custom.h" +#include "4ed.h" +#include "4ed_system.h" +#include "4ed_rendering.h" + +struct TEMP_BACKDOOR{ + Get_Binding_Data_Function *get_bindings; + Set_Extra_Font_Function *set_extra_font; + Start_Hook_Function *start_hook; +} TEMP; + +#if FRED_INTERNAL + +struct Sys_Bubble : public Bubble{ + i32 line_number; + char *file_name; +}; + +#endif + +#include +#include + +#include "4ed_internal.h" +#include "4ed_rendering.cpp" +#include "4ed_command.cpp" +#include "4ed_layout.cpp" +#include "4ed_style.cpp" +#include "4ed_file_view.cpp" +#include "4ed_color_view.cpp" +#include "4ed_interactive_view.cpp" +#include "4ed_menu_view.cpp" +#include "4ed_debug_view.cpp" +#include "4ed.cpp" +#include "4ed_keyboard.cpp" + +struct Full_Job_Data{ + Job_Data job; + + u32 job_memory_index; + u32 running_thread; + bool32 finished; + u32 id; +}; + +struct Work_Queue{ + u32 volatile write_position; + u32 volatile read_position; + Full_Job_Data jobs[256]; + + HANDLE semaphore; +}; + +struct Thread_Context{ + u32 job_id; + bool32 running; + + Work_Queue *queue; + u32 id; + u32 windows_id; + HANDLE handle; +}; + +struct Thread_Group{ + Thread_Context *threads; + i32 count; +}; + +struct Win32_Vars{ + HWND window_handle; + Key_Codes key_codes, loose_codes; + Key_Input_Data input_data, previous_data; + +#if SOFTWARE_RENDER + BITMAPINFO bmp_info; + union{ + struct{ + void *pixel_data; + i32 width, height, pitch; + }; + Render_Target target; + }; + i32 true_pixel_size; +#else + Render_Target target; +#endif + + u32 volatile force_redraw; + + Mouse_State mouse; + bool32 focus; + bool32 keep_playing; + HCURSOR cursor_ibeam; + HCURSOR cursor_arrow; + HCURSOR cursor_leftright; + HCURSOR cursor_updown; + Application_Mouse_Cursor prev_mouse_cursor; + Clipboard_Contents clipboard_contents; + bool32 next_clipboard_is_self; + DWORD clipboard_sequence; + + Thread_Context main_thread; + + Thread_Group groups[THREAD_GROUP_COUNT]; + Work_Queue queues[THREAD_GROUP_COUNT]; + HANDLE locks[LOCK_COUNT]; + HANDLE DEBUG_sysmem_lock; + Thread_Memory *thread_memory; + + HMODULE custom; + + i64 performance_frequency; + i64 start_pcount; +}; + +globalvar Win32_Vars win32vars; +globalvar Application_Memory win32memory; + +internal void +_OutDbgStr(u8 *msg){ + OutputDebugString((char*)msg); +} + +internal void +system_fatal_error(u8 *message){ + MessageBox(0, (char*)message, "4ed Error", MB_OK|MB_ICONERROR); +} + +internal File_Data +system_load_file(u8 *filename){ + File_Data result = {}; + HANDLE file; + file = CreateFile((char*)filename, GENERIC_READ, 0, 0, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + if (!file){ + return result; + } + + DWORD lo, hi; + lo = GetFileSize(file, &hi); + + if (hi != 0){ + CloseHandle(file); + return result; + } + + result.size = (lo) + (((u64)hi) << 32); + result.data = system_get_memory(result.size); + + if (!result.data){ + CloseHandle(file); + result = {}; + return result; + } + + DWORD read_size; + BOOL read_result = ReadFile(file, result.data, result.size, + &read_size, 0); + if (!read_result || read_size != result.size){ + CloseHandle(file); + system_free_memory(result.data); + result = {}; + return result; + } + + CloseHandle(file); + return result; +} + +internal bool32 +system_save_file(u8 *filename, void *data, i32 size){ + HANDLE file; + file = CreateFile((char*)filename, GENERIC_WRITE, 0, 0, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + + if (!file){ + return 0; + } + + BOOL write_result; + DWORD bytes_written; + write_result = WriteFile(file, data, size, &bytes_written, 0); + + CloseHandle(file); + + if (!write_result || bytes_written != (u32)size){ + return 0; + } + + return 1; +} + +internal Time_Stamp +system_file_time_stamp(u8 *filename){ + Time_Stamp result; + result = {}; + + FILETIME last_write; + WIN32_FILE_ATTRIBUTE_DATA data; + if (GetFileAttributesEx((char*)filename, GetFileExInfoStandard, &data)){ + last_write = data.ftLastWriteTime; + + result.time = ((u64)last_write.dwHighDateTime << 32) | last_write.dwLowDateTime; + result.success = 1; + } + + return result; +} + +internal u64 +system_get_now(){ + u64 result; + SYSTEMTIME sys_now; + FILETIME file_now; + GetSystemTime(&sys_now); + SystemTimeToFileTime(&sys_now, &file_now); + result = ((u64)file_now.dwHighDateTime << 32) | file_now.dwLowDateTime; + return result; +} + +internal void +system_free_file(File_Data data){ + system_free_memory(data.data); +} + +internal i32 +system_get_working_directory(u8 *destination, i32 max_size){ + DWORD required = GetCurrentDirectory(0, 0); + if ((i32) required > max_size){ + // TODO(allen): WHAT NOW? Not enough space in destination for + // current directory. Two step approach perhaps? + return 0; + } + DWORD written = GetCurrentDirectory(max_size, (char*)destination); + return (i32)written; +} + +internal i32 +system_get_easy_directory(u8 *destination){ + persist char easydir[] = "C:\\"; + for (i32 i = 0; i < ArrayCount(easydir); ++i){ + destination[i] = easydir[i]; + } + return ArrayCount(easydir)-1; +} + +internal File_List +system_get_files(String directory){ + File_List result = {}; + + if (directory.size > 0){ + char dir_space[MAX_PATH + 32]; + String dir = make_string(dir_space, 0, MAX_PATH + 32); + append(&dir, directory); + char trail_str[] = "\\*"; + append(&dir, trail_str); + + char *c_str_dir = make_c_str(dir); + + WIN32_FIND_DATA find_data; + HANDLE search; + search = FindFirstFileA(c_str_dir, &find_data); + + if (search != INVALID_HANDLE_VALUE){ + i32 count = 0; + i32 file_count = 0; + BOOL more_files = 1; + do{ + if (!match(find_data.cFileName, ".") && + !match(find_data.cFileName, "..")){ + ++file_count; + i32 size = 0; + for(;find_data.cFileName[size];++size); + count += size + 1; + } + more_files = FindNextFile(search, &find_data); + }while(more_files); + FindClose(search); + + result.block = system_get_memory(count + file_count * sizeof(File_Info)); + result.infos = (File_Info*)result.block; + char *name = (char*)(result.infos + file_count); + if (result.block){ + search = FindFirstFileA(c_str_dir, &find_data); + + if (search != INVALID_HANDLE_VALUE){ + File_Info *info = result.infos; + more_files = 1; + do{ + if (!match(find_data.cFileName, ".") && + !match(find_data.cFileName, "..")){ + info->folder = (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + info->filename.str = name; + + char *name_base = name; + i32 i = 0; + for(;find_data.cFileName[i];++i) *name++ = find_data.cFileName[i]; + info->filename.size = (i32)(name - name_base); + info->filename.memory_size = info->filename.size + 1; + *name++ = 0; + ++info; + } + more_files = FindNextFile(search, &find_data); + }while(more_files); + FindClose(search); + + result.count = file_count; + + }else{ + system_free_memory(result.block); + result = {}; + } + } + } + } + + return result; +} + +internal void +system_free_file_list(File_List list){ + system_free_memory(list.block); +} + +#if FRED_INTERNAL +Sys_Bubble INTERNAL_sentinel; + +internal Bubble* +INTERNAL_system_sentinel(){ + return &INTERNAL_sentinel; +} +#endif + +internal void* +system_get_memory_(i32 size, i32 line_number, char *file_name){ + void *ptr = 0; + +#if FRED_INTERNAL + ptr = VirtualAlloc(0, size + sizeof(Sys_Bubble), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + Sys_Bubble *bubble = (Sys_Bubble*)ptr; + bubble->flags = MEM_BUBBLE_SYS_DEBUG; + bubble->line_number = line_number; + bubble->file_name = file_name; + bubble->size = size; + WaitForSingleObject(win32vars.DEBUG_sysmem_lock, INFINITE); + insert_bubble(&INTERNAL_sentinel, bubble); + ReleaseSemaphore(win32vars.DEBUG_sysmem_lock, 1, 0); + ptr = bubble + 1; +#else + ptr = VirtualAlloc(0, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); +#endif + + return ptr; +} + +internal void +system_free_memory(void *block){ + if (block){ +#if FRED_INTERNAL + Sys_Bubble *bubble = ((Sys_Bubble*)block) - 1; + Assert((bubble->flags & MEM_BUBBLE_DEBUG_MASK) == MEM_BUBBLE_SYS_DEBUG); + WaitForSingleObject(win32vars.DEBUG_sysmem_lock, INFINITE); + remove_bubble(bubble); + ReleaseSemaphore(win32vars.DEBUG_sysmem_lock, 1, 0); + VirtualFree(bubble, 0, MEM_RELEASE); +#else + VirtualFree(block, 0, MEM_RELEASE); +#endif + } +} + +internal i64 +system_time(){ + i64 result = 0; + LARGE_INTEGER time; + if (QueryPerformanceCounter(&time)){ + result = (i64)(time.QuadPart - win32vars.start_pcount) * 1000000 / win32vars.performance_frequency; + } + return result; +} + +// TODO(allen): Probably best to just drop all system functions here again. +internal void +system_post_clipboard(String str){ + if (OpenClipboard(win32vars.window_handle)){ + EmptyClipboard(); + HANDLE memory_handle; + memory_handle = GlobalAlloc(GMEM_MOVEABLE, str.size+1); + if (memory_handle){ + char *dest = (char*)GlobalLock(memory_handle); + copy_fast_unsafe(dest, str); + GlobalUnlock(memory_handle); + SetClipboardData(CF_TEXT, memory_handle); + win32vars.next_clipboard_is_self = 1; + } + CloseClipboard(); + } +} + +#if SOFTWARE_RENDER +internal void +Win32RedrawScreen(HDC hdc){ + win32vars.bmp_info.bmiHeader.biHeight = + -win32vars.bmp_info.bmiHeader.biHeight; + SetDIBitsToDevice(hdc, + 0, 0, + win32vars.width, win32vars.height, + 0, 0, + 0, win32vars.height, + win32vars.pixel_data, + &win32vars.bmp_info, + DIB_RGB_COLORS); + win32vars.bmp_info.bmiHeader.biHeight = + -win32vars.bmp_info.bmiHeader.biHeight; +} +#else +internal void +Win32RedrawScreen(HDC hdc){ + glFlush(); + SwapBuffers(hdc); +} +#endif + +internal void +Win32Resize(i32 width, i32 height){ + if (width > 0 && height > 0){ + glViewport(0, 0, width, height); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, width, height, 0, -1, 1); + glScissor(0, 0, width, height); + + win32vars.target.width = width; + win32vars.target.height = height; + } +} + +internal void +Win32KeyboardHandle(bool8 current_state, bool8 previous_state, WPARAM wParam){ + switch (wParam){ + case VK_LSHIFT: + case VK_RSHIFT: + case VK_SHIFT: + { + win32vars.input_data.control_keys[CONTROL_KEY_SHIFT] = current_state; + }break; + case VK_LCONTROL: + case VK_RCONTROL: + case VK_CONTROL: + { + win32vars.input_data.control_keys[CONTROL_KEY_CONTROL] = current_state; + }break; + case VK_LMENU: + case VK_RMENU: + case VK_MENU: + { + win32vars.input_data.control_keys[CONTROL_KEY_ALT] = current_state; + }break; + default: + { + u16 key = keycode_lookup((u8)wParam); + if (key != -1){ + if (current_state & !previous_state){ + i32 count = win32vars.input_data.press_count; + if (count < KEY_INPUT_BUFFER_SIZE){ + win32vars.input_data.press[count].keycode = key; + win32vars.input_data.press[count].loose_keycode = loose_keycode_lookup((u8)wParam); + ++win32vars.input_data.press_count; + } + } + else if (current_state){ + i32 count = win32vars.input_data.hold_count; + if (count < KEY_INPUT_BUFFER_SIZE){ + win32vars.input_data.hold[count].keycode = key; + win32vars.input_data.hold[count].loose_keycode = loose_keycode_lookup((u8)wParam); + ++win32vars.input_data.hold_count; + } + } + } + }break; + } +} + +#define HOTKEY_ALT_ID 0 + +internal LRESULT +Win32Callback(HWND hwnd, UINT uMsg, + WPARAM wParam, LPARAM lParam){ + + LRESULT result = {}; + switch (uMsg){ + case WM_MENUCHAR: + case WM_SYSCHAR:break; + + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + case WM_KEYDOWN: + case WM_KEYUP: + { + bool8 previous_state, current_state; + previous_state = ((lParam & Bit_30)?(1):(0)); + current_state = ((lParam & Bit_31)?(0):(1)); + Win32KeyboardHandle(current_state, previous_state, wParam); + }break; + + case WM_MOUSEMOVE: + { + win32vars.mouse.x = LOWORD(lParam); + win32vars.mouse.y = HIWORD(lParam); + }break; + + case WM_MOUSEWHEEL: + { + i16 rotation = GET_WHEEL_DELTA_WPARAM(wParam); + if (rotation > 0){ + win32vars.mouse.wheel = 1; + } + else{ + win32vars.mouse.wheel = -1; + } + }break; + + case WM_LBUTTONDOWN: + { + win32vars.mouse.left_button = true; + }break; + + case WM_RBUTTONDOWN: + { + win32vars.mouse.right_button = true; + }break; + + case WM_LBUTTONUP: + { + win32vars.mouse.left_button = false; + }break; + + case WM_RBUTTONUP: + { + win32vars.mouse.right_button = false; + }break; + + case WM_KILLFOCUS: + { + win32vars.focus = 0; + win32vars.mouse.left_button = false; + win32vars.mouse.right_button = false; + for (int i = 0; i < CONTROL_KEY_COUNT; ++i){ + win32vars.input_data.control_keys[i] = 0; + } + }break; + + case WM_SETFOCUS: + { + win32vars.focus = 1; + }break; + + case WM_SIZE: + { +#if SOFTWARE_RENDER + i32 new_width = LOWORD(lParam); + i32 new_height = HIWORD(lParam); + i32 new_pitch = new_width * 4; + + if (new_height*new_pitch > win32vars.true_pixel_size){ + system_free_memory(win32vars.pixel_data); + + win32vars.pixel_data = system_get_memory(new_height*new_pitch); + win32vars.true_pixel_size = new_height*new_pitch; + + if (!win32vars.pixel_data){ + FatalError("Failure allocating new screen memory"); + win32vars.keep_playing = 0; + } + } + + win32vars.width = new_width; + win32vars.height = new_height; + win32vars.pitch = new_pitch; + + win32vars.bmp_info.bmiHeader.biWidth = win32vars.width; + win32vars.bmp_info.bmiHeader.biHeight = win32vars.height; +#else + if (win32vars.target.handle){ + i32 new_width = LOWORD(lParam); + i32 new_height = HIWORD(lParam); + + Win32Resize(new_width, new_height); + } +#endif + }break; + + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hwnd, &ps); + + Clipboard_Contents empty_contents = {}; +#if FRED_INTERNAL + INTERNAL_collecting_events = 0; +#endif + app_step(&win32vars.main_thread, + &win32vars.key_codes, + &win32vars.previous_data, &win32vars.mouse, + 0, &win32vars.target, &win32memory, empty_contents, 0, 1); +#if FRED_INTERNAL + INTERNAL_collecting_events = 1; +#endif + Win32RedrawScreen(hdc); + + EndPaint(hwnd, &ps); + }break; + + case WM_CLOSE: // NOTE(allen): I expect WM_CLOSE not WM_DESTROY + case WM_DESTROY: + { + win32vars.keep_playing = 0; + }break; + + default: + { + result = DefWindowProc(hwnd, uMsg, wParam, lParam); + }break; + } + return result; +} + +#define THREAD_NOT_ASSIGNED 0xFFFFFFFF + +#define JOB_ID_WRAP (ArrayCount(queue->jobs) * 4) +#define QUEUE_WRAP (ArrayCount(queue->jobs)) + +internal DWORD WINAPI +ThreadProc(LPVOID lpParameter){ + Thread_Context *thread = (Thread_Context*)lpParameter; + Work_Queue *queue = thread->queue; + + for (;;){ + u32 read_index = queue->read_position; + u32 write_index = queue->write_position; + + if (read_index != write_index){ + u32 next_read_index = (read_index + 1) % JOB_ID_WRAP; + u32 safe_read_index = + InterlockedCompareExchange(&queue->read_position, + next_read_index, read_index); + + if (safe_read_index == read_index){ + Full_Job_Data *full_job = queue->jobs + (safe_read_index % QUEUE_WRAP); + // NOTE(allen): This is interlocked so that it plays nice + // with the cancel job routine, which may try to cancel this job + // at the same time that we try to run it + + i32 safe_running_thread = + InterlockedCompareExchange(&full_job->running_thread, + thread->id, THREAD_NOT_ASSIGNED); + + if (safe_running_thread == THREAD_NOT_ASSIGNED){ + thread->job_id = full_job->id; + thread->running = 1; + Thread_Memory *thread_memory = 0; + if (full_job->job.memory_request != 0){ + thread_memory = win32vars.thread_memory + thread->id - 1; + if (thread_memory->size < full_job->job.memory_request){ + if (thread_memory->data){ + system_free_memory(thread_memory->data); + } + i32 new_size = LargeRoundUp(full_job->job.memory_request, Kbytes(4)); + thread_memory->data = system_get_memory(new_size); + thread_memory->size = new_size; + } + } + full_job->job.callback(thread, thread_memory, full_job->job.data); + full_job->running_thread = 0; + thread->running = 0; + } + } + } + else{ + WaitForSingleObject(queue->semaphore, INFINITE); + } + } +} + +internal bool32 +Win32JobIsPending(Work_Queue *queue, u32 job_id){ + bool32 result; + u32 job_index; + Full_Job_Data *full_job; + + job_index = job_id % QUEUE_WRAP; + full_job = queue->jobs + job_index; + + Assert(full_job->id == job_id); + + result = 0; + if (full_job->running_thread != 0){ + result = 1; + } + + return result; +} + +internal u32 +system_thread_get_id(Thread_Context *thread){ + return thread->id; +} + +internal u32 +system_thread_current_job_id(Thread_Context *thread){ + return thread->job_id; +} + +internal u32 +system_post_job(Thread_Group_ID group_id, Job_Data job){ + Work_Queue *queue = win32vars.queues + group_id; + + Assert((queue->write_position + 1) % QUEUE_WRAP != queue->read_position % QUEUE_WRAP); + + bool32 success = 0; + u32 result = 0; + while (!success){ + u32 write_index = queue->write_position; + u32 next_write_index = (write_index + 1) % JOB_ID_WRAP; + u32 safe_write_index = + InterlockedCompareExchange(&queue->write_position, + next_write_index, write_index); + if (safe_write_index == write_index){ + result = write_index; + write_index = write_index % QUEUE_WRAP; + queue->jobs[write_index].job = job; + queue->jobs[write_index].running_thread = THREAD_NOT_ASSIGNED; + queue->jobs[write_index].id = result; + success = 1; + } + } + + ReleaseSemaphore(queue->semaphore, 1, 0); + + return result; +} + +internal void +system_cancel_job(Thread_Group_ID group_id, u32 job_id){ + Work_Queue *queue = win32vars.queues + group_id; + Thread_Group *group = win32vars.groups + group_id; + + u32 job_index; + u32 thread_id; + Full_Job_Data *full_job; + Thread_Context *thread; + + job_index = job_id % QUEUE_WRAP; + full_job = queue->jobs + job_index; + + Assert(full_job->id == job_id); + thread_id = + InterlockedCompareExchange(&full_job->running_thread, + 0, THREAD_NOT_ASSIGNED); + + if (thread_id != THREAD_NOT_ASSIGNED){ + system_aquire_lock(CANCEL_LOCK0 + thread_id - 1); + thread = group->threads + thread_id - 1; + TerminateThread(thread->handle, 0); + u32 creation_flag = 0; + thread->handle = CreateThread(0, 0, ThreadProc, thread, creation_flag, (LPDWORD)&thread->windows_id); + system_release_lock(CANCEL_LOCK0 + thread_id - 1); + thread->running = 0; + } +} + +internal bool32 +system_job_is_pending(Thread_Group_ID group_id, u32 job_id){ + Work_Queue *queue = win32vars.queues + group_id;; + return Win32JobIsPending(queue, job_id); +} + +internal void +system_aquire_lock(Lock_ID id){ + WaitForSingleObject(win32vars.locks[id], INFINITE); +} + +internal void +system_release_lock(Lock_ID id){ + ReleaseSemaphore(win32vars.locks[id], 1, 0); +} + +internal void +system_aquire_lock(i32 id){ + WaitForSingleObject(win32vars.locks[id], INFINITE); +} + +internal void +system_release_lock(i32 id){ + ReleaseSemaphore(win32vars.locks[id], 1, 0); +} + +internal void +system_grow_thread_memory(Thread_Memory *memory){ + system_aquire_lock(CANCEL_LOCK0 + memory->id - 1); + void *old_data = memory->data; + i32 old_size = memory->size; + i32 new_size = LargeRoundUp(memory->size*2, Kbytes(4)); + memory->data = system_get_memory(new_size); + memory->size = new_size; + if (old_data){ + memcpy(memory->data, old_data, old_size); + system_free_memory(old_data); + } + system_release_lock(CANCEL_LOCK0 + memory->id - 1); +} + +internal void +system_force_redraw(){ + InterlockedExchange(&win32vars.force_redraw, 1); +} + +#if FRED_INTERNAL +internal void +INTERNAL_get_thread_states(Thread_Group_ID id, bool8 *running, i32 *pending){ + Work_Queue *queue = win32vars.queues + id; + u32 write = queue->write_position; + u32 read = queue->read_position; + if (write < read) write += JOB_ID_WRAP; + *pending = (i32)(write - read); + + Thread_Group *group = win32vars.groups + id; + for (i32 i = 0; i < group->count; ++i){ + running[i] = (group->threads[i].running != 0); + } +} +#endif + +int +WinMain(HINSTANCE hInstance, + HINSTANCE hPrevInstance, + LPSTR lpCmdLine, + int nCmdShow){ + win32vars = {}; + TEMP = {}; + LARGE_INTEGER lpf; + QueryPerformanceFrequency(&lpf); + win32vars.performance_frequency = lpf.QuadPart; + QueryPerformanceCounter(&lpf); + win32vars.start_pcount = lpf.QuadPart; + +#if FRED_INTERNAL + memset(INTERNAL_event_hits, 0, INTERNAL_event_index_count * sizeof(u32)); + INTERNAL_frame_index = 0; + INTERNAL_updating_profile = 1; + INTERNAL_collecting_events = 1; + + INTERNAL_sentinel.next = &INTERNAL_sentinel; + INTERNAL_sentinel.prev = &INTERNAL_sentinel; + INTERNAL_sentinel.flags = MEM_BUBBLE_SYS_DEBUG; +#endif + + keycode_init(&win32vars.key_codes, &win32vars.loose_codes); + +#ifdef FRED_SUPER + win32vars.custom = LoadLibraryA("4coder_custom.dll"); + if (win32vars.custom){ + TEMP.get_bindings = (Get_Binding_Data_Function*) + GetProcAddress(win32vars.custom, "get_bindings"); + + TEMP.set_extra_font = (Set_Extra_Font_Function*) + GetProcAddress(win32vars.custom, "set_extra_font"); + + TEMP.start_hook = (Start_Hook_Function*) + GetProcAddress(win32vars.custom, "start_hook"); + } +#endif + + Thread_Context background[4]; + memset(background, 0, sizeof(background)); + win32vars.groups[BACKGROUND_THREADS].threads = background; + win32vars.groups[BACKGROUND_THREADS].count = ArrayCount(background); + + Thread_Memory thread_memory[ArrayCount(background)]; + win32vars.thread_memory = thread_memory; + + win32vars.queues[BACKGROUND_THREADS].semaphore = + CreateSemaphore(0, 0, win32vars.groups[BACKGROUND_THREADS].count, 0); + + u32 creation_flag = 0; + for (i32 i = 0; i < win32vars.groups[BACKGROUND_THREADS].count; ++i){ + Thread_Context *thread = win32vars.groups[BACKGROUND_THREADS].threads + i; + thread->handle = CreateThread(0, 0, ThreadProc, thread, creation_flag, (LPDWORD)&thread->windows_id); + thread->id = i + 1; + thread->queue = &win32vars.queues[BACKGROUND_THREADS]; + + Thread_Memory *memory = win32vars.thread_memory + i; + *memory = {}; + memory->id = thread->id; + } + + Assert(win32vars.locks); + for (i32 i = 0; i < LOCK_COUNT; ++i){ + win32vars.locks[i] = CreateSemaphore(0, 1, 1, 0); + } + win32vars.DEBUG_sysmem_lock = CreateSemaphore(0, 1, 1, 0); + + win32vars.cursor_ibeam = LoadCursor(NULL, IDC_IBEAM); + win32vars.cursor_arrow = LoadCursor(NULL, IDC_ARROW); + win32vars.cursor_leftright = LoadCursor(NULL, IDC_SIZEWE); + win32vars.cursor_updown = LoadCursor(NULL, IDC_SIZENS); + win32vars.prev_mouse_cursor = APP_MOUSE_CURSOR_ARROW; + + WNDCLASS window_class = {}; + window_class.style = CS_HREDRAW|CS_VREDRAW|CS_OWNDC; + window_class.lpfnWndProc = Win32Callback; + window_class.hInstance = hInstance; + window_class.lpszClassName = "4coder-win32-wndclass"; + + if (!RegisterClass(&window_class)){ + // TODO(allen): diagnostics + FatalError("Failed to create window class"); + return 1; + } + + RECT window_rect = {}; + window_rect.right = 800; + window_rect.bottom = 600; + + if (!AdjustWindowRect(&window_rect, WS_OVERLAPPEDWINDOW, false)){ + // TODO(allen): non-fatal diagnostics + } + +#if SOFTWARE_RENDER +#define WINDOW_NAME "4coder-softrender-window" +#else +#define WINDOW_NAME "4coder-window" +#endif + + HWND window_handle = {}; + window_handle = CreateWindowA( + window_class.lpszClassName, + WINDOW_NAME, + WS_OVERLAPPEDWINDOW | WS_VISIBLE, + CW_USEDEFAULT/*x*/, CW_USEDEFAULT/*y*/, + window_rect.right - window_rect.left, + window_rect.bottom - window_rect.top, + 0, 0, hInstance, 0); + + if (!window_handle){ + // TODO(allen): diagnostics + FatalError("Failed to create window"); + return 2; + } + + // TODO(allen): errors? + win32vars.window_handle = window_handle; + HDC hdc = GetDC(window_handle); + + GetClientRect(window_handle, &window_rect); + +#if SOFTWARE_RENDER + win32vars.width = window_rect.right - window_rect.left; + win32vars.height = window_rect.bottom - window_rect.top; + + win32vars.pitch = win32vars.width*4; +#define bmi_header win32vars.bmp_info.bmiHeader + bmi_header = {}; + bmi_header.biSize = sizeof(BITMAPINFOHEADER); + bmi_header.biWidth = win32vars.width; + bmi_header.biHeight = win32vars.height; + bmi_header.biPlanes = 1; + bmi_header.biBitCount = 32; + bmi_header.biCompression = BI_RGB; +#undef bmi_header + + win32vars.true_pixel_size = win32vars.height*win32vars.pitch; + win32vars.pixel_data = system_get_memory(win32vars.true_pixel_size); + + if (!win32vars.pixel_data){ + FatalError("Failure allocating screen memory"); + return 3; + } +#else + static PIXELFORMATDESCRIPTOR pfd = { + sizeof(PIXELFORMATDESCRIPTOR), + 1, + PFD_DRAW_TO_WINDOW | + PFD_SUPPORT_OPENGL | + PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, + 32, + 0, 0, 0, 0, 0, 0, + 0, + 0, + 0, + 0, 0, 0, 0, + 16, + 0, + 0, + PFD_MAIN_PLANE, + 0, + 0, 0, 0 }; + + i32 pixel_format; + pixel_format = ChoosePixelFormat(hdc, &pfd); + SetPixelFormat(hdc, pixel_format, &pfd); + + win32vars.target.handle = hdc; + win32vars.target.context = wglCreateContext(hdc); + wglMakeCurrent(hdc, (HGLRC)win32vars.target.context); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_SCISSOR_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + Win32Resize(window_rect.right - window_rect.left, window_rect.bottom - window_rect.top); +#endif + + LPVOID base; +#if FRED_INTERNAL + base = (LPVOID)Tbytes(1); +#else + base = (LPVOID)0; +#endif + + win32memory.vars_memory_size = Mbytes(2); + win32memory.vars_memory = VirtualAlloc(base, win32memory.vars_memory_size, + MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE); + +#if FRED_INTERNAL + base = (LPVOID)Tbytes(2); +#else + base = (LPVOID)0; +#endif + win32memory.target_memory_size = Mbytes(512); + win32memory.target_memory = VirtualAlloc(base, win32memory.target_memory_size, + MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE); + + if (!win32memory.vars_memory){ + FatalError("Failure allocating application memory"); + return 4; + } + + win32vars.clipboard_sequence = GetClipboardSequenceNumber(); + + if (win32vars.clipboard_sequence == 0){ + system_post_clipboard(make_lit_string("")); + + win32vars.clipboard_sequence = GetClipboardSequenceNumber(); + win32vars.next_clipboard_is_self = 0; + + if (win32vars.clipboard_sequence == 0){ + FatalError("Failure to access platform's clipboard"); + } + } + else{ + if (IsClipboardFormatAvailable(CF_TEXT)){ + if (OpenClipboard(win32vars.window_handle)){ + HANDLE clip_data; + clip_data = GetClipboardData(CF_TEXT); + if (clip_data){ + win32vars.clipboard_contents.str = (u8*)GlobalLock(clip_data); + if (win32vars.clipboard_contents.str){ + win32vars.clipboard_contents.size = str_size((char*)win32vars.clipboard_contents.str); + GlobalUnlock(clip_data); + } + } + CloseClipboard(); + } + } + } + + if (!app_init(&win32vars.main_thread, + &win32memory, &win32vars.key_codes, + win32vars.clipboard_contents)){ + return 5; + } + + win32vars.keep_playing = 1; + timeBeginPeriod(1); + + system_aquire_lock(FRAME_LOCK); + Thread_Context *thread = &win32vars.main_thread; + AllowLocal(thread); + bool32 first = 1; + i64 timer_start = system_time(); + while (win32vars.keep_playing){ +#if FRED_INTERNAL + i64 dbg_procing_start = system_time(); + if (!first){ + if (INTERNAL_updating_profile){ + i32 j = (INTERNAL_frame_index % 30); + Profile_Frame *frame = past_frames + j; + + sort(&profile_frame.events); + + frame->events.count = profile_frame.events.count; + memcpy(frame->events.e, profile_frame.events.e, sizeof(Debug_Event)*profile_frame.events.count); + + past_frames[j].dbg_procing_start = profile_frame.dbg_procing_start; + past_frames[j].dbg_procing_end = profile_frame.dbg_procing_end; + past_frames[j].index = profile_frame.index; + past_frames[j].first_key = profile_frame.first_key; + + ++INTERNAL_frame_index; + if (INTERNAL_frame_index < 0){ + INTERNAL_frame_index = ((INTERNAL_frame_index - 1) % 30) + 1; + } + memset(INTERNAL_event_hits, 0, INTERNAL_event_index_count * sizeof(u32)); + } + } + profile_frame.events.count = 0; + profile_frame.first_key = -1; + profile_frame.index = INTERNAL_frame_index; + INTERNAL_frame_start_time = timer_start; + profile_frame.dbg_procing_start = (i32)(dbg_procing_start - INTERNAL_frame_start_time); + profile_frame.dbg_procing_end = (i32)(system_time() - INTERNAL_frame_start_time); +#endif + + ProfileStart(OS_input); + win32vars.previous_data = win32vars.input_data; + win32vars.input_data.press_count = 0; + win32vars.input_data.hold_count = 0; + win32vars.input_data.caps_lock = GetKeyState(VK_CAPITAL) & 0x1; + win32vars.mouse.left_button_prev = win32vars.mouse.left_button; + win32vars.mouse.right_button_prev = win32vars.mouse.right_button; + win32vars.mouse.wheel = 0; + + MSG msg; + while (PeekMessage(&msg, window_handle, 0, 0, PM_REMOVE) && win32vars.keep_playing){ + if (msg.message == WM_QUIT){ + win32vars.keep_playing = 0; + }else{ + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + if (!win32vars.keep_playing){ + break; + } + + win32vars.mouse.out_of_window = 0; + POINT mouse_point; + if (GetCursorPos(&mouse_point) && + ScreenToClient(window_handle, &mouse_point)){ + + if (mouse_point.x < 0 || mouse_point.x >= win32vars.target.width || + mouse_point.y < 0 || mouse_point.y >= win32vars.target.height){ + win32vars.mouse.out_of_window = 1; + } + } + else{ + win32vars.mouse.out_of_window = 1; + } + + bool32 shift = win32vars.input_data.control_keys[CONTROL_KEY_SHIFT]; + bool32 caps_lock = win32vars.input_data.caps_lock; + for (i32 i = 0; i < win32vars.input_data.press_count; ++i){ + i16 keycode = win32vars.input_data.press[i].keycode; + win32vars.input_data.press[i].character = + keycode_to_character_ascii(&win32vars.key_codes, keycode, + shift, caps_lock); + + win32vars.input_data.press[i].character_no_caps_lock = + keycode_to_character_ascii(&win32vars.key_codes, keycode, + shift, 0); + } + + for (i32 i = 0; i < win32vars.input_data.hold_count; ++i){ + i16 keycode = win32vars.input_data.hold[i].keycode; + win32vars.input_data.hold[i].character = + keycode_to_character_ascii(&win32vars.key_codes, keycode, + shift, caps_lock); + + win32vars.input_data.hold[i].character_no_caps_lock = + keycode_to_character_ascii(&win32vars.key_codes, keycode, + shift, 0); + } + + win32vars.clipboard_contents = {}; + if (win32vars.clipboard_sequence != 0){ + DWORD new_number = GetClipboardSequenceNumber(); + if (new_number != win32vars.clipboard_sequence){ + win32vars.clipboard_sequence = new_number; + if (win32vars.next_clipboard_is_self){ + win32vars.next_clipboard_is_self = 0; + } + else if (IsClipboardFormatAvailable(CF_TEXT)){ + if (OpenClipboard(win32vars.window_handle)){ + HANDLE clip_data; + clip_data = GetClipboardData(CF_TEXT); + if (clip_data){ + win32vars.clipboard_contents.str = (u8*)GlobalLock(clip_data); + if (win32vars.clipboard_contents.str){ + win32vars.clipboard_contents.size = str_size((char*)win32vars.clipboard_contents.str); + GlobalUnlock(clip_data); + } + } + CloseClipboard(); + } + } + } + } + + i32 redraw = InterlockedExchange(&win32vars.force_redraw, 0); + ProfileEnd(OS_input); + + Application_Step_Result result = + app_step(&win32vars.main_thread, + &win32vars.key_codes, + &win32vars.input_data, &win32vars.mouse, + 1, &win32vars.target, + &win32memory, + win32vars.clipboard_contents, + first, redraw); + + ProfileStart(OS_frame_out); + first = 0; + switch (result.mouse_cursor_type){ + case APP_MOUSE_CURSOR_ARROW: + SetCursor(win32vars.cursor_arrow); break; + + case APP_MOUSE_CURSOR_IBEAM: + SetCursor(win32vars.cursor_ibeam); break; + + case APP_MOUSE_CURSOR_LEFTRIGHT: + SetCursor(win32vars.cursor_leftright); break; + + case APP_MOUSE_CURSOR_UPDOWN: + SetCursor(win32vars.cursor_updown); break; + } + + if (result.redraw) Win32RedrawScreen(hdc); + ProfileEnd(OS_frame_out); + + ProfileStart(frame_sleep); + i64 timer_end = system_time(); + i64 end_target = (timer_start + FRAME_TIME); + + system_release_lock(FRAME_LOCK); + while (timer_end < end_target){ + DWORD samount = (DWORD)((end_target - timer_end) / 1000); + if (samount > 0) Sleep(samount); + timer_end = system_time(); + } + system_aquire_lock(FRAME_LOCK); + timer_start = system_time(); + ProfileEnd(frame_sleep); + } + + return 0; +} + +#if FRED_INTERNAL +const i32 INTERNAL_event_index_count = __COUNTER__; +u32 INTERNAL_event_hits[__COUNTER__]; +#endif + +// BOTTOM +