/* * Example use of customization API */ // NOTE(allen): NEW THINGS TO LOOK FOR: // mapid_user_custom - define maps other than the built in global and file maps // // inherit_map - override bindings or add new bindings in a new map with another // // set_hook - if you were using start_hook, it is still available if you use set_hook // // push_parameter - see description of parameter stack immediately below // clear_parameters // exec_command_keep_stack // // THE PARAMETER STACK: // In this version I have introduced a parameter stack. // Calls to commands through exec_command can now be parameterized // by first pushing parameters onto that stack. This is achieved through // the push_parameter. If you look at the signature for the function it // uses a "Dynamic" struct, but 4coder_helper.h has overrides that accept // ints or strings. The helper functions also take care of copying the // strings inline into the stack so that you don't have to maintain your copy. // // If you'd like to optimize out the extra copy it will work, just use the // main app.push_parameter and keep the memory of the string alive until // the stack is cleared. // // A call to exec_command executes the command with the current stack, // then clears the stack. To keep the stack use exec_command_keep_stack. // // If you would like to allocate your own memory, you can tie your memory // to the parameter stack by calling push_memory which takes a cmd_context and len. // It will return a char* to your memory block. Until you call clear_parameters // or exec_command the memory will remain on the stack for you to use. Memory // chunks on the stack are ignored by commands that use parameters, so your memory // will not influence the behavior of any commands. // #include "4coder_custom.h" #include "4coder_helper.h" #define exec_command_keep_stack app.exec_command_keep_stack #define clear_parameters app.clear_parameters #define get_active_buffer app.get_active_buffer #define exec_command(cmd_context, id) \ exec_command_keep_stack(cmd_context, id); \ clear_parameters(cmd_context) #define push_parameter(cmd_context, ...) push_parameter_helper(cmd_context, app, __VA_ARGS__) #define push_memory(cmd_context, len) app.push_memory(cmd_context, len) #define literal(s) s, (sizeof(s)-1) // NOTE(allen): All of your custom ids should be >= mapid_user_custom. // I recommend enumerating your own map ids as shown here. enum My_Maps{ my_code_map = mapid_user_custom, }; HOOK_SIG(my_start){ exec_command(cmd_context, cmdid_open_panel_vsplit); exec_command(cmd_context, cmdid_change_active_panel); } CUSTOM_COMMAND_SIG(open_my_files){ // NOTE(allen): The command cmdid_interactive_open has is now able to // open a file specified on the parameter stack. If the file does not // exist cmdid_interactive_open behaves as usual. push_parameter(cmd_context, par_name, literal("w:/4ed/data/test/basic.cpp")); exec_command(cmd_context, cmdid_interactive_open); exec_command(cmd_context, cmdid_change_active_panel); char my_file[256]; int my_file_len; my_file_len = sizeof("w:/4ed/data/test/basic.txt") - 1; for (int i = 0; i < my_file_len; ++i){ my_file[i] = ("w:/4ed/data/test/basic.txt")[i]; } // NOTE(allen): null terminators are not needed for strings. push_parameter(cmd_context, par_name, my_file, my_file_len); exec_command(cmd_context, cmdid_interactive_open); exec_command(cmd_context, cmdid_change_active_panel); } char *get_extension(const char *filename, int len, int *extension_len){ char *c = (char*)(filename + len - 1); char *end = c; while (*c != '.' && c > filename) --c; *extension_len = (int)(end - c); return c+1; } bool str_match(const char *a, int len_a, const char *b, int len_b){ bool result = 0; if (len_a == len_b){ char *end = (char*)(a + len_a); while (a < end && *a == *b){ ++a; ++b; } if (a == end) result = 1; } return result; } HOOK_SIG(my_file_settings){ Buffer_Summary buffer = get_active_buffer(cmd_context); int treat_as_code = 0; // NOTE(allen): This checks buffer.file_name just in case get_active_buffer returns back // a null buffer (where every member is 0). if (buffer.file_name && buffer.size < (16 << 20)){ int extension_len; char *extension = get_extension(buffer.file_name, buffer.file_name_len, &extension_len); if (str_match(extension, extension_len, literal("cpp"))) treat_as_code = 1; else if (str_match(extension, extension_len, literal("h"))) treat_as_code = 1; else if (str_match(extension, extension_len, literal("c"))) treat_as_code = 1; else if (str_match(extension, extension_len, literal("hpp"))) treat_as_code = 1; } push_parameter(cmd_context, par_lex_as_cpp_file, treat_as_code); push_parameter(cmd_context, par_wrap_lines, !treat_as_code); push_parameter(cmd_context, par_key_mapid, (treat_as_code)?(my_code_map):(mapid_file)); push_parameter(cmd_context, par_end_line_mode, EOL_USE_CRLF); exec_command(cmd_context, cmdid_set_settings); } 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; // NOTE(allen): Right now hooks have no loyalties to maps, all hooks are // global and once set they always apply. set_hook(context, hook_start, my_start); set_hook(context, hook_open_file, my_file_settings); begin_map(context, mapid_global); 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_buffer); bind(context, 'i', MDFR_CTRL, cmdid_interactive_switch_buffer); 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); // NOTE(allen): Go look at open_my_files, that's the only point of this being here. bind_me(context, 'M', MDFR_ALT | MDFR_CTRL, open_my_files); end_map(context); begin_map(context, my_code_map); // NOTE(allen): Set this map (my_code_map == mapid_user_custom) to // inherit from mapid_file. When searching if a key is bound // in this map, if it is not found here it will then search mapid_file. // // If this is not set, it defaults to mapid_global. inherit_map(context, mapid_file); // NOTE(allen): This demonstrates that children can override parent's bindings. bind(context, codes->right, MDFR_CTRL, cmdid_seek_alphanumeric_or_camel_right); bind(context, codes->left, MDFR_CTRL, cmdid_seek_alphanumeric_or_camel_left); // NOTE(allen): Not currently functional bind(context, '\t', MDFR_CTRL, cmdid_auto_tab); end_map(context); begin_map(context, mapid_file); // NOTE(allen): Binding this essentially binds all key combos that // would normally insert a character into a buffer. // Or apply this rule (which always works): if the code for the key // is not in the codes struct, it is a vanilla key. // It is possible to override this binding for individual keys. bind_vanilla_keys(context, cmdid_write_character); 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_whitespace_right); bind(context, codes->left, MDFR_CTRL, cmdid_seek_whitespace_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, 'Z', MDFR_CTRL, cmdid_timeline_scrub); bind(context, 'z', MDFR_CTRL, cmdid_undo); bind(context, 'y', MDFR_CTRL, cmdid_redo); bind(context, codes->left, MDFR_ALT, cmdid_increase_rewind_speed); bind(context, codes->right, MDFR_ALT, cmdid_increase_fastforward_speed); bind(context, codes->down, MDFR_ALT, cmdid_stop_rewind_fastforward); bind(context, 'h', MDFR_CTRL, cmdid_history_backward); bind(context, 'H', MDFR_CTRL, cmdid_history_forward); 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); // NOTE(allen): These whitespace manipulators are not currently functional 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, 'K', MDFR_CTRL, cmdid_kill_buffer); 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; }