diff --git a/4coder_custom.cpp b/4coder_custom.cpp index 2a022546..33d70989 100644 --- a/4coder_custom.cpp +++ b/4coder_custom.cpp @@ -1,6 +1,8 @@ -// Set which customization you want to use with this define or write your own +// Set to Custom_None if you're going to drop in your own include/get_bindings call. +// or choose one of the preexisting customizations #define Custom_Current Custom_Default +#define Custom_None -1 #define Custom_Default 0 // The following customization schemes are power users only: @@ -8,1219 +10,25 @@ // TOP -#include "4coder_custom.h" -#define FCPP_STRING_IMPLEMENTATION -#include "4coder_string.h" - -#define UseInterfacesThatArePhasingOut 0 -#include "4coder_helper.h" - -#ifndef literal -#define literal(s) (s), (sizeof(s)-1) -#endif - -// NOTE(allen|a3.3): All of your custom ids should be enumerated -// as shown here, they may start at 0, and you can only have -// 2^24 of them so don't be wasteful! -enum My_Maps{ - my_code_map -}; - -HOOK_SIG(my_start){ - exec_command(app, cmdid_open_panel_vsplit); - exec_command(app, cmdid_change_active_panel); - - app->change_theme(app, literal("4coder")); - app->change_font(app, literal("liberation sans")); - - // Theme options: - // "4coder" - // "Handmade Hero" - // "Twilight" - // "Woverine" - // "stb" - - // Font options: - // "liberation sans" - // "liberation mono" - // "hack" - // "cutive mono" - // "inconsolata" - - // no meaning for return - return(0); -} - -HOOK_SIG(my_file_settings){ - // NOTE(allen|a4): In hooks that want parameters, such as this file - // created hook. The file created hook is guaranteed to have only - // and exactly one buffer parameter. In normal command callbacks - // there are no parameter buffers. - Buffer_Summary buffer = app->get_parameter_buffer(app, 0); - assert(buffer.exists); - - int treat_as_code = 0; - - if (buffer.file_name && buffer.size < (16 << 20)){ - String ext = file_extension(make_string(buffer.file_name, buffer.file_name_len)); - if (match(ext, make_lit_string("cpp"))) treat_as_code = 1; - else if (match(ext, make_lit_string("h"))) treat_as_code = 1; - else if (match(ext, make_lit_string("c"))) treat_as_code = 1; - else if (match(ext, make_lit_string("hpp"))) treat_as_code = 1; - } - - push_parameter(app, par_lex_as_cpp_file, treat_as_code); - push_parameter(app, par_wrap_lines, !treat_as_code); - push_parameter(app, par_key_mapid, (treat_as_code)?((int)my_code_map):((int)mapid_file)); - exec_command(app, cmdid_set_settings); - - // no meaning for return - return(0); -} - -unsigned char blink_t = 0; - -HOOK_SIG(my_frame){ - // NOTE(allen|a4): Please use me sparingly! This get's called roughly once every *33 ms* if everything is going well. - // But if you start doing a lot in here, there's nothing 4codes does to stop you from making it a lot slower. - - int result = 0; - Theme_Color theme_color_1[] = { - {Stag_Cursor, 0x00FF00}, - {Stag_At_Cursor, 0x000000} - }; - - Theme_Color theme_color_2[2] = { - {Stag_Cursor, 0x000000}, - {Stag_At_Cursor, 0xFFFFFF} - }; - - Theme_Color *theme_color; - - ++blink_t; - - if (blink_t == 20 || blink_t == 40){ - if (blink_t == 20){ - theme_color = theme_color_2; - } - else{ - theme_color = theme_color_1; - blink_t = 0; - } - - result = 1; - app->set_theme_colors(app, theme_color, 2); - } - - // return non-zero if you do anything that might change the screen! - // 4coder won't redraw unless you tell it you've changed something important. - // If you redraw *all* the time it's going to slow 4coder down and increase power consumption. - return(result); -} - -static void -write_string(Application_Links *app, String string){ - Buffer_Summary buffer = app->get_active_buffer(app); - app->buffer_replace_range(app, &buffer, buffer.buffer_cursor_pos, buffer.buffer_cursor_pos, string.str, string.size); -} - -CUSTOM_COMMAND_SIG(write_increment){ - write_string(app, make_lit_string("++")); -} - -CUSTOM_COMMAND_SIG(write_decrement){ - write_string(app, make_lit_string("--")); -} - -CUSTOM_COMMAND_SIG(write_allen_todo){ - write_string(app, make_lit_string("// TODO(allen): ")); -} - -CUSTOM_COMMAND_SIG(write_allen_note){ - write_string(app, make_lit_string("// NOTE(allen): ")); -} - -static void -basic_seek(Application_Links *app, Command_ID seek_type, unsigned int flags){ - push_parameter(app, par_flags, flags); - exec_command(app, seek_type); -} - -#define SEEK_COMMAND(n, dir, flags)\ -CUSTOM_COMMAND_SIG(seek_##n##_##dir){\ - basic_seek(app, cmdid_seek_##dir, flags);\ -} - -SEEK_COMMAND(whitespace, right, BoundryWhitespace) -SEEK_COMMAND(whitespace, left, BoundryWhitespace) -SEEK_COMMAND(token, right, BoundryToken) -SEEK_COMMAND(token, left, BoundryToken) -SEEK_COMMAND(white_or_token, right, BoundryToken | BoundryWhitespace) -SEEK_COMMAND(white_or_token, left, BoundryToken | BoundryWhitespace) -SEEK_COMMAND(alphanumeric, right, BoundryAlphanumeric) -SEEK_COMMAND(alphanumeric, left, BoundryAlphanumeric) -SEEK_COMMAND(alphanumeric_or_camel, right, BoundryAlphanumeric | BoundryCamelCase) -SEEK_COMMAND(alphanumeric_or_camel, left, BoundryAlphanumeric | BoundryCamelCase) - -static void -long_braces(Application_Links *app, char *text, int size){ - View_Summary view; - Buffer_Summary buffer; - int pos; - - view = app->get_active_view(app); - buffer = app->get_buffer(app, view.buffer_id); - - pos = view.cursor.pos; - app->buffer_replace_range(app, &buffer, pos, pos, text, size); - app->view_set_cursor(app, &view, seek_pos(pos + 2), 1); - - push_parameter(app, par_range_start, pos); - push_parameter(app, par_range_end, pos + size); - push_parameter(app, par_clear_blank_lines, 0); - exec_command(app, cmdid_auto_tab_range); -} - -CUSTOM_COMMAND_SIG(open_long_braces){ - char text[] = "{\n\n}"; - int size = sizeof(text) - 1; - long_braces(app, text, size); -} - -CUSTOM_COMMAND_SIG(open_long_braces_semicolon){ - char text[] = "{\n\n};"; - int size = sizeof(text) - 1; - long_braces(app, text, size); -} - -CUSTOM_COMMAND_SIG(open_long_braces_break){ - char text[] = "{\n\n}break;"; - int size = sizeof(text) - 1; - long_braces(app, text, size); -} - -CUSTOM_COMMAND_SIG(paren_wrap){ - View_Summary view; - Buffer_Summary buffer; - - char text1[] = "("; - int size1 = sizeof(text1) - 1; - - char text2[] = ")"; - int size2 = sizeof(text2) - 1; - - Range range; - int pos; - - view = app->get_active_view(app); - buffer = app->get_active_buffer(app); - - range = get_range(&view); - pos = range.max; - app->buffer_replace_range(app, &buffer, pos, pos, text2, size2); - - pos = range.min; - app->buffer_replace_range(app, &buffer, pos, pos, text1, size1); -} - -CUSTOM_COMMAND_SIG(if0_off){ - View_Summary view; - Buffer_Summary buffer; - - char text1[] = "\n#if 0"; - int size1 = sizeof(text1) - 1; - - char text2[] = "#endif\n"; - int size2 = sizeof(text2) - 1; - - Range range; - int pos; - - view = app->get_active_view(app); - buffer = app->get_active_buffer(app); - - range = get_range(&view); - pos = range.min; - - app->buffer_replace_range(app, &buffer, pos, pos, text1, size1); - - push_parameter(app, par_range_start, pos); - push_parameter(app, par_range_end, pos); - exec_command(app, cmdid_auto_tab_range); - - app->refresh_view(app, &view); - range = get_range(&view); - pos = range.max; - - app->buffer_replace_range(app, &buffer, pos, pos, text2, size2); - - push_parameter(app, par_range_start, pos); - push_parameter(app, par_range_end, pos); - exec_command(app, cmdid_auto_tab_range); -} - -CUSTOM_COMMAND_SIG(backspace_word){ - View_Summary view; - Buffer_Summary buffer; - int pos2, pos1; - - view = app->get_active_view(app); - - pos2 = view.cursor.pos; - exec_command(app, seek_alphanumeric_left); - app->refresh_view(app, &view); - pos1 = view.cursor.pos; - - buffer = app->get_buffer(app, view.buffer_id); - app->buffer_replace_range(app, &buffer, pos1, pos2, 0, 0); -} - -CUSTOM_COMMAND_SIG(snipe_token_or_word){ - View_Summary view; - Buffer_Summary buffer; - int pos1, pos2; - - view = app->get_active_view(app); - - push_parameter(app, par_flags, BoundryToken | BoundryWhitespace); - exec_command(app, cmdid_seek_left); - app->refresh_view(app, &view); - pos1 = view.cursor.pos; - - push_parameter(app, par_flags, BoundryToken | BoundryWhitespace); - exec_command(app, cmdid_seek_right); - app->refresh_view(app, &view); - pos2 = view.cursor.pos; - - Range range = make_range(pos1, pos2); - buffer = app->get_buffer(app, view.buffer_id); - app->buffer_replace_range(app, &buffer, range.start, range.end, 0, 0); -} - -CUSTOM_COMMAND_SIG(switch_to_compilation){ - View_Summary view; - Buffer_Summary buffer; - - char name[] = "*compilation*"; - int name_size = sizeof(name)-1; - - view = app->get_active_view(app); - buffer = app->get_buffer_by_name(app, name, name_size); - - app->view_set_buffer(app, &view, buffer.buffer_id); -} - -CUSTOM_COMMAND_SIG(move_up_10){ - View_Summary view; - float x, y; - - view = app->get_active_view(app); - x = view.preferred_x; - - if (view.unwrapped_lines){ - y = view.cursor.unwrapped_y; - } - else{ - y = view.cursor.wrapped_y; - } - - y -= 10*view.line_height; - - app->view_set_cursor(app, &view, seek_xy(x, y, 0, view.unwrapped_lines), 0); -} - -CUSTOM_COMMAND_SIG(move_down_10){ - View_Summary view; - float x, y; - - view = app->get_active_view(app); - x = view.preferred_x; - - if (view.unwrapped_lines){ - y = view.cursor.wrapped_y; - } - else{ - y = view.cursor.wrapped_y; - } - - y += 10*view.line_height; - - app->view_set_cursor(app, &view, seek_xy(x, y, 0, view.unwrapped_lines), 0); -} - -CUSTOM_COMMAND_SIG(open_file_in_quotes){ - View_Summary view; - Buffer_Summary buffer; - char short_file_name[128]; - int pos, start, end, size; - - view = app->get_active_view(app); - buffer = app->get_buffer(app, view.buffer_id); - pos = view.cursor.pos; - app->buffer_seek_delimiter(app, &buffer, pos, '"', 1, &end); - app->buffer_seek_delimiter(app, &buffer, pos, '"', 0, &start); - - ++start; - size = end - start; - - // NOTE(allen): This check is necessary because app->buffer_read_range - // requiers that the output buffer you provide is at least (end - start) bytes long. - if (size < sizeof(short_file_name)){ - char file_name_[256]; - String file_name = make_fixed_width_string(file_name_); - - app->buffer_read_range(app, &buffer, start, end, short_file_name); - - copy(&file_name, make_string(buffer.file_name, buffer.file_name_len)); - remove_last_folder(&file_name); - append(&file_name, make_string(short_file_name, size)); - - exec_command(app, cmdid_change_active_panel); - push_parameter(app, par_name, expand_str(file_name)); - exec_command(app, cmdid_interactive_open); - } -} - -CUSTOM_COMMAND_SIG(goto_line){ - int line_number; - Query_Bar bar; - char string_space[256]; - - bar.prompt = make_lit_string("Goto Line: "); - bar.string = make_fixed_width_string(string_space); - - if (query_user_number(app, &bar)){ - line_number = str_to_int(bar.string); - active_view_to_line(app, line_number); - } -} - -CUSTOM_COMMAND_SIG(search); -CUSTOM_COMMAND_SIG(reverse_search); - -static void -isearch(Application_Links *app, int start_reversed){ - View_Summary view; - Buffer_Summary buffer; - User_Input in; - Query_Bar bar; - - if (app->start_query_bar(app, &bar, 0) == 0) return; - - Range match; - int reverse = start_reversed; - int pos; - - view = app->get_active_view(app); - buffer = app->get_buffer(app, view.buffer_id); - - pos = view.cursor.pos; - match = make_range(pos, pos); - - char bar_string_space[256]; - bar.string = make_fixed_width_string(bar_string_space); - - String isearch = make_lit_string("I-Search: "); - String rsearch = make_lit_string("Reverse-I-Search: "); - - while (1){ - // NOTE(allen): Change the bar's prompt to match the current direction. - if (reverse) bar.prompt = rsearch; - else bar.prompt = isearch; - - in = app->get_user_input(app, EventOnAnyKey, EventOnEsc | EventOnButton); - if (in.abort) break; - - // NOTE(allen): If we're getting mouse events here it's a 4coder bug, because we - // only asked to intercept key events. - assert(in.type == UserInputKey); - - int made_change = 0; - if (in.key.keycode == '\n' || in.key.keycode == '\t'){ - break; - } - else if (in.key.character && key_is_unmodified(&in.key)){ - append(&bar.string, in.key.character); - made_change = 1; - } - else if (in.key.keycode == key_back){ - if (bar.string.size > 0){ - --bar.string.size; - made_change = 1; - } - } - - int step_forward = 0; - int step_backward = 0; - - if (CommandEqual(in.command, search) || - in.key.keycode == key_page_down || in.key.keycode == key_down) step_forward = 1; - if (CommandEqual(in.command, reverse_search) || - in.key.keycode == key_page_up || in.key.keycode == key_up) step_backward = 1; - - int start_pos = pos; - if (step_forward && reverse){ - start_pos = match.start + 1; - pos = start_pos; - reverse = 0; - step_forward = 0; - } - if (step_backward && !reverse){ - start_pos = match.start - 1; - pos = start_pos; - reverse = 1; - step_backward = 0; - } - - if (in.key.keycode != key_back){ - int new_pos; - if (reverse){ - app->buffer_seek_string(app, &buffer, start_pos - 1, bar.string.str, bar.string.size, 0, &new_pos); - if (new_pos >= 0){ - if (step_backward){ - pos = new_pos; - start_pos = new_pos; - app->buffer_seek_string(app, &buffer, start_pos - 1, bar.string.str, bar.string.size, 0, &new_pos); - if (new_pos < 0) new_pos = start_pos; - } - match.start = new_pos; - match.end = match.start + bar.string.size; - } - } - else{ - app->buffer_seek_string(app, &buffer, start_pos + 1, bar.string.str, bar.string.size, 1, &new_pos); - if (new_pos < buffer.size){ - if (step_forward){ - pos = new_pos; - start_pos = new_pos; - app->buffer_seek_string(app, &buffer, start_pos + 1, bar.string.str, bar.string.size, 1, &new_pos); - if (new_pos >= buffer.size) new_pos = start_pos; - } - match.start = new_pos; - match.end = match.start + bar.string.size; - } - } - } - else{ - match.end = match.start + bar.string.size; - } - - app->view_set_highlight(app, &view, match.start, match.end, 1); - } - app->view_set_highlight(app, &view, 0, 0, 0); - if (in.abort) return; - - app->view_set_cursor(app, &view, seek_pos(match.min), 1); -} - -CUSTOM_COMMAND_SIG(search){ - isearch(app, 0); -} - -CUSTOM_COMMAND_SIG(reverse_search){ - isearch(app, 1); -} - -CUSTOM_COMMAND_SIG(rewrite_as_single_caps){ - View_Summary view; - Buffer_Summary buffer; - Range range; - String string; - int is_first, i; - - exec_command(app, seek_token_left); - view = app->get_active_view(app); - range.min = view.cursor.pos; - - exec_command(app, seek_token_right); - app->refresh_view(app, &view); - range.max = view.cursor.pos; - - string.str = (char*)app->memory; - string.size = range.max - range.min; - assert(string.size < app->memory_size); - - buffer = app->get_buffer(app, view.buffer_id); - app->buffer_read_range(app, &buffer, range.min, range.max, string.str); - - is_first = 1; - for (i = 0; i < string.size; ++i){ - if (char_is_alpha_true(string.str[i])){ - if (is_first) is_first = 0; - else string.str[i] = char_to_lower(string.str[i]); - } - else{ - is_first = 1; - } - } - - app->buffer_replace_range(app, &buffer, range.min, range.max, string.str, string.size); -} - -// TODO(allen): -// get range by specific "word" type (for example "get token range") -// read range by specific "word" type -// Dream API for rewrite_as_single_caps: -#if 0 -{ - rewrite = get_rewrite(app, ByToken); - string = get_rewrite_string(app, &rewrite, app->memory, app->memory_size); - - is_first = 1; - for (i = 0; i < string.size; ++i){ - if (char_is_alpha_true(string.str[i])){ - if (is_first) is_first = 0; - else string.str[i] = char_to_lower(string.str[i]); - } - else{ - is_first = 1; - } - } - - do_rewrite(app, &rewrite, string); -} -#endif - -CUSTOM_COMMAND_SIG(replace_in_range){ - Query_Bar replace; - char replace_space[1024]; - replace.prompt = make_lit_string("Replace: "); - replace.string = make_fixed_width_string(replace_space); - - Query_Bar with; - char with_space[1024]; - with.prompt = make_lit_string("With: "); - with.string = make_fixed_width_string(with_space); - - if (!query_user_string(app, &replace)) return; - if (replace.string.size == 0) return; - - if (!query_user_string(app, &with)) return; - - String r, w; - r = replace.string; - w = with.string; - - Buffer_Summary buffer; - View_Summary view; - - view = app->get_active_view(app); - buffer = app->get_buffer(app, view.buffer_id); - - Range range = get_range(&view); - - int pos, new_pos; - pos = range.min; - app->buffer_seek_string(app, &buffer, pos, r.str, r.size, 1, &new_pos); - - while (new_pos + r.size < range.end){ - app->buffer_replace_range(app, &buffer, new_pos, new_pos + r.size, w.str, w.size); - range = get_range(&view); - pos = new_pos + w.size; - app->buffer_seek_string(app, &buffer, pos, r.str, r.size, 1, &new_pos); - } -} - -CUSTOM_COMMAND_SIG(query_replace){ - Query_Bar replace; - char replace_space[1024]; - replace.prompt = make_lit_string("Replace: "); - replace.string = make_fixed_width_string(replace_space); - - Query_Bar with; - char with_space[1024]; - with.prompt = make_lit_string("With: "); - with.string = make_fixed_width_string(with_space); - - if (!query_user_string(app, &replace)) return; - if (replace.string.size == 0) return; - - if (!query_user_string(app, &with)) return; - - String r, w; - r = replace.string; - w = with.string; - - Query_Bar bar; - Buffer_Summary buffer; - View_Summary view; - int pos, new_pos; - - bar.prompt = make_lit_string("Replace? (y)es, (n)ext, (esc)\n"); - bar.string = empty_string(); - - app->start_query_bar(app, &bar, 0); - - view = app->get_active_view(app); - buffer = app->get_buffer(app, view.buffer_id); - - pos = view.cursor.pos; - app->buffer_seek_string(app, &buffer, pos, r.str, r.size, 1, &new_pos); - - User_Input in = {0}; - while (new_pos < buffer.size){ - Range match = make_range(new_pos, new_pos + r.size); - app->view_set_highlight(app, &view, match.min, match.max, 1); - - in = app->get_user_input(app, EventOnAnyKey, EventOnButton); - if (in.abort || in.key.keycode == key_esc || !key_is_unmodified(&in.key)) break; - - if (in.key.character == 'y' || in.key.character == 'Y' || in.key.character == '\n' || in.key.character == '\t'){ - app->buffer_replace_range(app, &buffer, match.min, match.max, w.str, w.size); - pos = match.start + w.size; - } - else{ - pos = match.max; - } - - app->buffer_seek_string(app, &buffer, pos, r.str, r.size, 1, &new_pos); - } - - app->view_set_highlight(app, &view, 0, 0, 0); - if (in.abort) return; - - app->view_set_cursor(app, &view, seek_pos(pos), 1); -} - -CUSTOM_COMMAND_SIG(close_all_code){ - String extension; - Buffer_Summary buffer; - - for (buffer = app->get_buffer_first(app); - buffer.exists; - app->get_buffer_next(app, &buffer)){ - - extension = file_extension(make_string(buffer.file_name, buffer.file_name_len)); - if (match(extension, make_lit_string("cpp")) || - match(extension, make_lit_string("hpp")) || - match(extension, make_lit_string("c")) || - match(extension, make_lit_string("h"))){ - // - push_parameter(app, par_buffer_id, buffer.buffer_id); - exec_command(app, cmdid_kill_buffer); - } - } -} - -CUSTOM_COMMAND_SIG(open_all_code){ - // NOTE(allen|a3.4.4): This method of getting the hot directory works - // because this custom.cpp gives no special meaning to app->memory - // and doesn't set up a persistent allocation system within app->memory. - // push_directory isn't a very good option since it's tied to the parameter - // stack, so I am phasing that idea out now. - String dir = make_string(app->memory, 0, app->memory_size); - dir.size = app->directory_get_hot(app, dir.str, dir.memory_size); - int dir_size = dir.size; - - // NOTE(allen|a3.4.4): Here we get the list of files in this directory. - // Notice that we free_file_list at the end. - File_List list = app->get_file_list(app, dir.str, dir.size); - - for (int i = 0; i < list.count; ++i){ - File_Info *info = list.infos + i; - if (!info->folder){ - String extension = file_extension(info->filename); - if (match(extension, make_lit_string("cpp")) || - match(extension, make_lit_string("hpp")) || - match(extension, make_lit_string("c")) || - match(extension, make_lit_string("h"))){ - // NOTE(allen): There's no way in the 4coder API to use relative - // paths at the moment, so everything should be full paths. Which is - // managable. Here simply set the dir string size back to where it - // was originally, so that new appends overwrite old ones. - dir.size = dir_size; - append(&dir, info->filename); - push_parameter(app, par_name, dir.str, dir.size); - //push_parameter(app, par_do_in_background, 1); - exec_command(app, cmdid_interactive_open); - } - } - } - - app->free_file_list(app, list); -} - -CUSTOM_COMMAND_SIG(execute_any_cli){ - Query_Bar bar_out, bar_cmd; - String hot_directory; - char space[1024], more_space[1024], even_more_space[1024]; - - bar_out.prompt = make_lit_string("Output Buffer: "); - bar_out.string = make_fixed_width_string(space); - if (!query_user_string(app, &bar_out)) return; - - bar_cmd.prompt = make_lit_string("Command: "); - bar_cmd.string = make_fixed_width_string(more_space); - if (!query_user_string(app, &bar_cmd)) return; - - hot_directory = make_fixed_width_string(even_more_space); - hot_directory.size = app->directory_get_hot(app, hot_directory.str, hot_directory.memory_size); - - push_parameter(app, par_flags, CLI_OverlapWithConflict); - push_parameter(app, par_name, bar_out.string.str, bar_out.string.size); - push_parameter(app, par_cli_path, hot_directory.str, hot_directory.size); - push_parameter(app, par_cli_command, bar_cmd.string.str, bar_cmd.string.size); - exec_command(app, cmdid_command_line); -} - -CUSTOM_COMMAND_SIG(execute_arbitrary_command){ - // NOTE(allen): This isn't a super powerful version of this command, I will expand - // upon it so that it has all the cmdid_* commands by default. However, with this - // as an example you have everything you need to make it work already. You could - // even use app->memory to create a hash table in the start hook. - Query_Bar bar; - char space[1024]; - bar.prompt = make_lit_string("Command: "); - bar.string = make_fixed_width_string(space); - - if (!query_user_string(app, &bar)) return; - - // NOTE(allen): Here I chose to end this query bar because when I call another - // command it might ALSO have query bars and I don't want this one hanging - // around at that point. Since the bar exists on my stack the result of the query - // is still available in bar.string though. - app->end_query_bar(app, &bar, 0); - - if (match(bar.string, make_lit_string("open all code"))){ - exec_command(app, open_all_code); - } - else if(match(bar.string, make_lit_string("close all code"))){ - exec_command(app, close_all_code); - } - else if (match(bar.string, make_lit_string("open menu"))){ - exec_command(app, cmdid_open_menu); - } - else if (match(bar.string, make_lit_string("dos lines"))){ - exec_command(app, cmdid_eol_dosify); - } - else if (match(bar.string, make_lit_string("nix lines"))){ - exec_command(app, cmdid_eol_nixify); - } - else{ - // TODO(allen): feedback message - } -} - -CUSTOM_COMMAND_SIG(open_in_other){ - exec_command(app, cmdid_change_active_panel); - exec_command(app, cmdid_interactive_open); -} - -CUSTOM_COMMAND_SIG(open_my_files){ - // NOTE(allen|a3.1): EXAMPLE probably not useful in practice. - // - // The command cmdid_interactive_open can now open - // a file specified on the parameter stack. If the file does not exist - // cmdid_interactive_open behaves as usual. If par_do_in_background - // is set to true the command is prevented from changing the view under - // any circumstance. - push_parameter(app, par_name, literal("w:/4ed/data/test/basic.cpp")); - exec_command(app, cmdid_interactive_open); - - exec_command(app, 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|a3.1): null terminators are not needed for strings. - push_parameter(app, par_name, my_file, my_file_len); - exec_command(app, cmdid_interactive_open); - - exec_command(app, cmdid_change_active_panel); -} - -CUSTOM_COMMAND_SIG(build_at_launch_location){ - // NOTE(allen|a3.3): EXAMPLE probably not all that useful in practice. - // - // An example of calling build by setting all - // parameters directly. This only works if build.bat can be called - // from the directory the application is launched at. - push_parameter(app, par_flags, CLI_OverlapWithConflict); - push_parameter(app, par_name, literal("*compilation*")); - push_parameter(app, par_cli_path, literal(".")); - push_parameter(app, par_cli_command, literal("build")); - exec_command(app, cmdid_command_line); -} - -CUSTOM_COMMAND_SIG(build_search){ - // NOTE(allen|a3.3): An example of traversing the filesystem through parent - // directories looking for a file, in this case a batch file to execute. - // - // - // Step 1: Grab all of the user memory (or, you know, less if you've got better - // thing to do with some of it). Make a string and store the hot directory in it. - // - // Step 2: app->file_exists queries the file system to see if "/build.bat" exists. - // If it does exist several parameters are pushed and cmdid_command_line is executed: - // - par_flags: flags for specifiying behaviors - // CLI_OverlapWithConflict - (on by default) if another CLI is still using the output buffer - // that process is detached from the buffer and this process executes outputing to the buffer - // CLI_AlwaysBindToView - if set, the current view always switches to the output buffer - // even if the output buffer is open in another view - // - // - par_name: the name of the buffer to fill with the output from the process - // - par_buffer_id: the buffer_id of the buffer to to fill with output - // If both are set buffer_id is used and the name is ignored. - // If neither is set the command runs without storing output anywhere. - // - // - par_cli_path: sets the path from which the command is executed - // If this parameter is unset the command runs from the hot directory. - // - // - par_cli_command: sets the actual command to be executed, this can be almost any - // command that you could execute through a command line interface. - // If this parameter is unset the command get's it's command from the range between - // the mark and cursor. - // - // Step 3: If the batch file did not exist change the dir string to the parent directory using - // app->directory_cd. The cd function can also be used to navigate to subdirectories. - // It returns true if it can actually move in the specified direction, and false otherwise. - // - // This doesn't actually change the hot directory of 4coder, it's only effect is to - // modify the string you passed in to reflect the change in directory if that change was possible. - - int keep_going = 1; - int old_size; - String dir = make_string(app->memory, 0, app->memory_size); - dir.size = app->directory_get_hot(app, dir.str, dir.memory_size); - - while (keep_going){ - old_size = dir.size; - append(&dir, "build.bat"); - - if (app->file_exists(app, dir.str, dir.size)){ - dir.size = old_size; - - push_parameter(app, par_flags, CLI_OverlapWithConflict); - push_parameter(app, par_name, literal("*compilation*")); - push_parameter(app, par_cli_path, dir.str, dir.size); - - if (append(&dir, "build")){ - push_parameter(app, par_cli_command, dir.str, dir.size); - exec_command(app, cmdid_command_line); - } - else{ - app->clear_parameters(app); - } - - return; - } - dir.size = old_size; - - if (app->directory_cd(app, dir.str, &dir.size, dir.memory_size, literal("..")) == 0){ - keep_going = 0; - } - } - - // TODO(allen): feedback message - couldn't find build.bat -} - -CUSTOM_COMMAND_SIG(auto_tab_line_at_cursor){ - View_Summary view = app->get_active_view(app); - push_parameter(app, par_range_start, view.cursor.pos); - push_parameter(app, par_range_end, view.cursor.pos); - push_parameter(app, par_clear_blank_lines, 0); - exec_command(app, cmdid_auto_tab_range); -} - -CUSTOM_COMMAND_SIG(auto_tab_whole_file){ - Buffer_Summary buffer = app->get_active_buffer(app); - push_parameter(app, par_range_start, 0); - push_parameter(app, par_range_end, buffer.size); - exec_command(app, cmdid_auto_tab_range); -} - -CUSTOM_COMMAND_SIG(write_and_auto_tab){ - exec_command(app, cmdid_write_character); - exec_command(app, auto_tab_line_at_cursor); -} - -// NOTE(allen|a4) See 4coder_styles.h for a list of available style tags. -// There are style tags corresponding to every color in the theme editor. -CUSTOM_COMMAND_SIG(improve_theme){ - Theme_Color colors[] = { - {Stag_Bar, 0xFF0088}, - {Stag_Margin, 0x880088}, - {Stag_Margin_Hover, 0xAA0088}, - {Stag_Margin_Active, 0xDD0088}, - {Stag_Cursor, 0xFF0000}, - }; - - int count = ArrayCount(colors); - - app->set_theme_colors(app, colors, count); -} - -CUSTOM_COMMAND_SIG(ruin_theme){ - Theme_Color colors[] = { - {Stag_Bar, 0x888888}, - {Stag_Margin, 0x181818}, - {Stag_Margin_Hover, 0x252525}, - {Stag_Margin_Active, 0x323232}, - {Stag_Cursor, 0x00EE00}, - }; - - int count = ArrayCount(colors); - - app->set_theme_colors(app, colors, count); -} - -// NOTE(allen|a4): scroll rule information -// -// The parameters: -// target_x, target_y -// This is where the view would like to be for the purpose of -// following the cursor, doing mouse wheel work, etc. -// -// scroll_x, scroll_y -// These are pointers to where the scrolling actually is. If you bind -// the scroll rule it is you have to update these in some way to move -// the actual location of the scrolling. -// -// view_id -// This corresponds to which view is computing it's new scrolling position. -// This id DOES correspond to the views that View_Summary contains. -// This will always be between 1 and 16 (0 is a null id). -// See below for an example of having state that carries across scroll udpates. -// -// is_new_target -// If the target of the view is different from the last target in either x or y -// this is true, otherwise it is false. -// -// The return: -// Should be true if and only if scroll_x or scroll_y are changed. -// -// Don't try to use the app pointer in a scroll rule, you're asking for trouble. -// -// If you don't bind scroll_rule, nothing bad will happen, yo will get default -// 4coder scrolling behavior. -// - -struct Scroll_Velocity{ - float x, y; -}; - -Scroll_Velocity scroll_velocity_[16] = {0}; -Scroll_Velocity *scroll_velocity = scroll_velocity_ - 1; - -static int -smooth_camera_step(float target, float *current, float *vel, float S, float T){ - int result = 0; - float curr = *current; - float v = *vel; - if (curr != target){ - if (curr > target - .1f && curr < target + .1f){ - curr = target; - v = 1.f; - } - else{ - float L = curr + T*(target - curr); - - int sign = (target > curr) - (target < curr); - float V = curr + sign*v; - - if (sign > 0) curr = (LV)?(L):(V); - - if (curr == V){ - v *= S; - } - } - - *current = curr; - *vel = v; - result = 1; - } - return result; -} - -SCROLL_RULE_SIG(smooth_scroll_rule){ - Scroll_Velocity *velocity = scroll_velocity + view_id; - int result = 0; - if (velocity->x == 0.f){ - velocity->x = 1.f; - velocity->y = 1.f; - } - - if (smooth_camera_step(target_y, scroll_y, &velocity->y, 40.f, 1.f/4.f)){ - result = 1; - } - if (smooth_camera_step(target_x, scroll_x, &velocity->x, 40.f, 1.f/4.f)){ - result = 1; - } - - return(result); -} - -#if Custom_Current == Custom_HandmadeHero +#if Custom_Current == Custom_Default +# include "4coder_default_bindings.cpp" +#elif Custom_Current == Custom_HandmadeHero # include "power/4coder_handmade_hero.cpp" #endif extern "C" GET_BINDING_DATA(get_bindings){ Bind_Helper context_actual = begin_bind_helper(data, size); Bind_Helper *context = &context_actual; - -#if Custom_Current == Custom_HandmadeHero + +#if Custom_Current == Custom_Default + default_get_bindings(context); +#elif Custom_Current == Custom_HandmadeHero casey_get_bindings(context); -#else - - // NOTE(allen|a3.1): Hooks have no loyalties to maps. All hooks are global - // and once set they always apply, regardless of what map is active. - set_hook(context, hook_start, my_start); - set_hook(context, hook_open_file, my_file_settings); - //set_hook(context, hook_frame, my_frame); // Example of a frame hook, but disabled by default. - - set_scroll_rule(context, smooth_scroll_rule); - - 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, 'o', MDFR_ALT, open_in_other); - - bind(context, 'm', MDFR_ALT, build_search); - bind(context, ',', MDFR_ALT, switch_to_compilation); - bind(context, 'x', MDFR_ALT, execute_arbitrary_command); - bind(context, 'z', MDFR_ALT, execute_any_cli); - - // NOTE(allen): These callbacks may not actually be useful to you, but - // go look at them and see what they do. - bind(context, 'M', MDFR_ALT | MDFR_CTRL, open_my_files); - bind(context, 'M', MDFR_ALT, build_at_launch_location); - - bind(context, '`', MDFR_ALT, improve_theme); - bind(context, '~', MDFR_ALT, ruin_theme); - - end_map(context); - - - begin_map(context, my_code_map); - - // NOTE(allen|a3.1): 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|a3.1): Children can override parent's bindings. - bind(context, key_right, MDFR_CTRL, seek_alphanumeric_or_camel_right); - bind(context, key_left, MDFR_CTRL, seek_alphanumeric_or_camel_left); - - // NOTE(allen|a3.2): Specific keys can override vanilla keys, - // and write character writes whichever character corresponds - // to the key that triggered the command. - bind(context, '\n', MDFR_NONE, write_and_auto_tab); - bind(context, '}', MDFR_NONE, write_and_auto_tab); - bind(context, ')', MDFR_NONE, write_and_auto_tab); - bind(context, ']', MDFR_NONE, write_and_auto_tab); - bind(context, ';', MDFR_NONE, write_and_auto_tab); - bind(context, '#', MDFR_NONE, write_and_auto_tab); - - bind(context, '\t', MDFR_NONE, cmdid_word_complete); - bind(context, '\t', MDFR_CTRL, cmdid_auto_tab_range); - bind(context, '\t', MDFR_SHIFT, auto_tab_line_at_cursor); - - bind(context, '=', MDFR_CTRL, write_increment); - bind(context, '-', MDFR_CTRL, write_decrement); - bind(context, 't', MDFR_ALT, write_allen_todo); - bind(context, 'n', MDFR_ALT, write_allen_note); - bind(context, '[', MDFR_CTRL, open_long_braces); - bind(context, '{', MDFR_CTRL, open_long_braces_semicolon); - bind(context, '}', MDFR_CTRL, open_long_braces_break); - bind(context, '9', MDFR_CTRL, paren_wrap); - bind(context, 'i', MDFR_ALT, if0_off); - bind(context, '1', MDFR_ALT, open_file_in_quotes); - - end_map(context); - - - begin_map(context, mapid_file); - - // NOTE(allen|a3.4.4): Binding this essentially binds - // all key combos that would normally insert a character - // into a buffer. If the code for the key is not an enum - // value such as key_left or key_back then it is a vanilla key. - // It is possible to override this binding for individual keys. - bind_vanilla_keys(context, cmdid_write_character); - - bind(context, key_left, MDFR_NONE, cmdid_move_left); - bind(context, key_right, MDFR_NONE, cmdid_move_right); - bind(context, key_del, MDFR_NONE, cmdid_delete); - bind(context, key_back, MDFR_NONE, cmdid_backspace); - bind(context, key_up, MDFR_NONE, cmdid_move_up); - bind(context, key_down, MDFR_NONE, cmdid_move_down); - bind(context, key_end, MDFR_NONE, cmdid_seek_end_of_line); - bind(context, key_home, MDFR_NONE, cmdid_seek_beginning_of_line); - bind(context, key_page_up, MDFR_NONE, cmdid_page_up); - bind(context, key_page_down, MDFR_NONE, cmdid_page_down); - - bind(context, key_right, MDFR_CTRL, seek_whitespace_right); - bind(context, key_left, MDFR_CTRL, seek_whitespace_left); - bind(context, key_up, MDFR_CTRL, cmdid_seek_whitespace_up); - bind(context, key_down, MDFR_CTRL, cmdid_seek_whitespace_down); - - bind(context, key_up, MDFR_ALT, move_up_10); - bind(context, key_down, MDFR_ALT, move_down_10); - - bind(context, key_back, MDFR_CTRL, backspace_word); - bind(context, key_back, MDFR_ALT, snipe_token_or_word); - - 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, 'h', MDFR_CTRL, cmdid_history_backward); - bind(context, 'H', MDFR_CTRL, cmdid_history_forward); - bind(context, 'd', MDFR_CTRL, cmdid_delete_range); - 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_all_lines); - bind(context, '1', MDFR_CTRL, cmdid_eol_dosify); - bind(context, '!', MDFR_CTRL, cmdid_eol_nixify); - - bind(context, 'f', MDFR_CTRL, search); - bind(context, 'r', MDFR_CTRL, reverse_search); - bind(context, 'g', MDFR_CTRL, goto_line); - bind(context, 'q', MDFR_CTRL, query_replace); - bind(context, 'a', MDFR_CTRL, replace_in_range); - bind(context, 's', MDFR_ALT, rewrite_as_single_caps); - - 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); - - bind(context, '\n', MDFR_SHIFT, write_and_auto_tab); - bind(context, ' ', MDFR_SHIFT, cmdid_write_character); - - end_map(context); #endif - + end_bind_helper(context); - return context->write_total; } - +// BOTTOM diff --git a/4coder_custom.h b/4coder_custom.h index ac2a8bb8..7eeb88c0 100644 --- a/4coder_custom.h +++ b/4coder_custom.h @@ -231,6 +231,8 @@ enum Param_ID{ par_cli_path, par_cli_command, par_clear_blank_lines, + par_use_tabs, + // never below this par_type_count }; @@ -374,6 +376,7 @@ struct Application_Links; // Directly get user input #define GET_USER_INPUT_SIG(n) User_Input n(Application_Links *app, unsigned int get_type, unsigned int abort_type) +#define GET_COMMAND_INPUT_SIG(n) User_Input n(Application_Links *app) // Queries #define START_QUERY_BAR_SIG(n) int n(Application_Links *app, Query_Bar *bar, unsigned int flags) @@ -453,6 +456,7 @@ extern "C"{ // Directly get user input typedef GET_USER_INPUT_SIG(Get_User_Input_Function); + typedef GET_COMMAND_INPUT_SIG(Get_Command_Input_Function); // Queries typedef START_QUERY_BAR_SIG(Start_Query_Bar_Function); @@ -514,6 +518,7 @@ struct Application_Links{ // Directly get user input Get_User_Input_Function *get_user_input; + Get_Command_Input_Function *get_command_input; // Queries Start_Query_Bar_Function *start_query_bar; diff --git a/4coder_default.cpp b/4coder_default.cpp new file mode 100644 index 00000000..ab553a2c --- /dev/null +++ b/4coder_default.cpp @@ -0,0 +1,766 @@ + +#include "4coder_custom.h" + +#define FCPP_STRING_IMPLEMENTATION +#include "4coder_string.h" + +#define UseInterfacesThatArePhasingOut 0 +#include "4coder_helper.h" + +// NOTE(allen|a3.3): All of your custom ids should be enumerated +// as shown here, they may start at 0, and you can only have +// 2^24 of them so don't be wasteful! +enum My_Maps{ + my_code_map +}; + +static void +write_string(Application_Links *app, String string){ + Buffer_Summary buffer = app->get_active_buffer(app); + app->buffer_replace_range(app, &buffer, buffer.buffer_cursor_pos, buffer.buffer_cursor_pos, string.str, string.size); +} + +CUSTOM_COMMAND_SIG(write_increment){ + write_string(app, make_lit_string("++")); +} + +CUSTOM_COMMAND_SIG(write_decrement){ + write_string(app, make_lit_string("--")); +} + +static void +basic_seek(Application_Links *app, Command_ID seek_type, unsigned int flags){ + push_parameter(app, par_flags, flags); + exec_command(app, seek_type); +} + +#define SEEK_COMMAND(n, dir, flags)\ +CUSTOM_COMMAND_SIG(seek_##n##_##dir){\ + basic_seek(app, cmdid_seek_##dir, flags);\ +} + +SEEK_COMMAND(whitespace, right, BoundryWhitespace) +SEEK_COMMAND(whitespace, left, BoundryWhitespace) +SEEK_COMMAND(token, right, BoundryToken) +SEEK_COMMAND(token, left, BoundryToken) +SEEK_COMMAND(white_or_token, right, BoundryToken | BoundryWhitespace) +SEEK_COMMAND(white_or_token, left, BoundryToken | BoundryWhitespace) +SEEK_COMMAND(alphanumeric, right, BoundryAlphanumeric) +SEEK_COMMAND(alphanumeric, left, BoundryAlphanumeric) +SEEK_COMMAND(alphanumeric_or_camel, right, BoundryAlphanumeric | BoundryCamelCase) +SEEK_COMMAND(alphanumeric_or_camel, left, BoundryAlphanumeric | BoundryCamelCase) + +static void +long_braces(Application_Links *app, char *text, int size){ + View_Summary view; + Buffer_Summary buffer; + int pos; + + view = app->get_active_view(app); + buffer = app->get_buffer(app, view.buffer_id); + + pos = view.cursor.pos; + app->buffer_replace_range(app, &buffer, pos, pos, text, size); + app->view_set_cursor(app, &view, seek_pos(pos + 2), 1); + + push_parameter(app, par_range_start, pos); + push_parameter(app, par_range_end, pos + size); + push_parameter(app, par_clear_blank_lines, 0); + push_parameter(app, par_use_tabs, 1); + exec_command(app, cmdid_auto_tab_range); +} + +CUSTOM_COMMAND_SIG(open_long_braces){ + char text[] = "{\n\n}"; + int size = sizeof(text) - 1; + long_braces(app, text, size); +} + +CUSTOM_COMMAND_SIG(open_long_braces_semicolon){ + char text[] = "{\n\n};"; + int size = sizeof(text) - 1; + long_braces(app, text, size); +} + +CUSTOM_COMMAND_SIG(open_long_braces_break){ + char text[] = "{\n\n}break;"; + int size = sizeof(text) - 1; + long_braces(app, text, size); +} + +CUSTOM_COMMAND_SIG(paren_wrap){ + View_Summary view; + Buffer_Summary buffer; + + char text1[] = "("; + int size1 = sizeof(text1) - 1; + + char text2[] = ")"; + int size2 = sizeof(text2) - 1; + + Range range; + int pos; + + view = app->get_active_view(app); + buffer = app->get_active_buffer(app); + + range = get_range(&view); + pos = range.max; + app->buffer_replace_range(app, &buffer, pos, pos, text2, size2); + + pos = range.min; + app->buffer_replace_range(app, &buffer, pos, pos, text1, size1); +} + +CUSTOM_COMMAND_SIG(if0_off){ + View_Summary view; + Buffer_Summary buffer; + + char text1[] = "\n#if 0"; + int size1 = sizeof(text1) - 1; + + char text2[] = "#endif\n"; + int size2 = sizeof(text2) - 1; + + Range range; + int pos; + + view = app->get_active_view(app); + buffer = app->get_active_buffer(app); + + range = get_range(&view); + pos = range.min; + + app->buffer_replace_range(app, &buffer, pos, pos, text1, size1); + + push_parameter(app, par_range_start, pos); + push_parameter(app, par_range_end, pos); + exec_command(app, cmdid_auto_tab_range); + + app->refresh_view(app, &view); + range = get_range(&view); + pos = range.max; + + app->buffer_replace_range(app, &buffer, pos, pos, text2, size2); + + push_parameter(app, par_range_start, pos); + push_parameter(app, par_range_end, pos); + exec_command(app, cmdid_auto_tab_range); +} + +CUSTOM_COMMAND_SIG(backspace_word){ + View_Summary view; + Buffer_Summary buffer; + int pos2, pos1; + + view = app->get_active_view(app); + + pos2 = view.cursor.pos; + exec_command(app, seek_alphanumeric_left); + app->refresh_view(app, &view); + pos1 = view.cursor.pos; + + buffer = app->get_buffer(app, view.buffer_id); + app->buffer_replace_range(app, &buffer, pos1, pos2, 0, 0); +} + +CUSTOM_COMMAND_SIG(snipe_token_or_word){ + View_Summary view; + Buffer_Summary buffer; + int pos1, pos2; + + view = app->get_active_view(app); + + push_parameter(app, par_flags, BoundryToken | BoundryWhitespace); + exec_command(app, cmdid_seek_left); + app->refresh_view(app, &view); + pos1 = view.cursor.pos; + + push_parameter(app, par_flags, BoundryToken | BoundryWhitespace); + exec_command(app, cmdid_seek_right); + app->refresh_view(app, &view); + pos2 = view.cursor.pos; + + Range range = make_range(pos1, pos2); + buffer = app->get_buffer(app, view.buffer_id); + app->buffer_replace_range(app, &buffer, range.start, range.end, 0, 0); +} + +CUSTOM_COMMAND_SIG(open_file_in_quotes){ + View_Summary view; + Buffer_Summary buffer; + char short_file_name[128]; + int pos, start, end, size; + + view = app->get_active_view(app); + buffer = app->get_buffer(app, view.buffer_id); + pos = view.cursor.pos; + app->buffer_seek_delimiter(app, &buffer, pos, '"', 1, &end); + app->buffer_seek_delimiter(app, &buffer, pos, '"', 0, &start); + + ++start; + size = end - start; + + // NOTE(allen): This check is necessary because app->buffer_read_range + // requiers that the output buffer you provide is at least (end - start) bytes long. + if (size < sizeof(short_file_name)){ + char file_name_[256]; + String file_name = make_fixed_width_string(file_name_); + + app->buffer_read_range(app, &buffer, start, end, short_file_name); + + copy(&file_name, make_string(buffer.file_name, buffer.file_name_len)); + remove_last_folder(&file_name); + append(&file_name, make_string(short_file_name, size)); + + exec_command(app, cmdid_change_active_panel); + push_parameter(app, par_name, expand_str(file_name)); + exec_command(app, cmdid_interactive_open); + } +} + +CUSTOM_COMMAND_SIG(goto_line){ + int line_number; + Query_Bar bar; + char string_space[256]; + + bar.prompt = make_lit_string("Goto Line: "); + bar.string = make_fixed_width_string(string_space); + + if (query_user_number(app, &bar)){ + line_number = str_to_int(bar.string); + active_view_to_line(app, line_number); + } +} + +CUSTOM_COMMAND_SIG(search); +CUSTOM_COMMAND_SIG(reverse_search); + +static void +isearch(Application_Links *app, int start_reversed){ + View_Summary view; + Buffer_Summary buffer; + User_Input in; + Query_Bar bar; + + if (app->start_query_bar(app, &bar, 0) == 0) return; + + Range match; + int reverse = start_reversed; + int pos; + + view = app->get_active_view(app); + buffer = app->get_buffer(app, view.buffer_id); + + pos = view.cursor.pos; + match = make_range(pos, pos); + + char bar_string_space[256]; + bar.string = make_fixed_width_string(bar_string_space); + + String isearch = make_lit_string("I-Search: "); + String rsearch = make_lit_string("Reverse-I-Search: "); + + while (1){ + // NOTE(allen): Change the bar's prompt to match the current direction. + if (reverse) bar.prompt = rsearch; + else bar.prompt = isearch; + + in = app->get_user_input(app, EventOnAnyKey, EventOnEsc | EventOnButton); + if (in.abort) break; + + // NOTE(allen): If we're getting mouse events here it's a 4coder bug, because we + // only asked to intercept key events. + assert(in.type == UserInputKey); + + int made_change = 0; + if (in.key.keycode == '\n' || in.key.keycode == '\t'){ + break; + } + else if (in.key.character && key_is_unmodified(&in.key)){ + append(&bar.string, in.key.character); + made_change = 1; + } + else if (in.key.keycode == key_back){ + if (bar.string.size > 0){ + --bar.string.size; + made_change = 1; + } + } + + int step_forward = 0; + int step_backward = 0; + + if (CommandEqual(in.command, search) || + in.key.keycode == key_page_down || in.key.keycode == key_down) step_forward = 1; + if (CommandEqual(in.command, reverse_search) || + in.key.keycode == key_page_up || in.key.keycode == key_up) step_backward = 1; + + int start_pos = pos; + if (step_forward && reverse){ + start_pos = match.start + 1; + pos = start_pos; + reverse = 0; + step_forward = 0; + } + if (step_backward && !reverse){ + start_pos = match.start - 1; + pos = start_pos; + reverse = 1; + step_backward = 0; + } + + if (in.key.keycode != key_back){ + int new_pos; + if (reverse){ + app->buffer_seek_string(app, &buffer, start_pos - 1, bar.string.str, bar.string.size, 0, &new_pos); + if (new_pos >= 0){ + if (step_backward){ + pos = new_pos; + start_pos = new_pos; + app->buffer_seek_string(app, &buffer, start_pos - 1, bar.string.str, bar.string.size, 0, &new_pos); + if (new_pos < 0) new_pos = start_pos; + } + match.start = new_pos; + match.end = match.start + bar.string.size; + } + } + else{ + app->buffer_seek_string(app, &buffer, start_pos + 1, bar.string.str, bar.string.size, 1, &new_pos); + if (new_pos < buffer.size){ + if (step_forward){ + pos = new_pos; + start_pos = new_pos; + app->buffer_seek_string(app, &buffer, start_pos + 1, bar.string.str, bar.string.size, 1, &new_pos); + if (new_pos >= buffer.size) new_pos = start_pos; + } + match.start = new_pos; + match.end = match.start + bar.string.size; + } + } + } + else{ + match.end = match.start + bar.string.size; + } + + app->view_set_highlight(app, &view, match.start, match.end, 1); + } + app->view_set_highlight(app, &view, 0, 0, 0); + if (in.abort) return; + + app->view_set_cursor(app, &view, seek_pos(match.min), 1); +} + +CUSTOM_COMMAND_SIG(search){ + isearch(app, 0); +} + +CUSTOM_COMMAND_SIG(reverse_search){ + isearch(app, 1); +} + +CUSTOM_COMMAND_SIG(replace_in_range){ + Query_Bar replace; + char replace_space[1024]; + replace.prompt = make_lit_string("Replace: "); + replace.string = make_fixed_width_string(replace_space); + + Query_Bar with; + char with_space[1024]; + with.prompt = make_lit_string("With: "); + with.string = make_fixed_width_string(with_space); + + if (!query_user_string(app, &replace)) return; + if (replace.string.size == 0) return; + + if (!query_user_string(app, &with)) return; + + String r, w; + r = replace.string; + w = with.string; + + Buffer_Summary buffer; + View_Summary view; + + view = app->get_active_view(app); + buffer = app->get_buffer(app, view.buffer_id); + + Range range = get_range(&view); + + int pos, new_pos; + pos = range.min; + app->buffer_seek_string(app, &buffer, pos, r.str, r.size, 1, &new_pos); + + while (new_pos + r.size < range.end){ + app->buffer_replace_range(app, &buffer, new_pos, new_pos + r.size, w.str, w.size); + range = get_range(&view); + pos = new_pos + w.size; + app->buffer_seek_string(app, &buffer, pos, r.str, r.size, 1, &new_pos); + } +} + +CUSTOM_COMMAND_SIG(query_replace){ + Query_Bar replace; + char replace_space[1024]; + replace.prompt = make_lit_string("Replace: "); + replace.string = make_fixed_width_string(replace_space); + + Query_Bar with; + char with_space[1024]; + with.prompt = make_lit_string("With: "); + with.string = make_fixed_width_string(with_space); + + if (!query_user_string(app, &replace)) return; + if (replace.string.size == 0) return; + + if (!query_user_string(app, &with)) return; + + String r, w; + r = replace.string; + w = with.string; + + Query_Bar bar; + Buffer_Summary buffer; + View_Summary view; + int pos, new_pos; + + bar.prompt = make_lit_string("Replace? (y)es, (n)ext, (esc)\n"); + bar.string = empty_string(); + + app->start_query_bar(app, &bar, 0); + + view = app->get_active_view(app); + buffer = app->get_buffer(app, view.buffer_id); + + pos = view.cursor.pos; + app->buffer_seek_string(app, &buffer, pos, r.str, r.size, 1, &new_pos); + + User_Input in = {0}; + while (new_pos < buffer.size){ + Range match = make_range(new_pos, new_pos + r.size); + app->view_set_highlight(app, &view, match.min, match.max, 1); + + in = app->get_user_input(app, EventOnAnyKey, EventOnButton); + if (in.abort || in.key.keycode == key_esc || !key_is_unmodified(&in.key)) break; + + if (in.key.character == 'y' || in.key.character == 'Y' || in.key.character == '\n' || in.key.character == '\t'){ + app->buffer_replace_range(app, &buffer, match.min, match.max, w.str, w.size); + pos = match.start + w.size; + } + else{ + pos = match.max; + } + + app->buffer_seek_string(app, &buffer, pos, r.str, r.size, 1, &new_pos); + } + + app->view_set_highlight(app, &view, 0, 0, 0); + if (in.abort) return; + + app->view_set_cursor(app, &view, seek_pos(pos), 1); +} + +CUSTOM_COMMAND_SIG(close_all_code){ + String extension; + Buffer_Summary buffer; + + for (buffer = app->get_buffer_first(app); + buffer.exists; + app->get_buffer_next(app, &buffer)){ + + extension = file_extension(make_string(buffer.file_name, buffer.file_name_len)); + if (match(extension, make_lit_string("cpp")) || + match(extension, make_lit_string("hpp")) || + match(extension, make_lit_string("c")) || + match(extension, make_lit_string("h"))){ + // + push_parameter(app, par_buffer_id, buffer.buffer_id); + exec_command(app, cmdid_kill_buffer); + } + } +} + +CUSTOM_COMMAND_SIG(open_all_code){ + // NOTE(allen|a3.4.4): This method of getting the hot directory works + // because this custom.cpp gives no special meaning to app->memory + // and doesn't set up a persistent allocation system within app->memory. + // push_directory isn't a very good option since it's tied to the parameter + // stack, so I am phasing that idea out now. + String dir = make_string(app->memory, 0, app->memory_size); + dir.size = app->directory_get_hot(app, dir.str, dir.memory_size); + int dir_size = dir.size; + + // NOTE(allen|a3.4.4): Here we get the list of files in this directory. + // Notice that we free_file_list at the end. + File_List list = app->get_file_list(app, dir.str, dir.size); + + for (int i = 0; i < list.count; ++i){ + File_Info *info = list.infos + i; + if (!info->folder){ + String extension = file_extension(info->filename); + if (match(extension, make_lit_string("cpp")) || + match(extension, make_lit_string("hpp")) || + match(extension, make_lit_string("c")) || + match(extension, make_lit_string("h"))){ + // NOTE(allen): There's no way in the 4coder API to use relative + // paths at the moment, so everything should be full paths. Which is + // managable. Here simply set the dir string size back to where it + // was originally, so that new appends overwrite old ones. + dir.size = dir_size; + append(&dir, info->filename); + push_parameter(app, par_name, dir.str, dir.size); + //push_parameter(app, par_do_in_background, 1); + exec_command(app, cmdid_interactive_open); + } + } + } + + app->free_file_list(app, list); +} + +CUSTOM_COMMAND_SIG(execute_any_cli){ + Query_Bar bar_out, bar_cmd; + String hot_directory; + char space[1024], more_space[1024], even_more_space[1024]; + + bar_out.prompt = make_lit_string("Output Buffer: "); + bar_out.string = make_fixed_width_string(space); + if (!query_user_string(app, &bar_out)) return; + + bar_cmd.prompt = make_lit_string("Command: "); + bar_cmd.string = make_fixed_width_string(more_space); + if (!query_user_string(app, &bar_cmd)) return; + + hot_directory = make_fixed_width_string(even_more_space); + hot_directory.size = app->directory_get_hot(app, hot_directory.str, hot_directory.memory_size); + + push_parameter(app, par_flags, CLI_OverlapWithConflict); + push_parameter(app, par_name, bar_out.string.str, bar_out.string.size); + push_parameter(app, par_cli_path, hot_directory.str, hot_directory.size); + push_parameter(app, par_cli_command, bar_cmd.string.str, bar_cmd.string.size); + exec_command(app, cmdid_command_line); +} + +CUSTOM_COMMAND_SIG(execute_arbitrary_command){ + // NOTE(allen): This isn't a super powerful version of this command, I will expand + // upon it so that it has all the cmdid_* commands by default. However, with this + // as an example you have everything you need to make it work already. You could + // even use app->memory to create a hash table in the start hook. + Query_Bar bar; + char space[1024]; + bar.prompt = make_lit_string("Command: "); + bar.string = make_fixed_width_string(space); + + if (!query_user_string(app, &bar)) return; + + // NOTE(allen): Here I chose to end this query bar because when I call another + // command it might ALSO have query bars and I don't want this one hanging + // around at that point. Since the bar exists on my stack the result of the query + // is still available in bar.string though. + app->end_query_bar(app, &bar, 0); + + if (match(bar.string, make_lit_string("open all code"))){ + exec_command(app, open_all_code); + } + else if(match(bar.string, make_lit_string("close all code"))){ + exec_command(app, close_all_code); + } + else if (match(bar.string, make_lit_string("open menu"))){ + exec_command(app, cmdid_open_menu); + } + else if (match(bar.string, make_lit_string("dos lines"))){ + exec_command(app, cmdid_eol_dosify); + } + else if (match(bar.string, make_lit_string("nix lines"))){ + exec_command(app, cmdid_eol_nixify); + } + else{ + // TODO(allen): feedback message + } +} + +CUSTOM_COMMAND_SIG(open_in_other){ + exec_command(app, cmdid_change_active_panel); + exec_command(app, cmdid_interactive_open); +} + +CUSTOM_COMMAND_SIG(build_search){ + // NOTE(allen|a3.3): An example of traversing the filesystem through parent + // directories looking for a file, in this case a batch file to execute. + // + // + // Step 1: Grab all of the user memory (or, you know, less if you've got better + // thing to do with some of it). Make a string and store the hot directory in it. + // + // Step 2: app->file_exists queries the file system to see if "/build.bat" exists. + // If it does exist several parameters are pushed and cmdid_command_line is executed: + // - par_flags: flags for specifiying behaviors + // CLI_OverlapWithConflict - (on by default) if another CLI is still using the output buffer + // that process is detached from the buffer and this process executes outputing to the buffer + // CLI_AlwaysBindToView - if set, the current view always switches to the output buffer + // even if the output buffer is open in another view + // + // - par_name: the name of the buffer to fill with the output from the process + // - par_buffer_id: the buffer_id of the buffer to to fill with output + // If both are set buffer_id is used and the name is ignored. + // If neither is set the command runs without storing output anywhere. + // + // - par_cli_path: sets the path from which the command is executed + // If this parameter is unset the command runs from the hot directory. + // + // - par_cli_command: sets the actual command to be executed, this can be almost any + // command that you could execute through a command line interface. + // If this parameter is unset the command get's it's command from the range between + // the mark and cursor. + // + // Step 3: If the batch file did not exist change the dir string to the parent directory using + // app->directory_cd. The cd function can also be used to navigate to subdirectories. + // It returns true if it can actually move in the specified direction, and false otherwise. + // + // This doesn't actually change the hot directory of 4coder, it's only effect is to + // modify the string you passed in to reflect the change in directory if that change was possible. + + int keep_going = 1; + int old_size; + String dir = make_string(app->memory, 0, app->memory_size); + dir.size = app->directory_get_hot(app, dir.str, dir.memory_size); + + while (keep_going){ + old_size = dir.size; + append(&dir, "build.bat"); + + if (app->file_exists(app, dir.str, dir.size)){ + dir.size = old_size; + + push_parameter(app, par_flags, CLI_OverlapWithConflict); + push_parameter(app, par_name, literal("*compilation*")); + push_parameter(app, par_cli_path, dir.str, dir.size); + + if (append(&dir, "build")){ + push_parameter(app, par_cli_command, dir.str, dir.size); + exec_command(app, cmdid_command_line); + } + else{ + app->clear_parameters(app); + } + + return; + } + dir.size = old_size; + + if (app->directory_cd(app, dir.str, &dir.size, dir.memory_size, literal("..")) == 0){ + keep_going = 0; + } + } + + // TODO(allen): feedback message - couldn't find build.bat +} + +CUSTOM_COMMAND_SIG(auto_tab_line_at_cursor){ + View_Summary view = app->get_active_view(app); + push_parameter(app, par_range_start, view.cursor.pos); + push_parameter(app, par_range_end, view.cursor.pos); + push_parameter(app, par_clear_blank_lines, 0); + exec_command(app, cmdid_auto_tab_range); +} + +CUSTOM_COMMAND_SIG(auto_tab_whole_file){ + Buffer_Summary buffer = app->get_active_buffer(app); + push_parameter(app, par_range_start, 0); + push_parameter(app, par_range_end, buffer.size); + exec_command(app, cmdid_auto_tab_range); +} + +CUSTOM_COMMAND_SIG(write_and_auto_tab){ + exec_command(app, cmdid_write_character); + exec_command(app, auto_tab_line_at_cursor); +} + +// NOTE(allen|a4): scroll rule information +// +// The parameters: +// target_x, target_y +// This is where the view would like to be for the purpose of +// following the cursor, doing mouse wheel work, etc. +// +// scroll_x, scroll_y +// These are pointers to where the scrolling actually is. If you bind +// the scroll rule it is you have to update these in some way to move +// the actual location of the scrolling. +// +// view_id +// This corresponds to which view is computing it's new scrolling position. +// This id DOES correspond to the views that View_Summary contains. +// This will always be between 1 and 16 (0 is a null id). +// See below for an example of having state that carries across scroll udpates. +// +// is_new_target +// If the target of the view is different from the last target in either x or y +// this is true, otherwise it is false. +// +// The return: +// Should be true if and only if scroll_x or scroll_y are changed. +// +// Don't try to use the app pointer in a scroll rule, you're asking for trouble. +// +// If you don't bind scroll_rule, nothing bad will happen, yo will get default +// 4coder scrolling behavior. +// + +struct Scroll_Velocity{ + float x, y; +}; + +Scroll_Velocity scroll_velocity_[16] = {0}; +Scroll_Velocity *scroll_velocity = scroll_velocity_ - 1; + +static int +smooth_camera_step(float target, float *current, float *vel, float S, float T){ + int result = 0; + float curr = *current; + float v = *vel; + if (curr != target){ + if (curr > target - .1f && curr < target + .1f){ + curr = target; + v = 1.f; + } + else{ + float L = curr + T*(target - curr); + + int sign = (target > curr) - (target < curr); + float V = curr + sign*v; + + if (sign > 0) curr = (LV)?(L):(V); + + if (curr == V){ + v *= S; + } + } + + *current = curr; + *vel = v; + result = 1; + } + return result; +} + +SCROLL_RULE_SIG(smooth_scroll_rule){ + Scroll_Velocity *velocity = scroll_velocity + view_id; + int result = 0; + if (velocity->x == 0.f){ + velocity->x = 1.f; + velocity->y = 1.f; + } + + if (smooth_camera_step(target_y, scroll_y, &velocity->y, 40.f, 1.f/4.f)){ + result = 1; + } + if (smooth_camera_step(target_x, scroll_x, &velocity->x, 40.f, 1.f/4.f)){ + result = 1; + } + + return(result); +} + + diff --git a/4coder_default_bindings.cpp b/4coder_default_bindings.cpp new file mode 100644 index 00000000..e0594575 --- /dev/null +++ b/4coder_default_bindings.cpp @@ -0,0 +1,424 @@ + +#include "4coder_default.cpp" + +unsigned char blink_t = 0; + +HOOK_SIG(my_start){ + exec_command(app, cmdid_open_panel_vsplit); + exec_command(app, cmdid_change_active_panel); + + app->change_theme(app, literal("4coder")); + app->change_font(app, literal("liberation sans")); + + // Theme options: + // "4coder" + // "Handmade Hero" + // "Twilight" + // "Woverine" + // "stb" + + // Font options: + // "liberation sans" + // "liberation mono" + // "hack" + // "cutive mono" + // "inconsolata" + + // no meaning for return + return(0); +} + +HOOK_SIG(my_file_settings){ + // NOTE(allen|a4): In hooks that want parameters, such as this file + // created hook. The file created hook is guaranteed to have only + // and exactly one buffer parameter. In normal command callbacks + // there are no parameter buffers. + Buffer_Summary buffer = app->get_parameter_buffer(app, 0); + assert(buffer.exists); + + int treat_as_code = 0; + + if (buffer.file_name && buffer.size < (16 << 20)){ + String ext = file_extension(make_string(buffer.file_name, buffer.file_name_len)); + if (match(ext, make_lit_string("cpp"))) treat_as_code = 1; + else if (match(ext, make_lit_string("h"))) treat_as_code = 1; + else if (match(ext, make_lit_string("c"))) treat_as_code = 1; + else if (match(ext, make_lit_string("hpp"))) treat_as_code = 1; + } + + push_parameter(app, par_lex_as_cpp_file, treat_as_code); + push_parameter(app, par_wrap_lines, !treat_as_code); + push_parameter(app, par_key_mapid, (treat_as_code)?((int)my_code_map):((int)mapid_file)); + exec_command(app, cmdid_set_settings); + + // no meaning for return + return(0); +} + +HOOK_SIG(my_frame){ + // NOTE(allen|a4): Please use me sparingly! This get's called roughly once every *33 ms* if everything is going well. + // But if you start doing a lot in here, there's nothing 4codes does to stop you from making it a lot slower. + + int result = 0; + Theme_Color theme_color_1[] = { + {Stag_Cursor, 0x00FF00}, + {Stag_At_Cursor, 0x000000} + }; + + Theme_Color theme_color_2[2] = { + {Stag_Cursor, 0x000000}, + {Stag_At_Cursor, 0xFFFFFF} + }; + + Theme_Color *theme_color; + + ++blink_t; + + if (blink_t == 20 || blink_t == 40){ + if (blink_t == 20){ + theme_color = theme_color_2; + } + else{ + theme_color = theme_color_1; + blink_t = 0; + } + + result = 1; + app->set_theme_colors(app, theme_color, 2); + } + + // return non-zero if you do anything that might change the screen! + // 4coder won't redraw unless you tell it you've changed something important. + // If you redraw *all* the time it's going to slow 4coder down and increase power consumption. + return(result); +} + +CUSTOM_COMMAND_SIG(write_allen_todo){ + write_string(app, make_lit_string("// TODO(allen): ")); +} + +CUSTOM_COMMAND_SIG(write_allen_note){ + write_string(app, make_lit_string("// NOTE(allen): ")); +} + +CUSTOM_COMMAND_SIG(write_capital){ + User_Input command_in = app->get_command_input(app); + char c = command_in.key.character_no_caps_lock; + if (c != 0){ + c = char_to_upper(c); + write_string(app, make_string(&c, 1)); + } +} + +CUSTOM_COMMAND_SIG(switch_to_compilation){ + View_Summary view; + Buffer_Summary buffer; + + char name[] = "*compilation*"; + int name_size = sizeof(name)-1; + + view = app->get_active_view(app); + buffer = app->get_buffer_by_name(app, name, name_size); + + app->view_set_buffer(app, &view, buffer.buffer_id); +} + +CUSTOM_COMMAND_SIG(move_up_10){ + View_Summary view; + float x, y; + + view = app->get_active_view(app); + x = view.preferred_x; + + if (view.unwrapped_lines){ + y = view.cursor.unwrapped_y; + } + else{ + y = view.cursor.wrapped_y; + } + + y -= 10*view.line_height; + + app->view_set_cursor(app, &view, seek_xy(x, y, 0, view.unwrapped_lines), 0); +} + +CUSTOM_COMMAND_SIG(move_down_10){ + View_Summary view; + float x, y; + + view = app->get_active_view(app); + x = view.preferred_x; + + if (view.unwrapped_lines){ + y = view.cursor.wrapped_y; + } + else{ + y = view.cursor.wrapped_y; + } + + y += 10*view.line_height; + + app->view_set_cursor(app, &view, seek_xy(x, y, 0, view.unwrapped_lines), 0); +} + +CUSTOM_COMMAND_SIG(rewrite_as_single_caps){ + View_Summary view; + Buffer_Summary buffer; + Range range; + String string; + int is_first, i; + + exec_command(app, seek_token_left); + view = app->get_active_view(app); + range.min = view.cursor.pos; + + exec_command(app, seek_token_right); + app->refresh_view(app, &view); + range.max = view.cursor.pos; + + string.str = (char*)app->memory; + string.size = range.max - range.min; + assert(string.size < app->memory_size); + + buffer = app->get_buffer(app, view.buffer_id); + app->buffer_read_range(app, &buffer, range.min, range.max, string.str); + + is_first = 1; + for (i = 0; i < string.size; ++i){ + if (char_is_alpha_true(string.str[i])){ + if (is_first) is_first = 0; + else string.str[i] = char_to_lower(string.str[i]); + } + else{ + is_first = 1; + } + } + + app->buffer_replace_range(app, &buffer, range.min, range.max, string.str, string.size); +} + +CUSTOM_COMMAND_SIG(open_my_files){ + // NOTE(allen|a3.1): EXAMPLE probably not useful in practice. + // + // The command cmdid_interactive_open can now open + // a file specified on the parameter stack. If the file does not exist + // cmdid_interactive_open behaves as usual. If par_do_in_background + // is set to true the command is prevented from changing the view under + // any circumstance. + push_parameter(app, par_name, literal("w:/4ed/data/test/basic.cpp")); + exec_command(app, cmdid_interactive_open); + + exec_command(app, 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|a3.1): null terminators are not needed for strings. + push_parameter(app, par_name, my_file, my_file_len); + exec_command(app, cmdid_interactive_open); + + exec_command(app, cmdid_change_active_panel); +} + +CUSTOM_COMMAND_SIG(build_at_launch_location){ + // NOTE(allen|a3.3): EXAMPLE probably not all that useful in practice. + // + // An example of calling build by setting all + // parameters directly. This only works if build.bat can be called + // from the directory the application is launched at. + push_parameter(app, par_flags, CLI_OverlapWithConflict); + push_parameter(app, par_name, literal("*compilation*")); + push_parameter(app, par_cli_path, literal(".")); + push_parameter(app, par_cli_command, literal("build")); + exec_command(app, cmdid_command_line); +} + +// NOTE(allen|a4) See 4coder_styles.h for a list of available style tags. +// There are style tags corresponding to every color in the theme editor. +CUSTOM_COMMAND_SIG(improve_theme){ + Theme_Color colors[] = { + {Stag_Bar, 0xFF0088}, + {Stag_Margin, 0x880088}, + {Stag_Margin_Hover, 0xAA0088}, + {Stag_Margin_Active, 0xDD0088}, + {Stag_Cursor, 0xFF0000}, + }; + + int count = ArrayCount(colors); + + app->set_theme_colors(app, colors, count); +} + +CUSTOM_COMMAND_SIG(ruin_theme){ + Theme_Color colors[] = { + {Stag_Bar, 0x888888}, + {Stag_Margin, 0x181818}, + {Stag_Margin_Hover, 0x252525}, + {Stag_Margin_Active, 0x323232}, + {Stag_Cursor, 0x00EE00}, + }; + + int count = ArrayCount(colors); + + app->set_theme_colors(app, colors, count); +} + +void default_get_bindings(Bind_Helper *context){ + // NOTE(allen|a3.1): Hooks have no loyalties to maps. All hooks are global + // and once set they always apply, regardless of what map is active. + set_hook(context, hook_start, my_start); + set_hook(context, hook_open_file, my_file_settings); + //set_hook(context, hook_frame, my_frame); // Example of a frame hook, but disabled by default. + + set_scroll_rule(context, smooth_scroll_rule); + + 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, 'o', MDFR_ALT, open_in_other); + + bind(context, 'm', MDFR_ALT, build_search); + bind(context, ',', MDFR_ALT, switch_to_compilation); + bind(context, 'x', MDFR_ALT, execute_arbitrary_command); + bind(context, 'z', MDFR_ALT, execute_any_cli); + + // NOTE(allen): These callbacks may not actually be useful to you, but + // go look at them and see what they do. + bind(context, 'M', MDFR_ALT | MDFR_CTRL, open_my_files); + bind(context, 'M', MDFR_ALT, build_at_launch_location); + + bind(context, '`', MDFR_ALT, improve_theme); + bind(context, '~', MDFR_ALT, ruin_theme); + + end_map(context); + + + begin_map(context, my_code_map); + + // NOTE(allen|a3.1): 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|a3.1): Children can override parent's bindings. + bind(context, key_right, MDFR_CTRL, seek_alphanumeric_or_camel_right); + bind(context, key_left, MDFR_CTRL, seek_alphanumeric_or_camel_left); + + // NOTE(allen|a3.2): Specific keys can override vanilla keys, + // and write character writes whichever character corresponds + // to the key that triggered the command. + bind(context, '\n', MDFR_NONE, write_and_auto_tab); + bind(context, '}', MDFR_NONE, write_and_auto_tab); + bind(context, ')', MDFR_NONE, write_and_auto_tab); + bind(context, ']', MDFR_NONE, write_and_auto_tab); + bind(context, ';', MDFR_NONE, write_and_auto_tab); + bind(context, '#', MDFR_NONE, write_and_auto_tab); + + bind(context, '\t', MDFR_NONE, cmdid_word_complete); + bind(context, '\t', MDFR_CTRL, cmdid_auto_tab_range); + bind(context, '\t', MDFR_SHIFT, auto_tab_line_at_cursor); + + bind(context, '=', MDFR_CTRL, write_increment); + bind(context, '-', MDFR_CTRL, write_decrement); + bind(context, 't', MDFR_ALT, write_allen_todo); + bind(context, 'n', MDFR_ALT, write_allen_note); + bind(context, '[', MDFR_CTRL, open_long_braces); + bind(context, '{', MDFR_CTRL, open_long_braces_semicolon); + bind(context, '}', MDFR_CTRL, open_long_braces_break); + bind(context, '9', MDFR_CTRL, paren_wrap); + bind(context, 'i', MDFR_ALT, if0_off); + bind(context, '1', MDFR_ALT, open_file_in_quotes); + + end_map(context); + + + begin_map(context, mapid_file); + + // NOTE(allen|a3.4.4): Binding this essentially binds + // all key combos that would normally insert a character + // into a buffer. If the code for the key is not an enum + // value such as key_left or key_back then it is a vanilla key. + // It is possible to override this binding for individual keys. + bind_vanilla_keys(context, cmdid_write_character); + + bind(context, key_left, MDFR_NONE, cmdid_move_left); + bind(context, key_right, MDFR_NONE, cmdid_move_right); + bind(context, key_del, MDFR_NONE, cmdid_delete); + bind(context, key_back, MDFR_NONE, cmdid_backspace); + bind(context, key_up, MDFR_NONE, cmdid_move_up); + bind(context, key_down, MDFR_NONE, cmdid_move_down); + bind(context, key_end, MDFR_NONE, cmdid_seek_end_of_line); + bind(context, key_home, MDFR_NONE, cmdid_seek_beginning_of_line); + bind(context, key_page_up, MDFR_NONE, cmdid_page_up); + bind(context, key_page_down, MDFR_NONE, cmdid_page_down); + + bind(context, key_right, MDFR_CTRL, seek_whitespace_right); + bind(context, key_left, MDFR_CTRL, seek_whitespace_left); + bind(context, key_up, MDFR_CTRL, cmdid_seek_whitespace_up); + bind(context, key_down, MDFR_CTRL, cmdid_seek_whitespace_down); + + bind(context, key_up, MDFR_ALT, move_up_10); + bind(context, key_down, MDFR_ALT, move_down_10); + + bind(context, key_back, MDFR_CTRL, backspace_word); + bind(context, key_back, MDFR_ALT, snipe_token_or_word); + + 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, 'h', MDFR_CTRL, cmdid_history_backward); + bind(context, 'H', MDFR_CTRL, cmdid_history_forward); + bind(context, 'd', MDFR_CTRL, cmdid_delete_range); + 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_all_lines); + bind(context, '1', MDFR_CTRL, cmdid_eol_dosify); + bind(context, '!', MDFR_CTRL, cmdid_eol_nixify); + + bind(context, 'f', MDFR_CTRL, search); + bind(context, 'r', MDFR_CTRL, reverse_search); + bind(context, 'g', MDFR_CTRL, goto_line); + bind(context, 'q', MDFR_CTRL, query_replace); + bind(context, 'a', MDFR_CTRL, replace_in_range); + bind(context, 's', MDFR_ALT, rewrite_as_single_caps); + + 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); + + bind(context, '\n', MDFR_SHIFT, write_and_auto_tab); + bind(context, ' ', MDFR_SHIFT, cmdid_write_character); + + bind(context, 'q', MDFR_ALT | MDFR_CTRL, write_capital); + bind(context, 'w', MDFR_ALT | MDFR_CTRL, write_capital); + bind(context, 'e', MDFR_ALT | MDFR_CTRL, write_capital); + + + end_map(context); +} diff --git a/4coder_string.h b/4coder_string.h index 774c373c..669380e3 100644 --- a/4coder_string.h +++ b/4coder_string.h @@ -71,6 +71,10 @@ inline String make_string(void *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))) +#ifndef literal +#define literal(s) (s), (sizeof(s)-1) +#endif + #define expand_str(s) ((s).str), ((s).size) inline String make_string_slowly(void *s); diff --git a/4coder_version.h b/4coder_version.h index 871cab4f..78045aae 100644 --- a/4coder_version.h +++ b/4coder_version.h @@ -1,6 +1,6 @@ #define MAJOR 4 #define MINOR 0 -#define PATCH 1 +#define PATCH 2 #define VN__(a,b,c) #a"."#b"."#c #define VN_(a,b,c) VN__(a,b,c) diff --git a/4ed.cpp b/4ed.cpp index aadf0fea..97ad16ce 100644 --- a/4ed.cpp +++ b/4ed.cpp @@ -1024,7 +1024,7 @@ COMMAND_DECL(save){ else{ file = working_set_get_active_file(&models->working_set, buffer_id); - if (!file->state.is_dummy && file_is_ready(file)){ + if (!file->state.is_dummy && file_is_ready(file) && buffer_needs_save(file)){ delayed_save(delay, name, file); } else{ @@ -1231,6 +1231,7 @@ COMMAND_DECL(auto_tab_range){ int r_start = 0, r_end = 0; int start_set = 0, end_set = 0; int clear_blank_lines = 1; + int use_tabs = 0; // TODO(allen): deduplicate Command_Parameter *end = param_stack_end(&command->part); @@ -1251,6 +1252,10 @@ COMMAND_DECL(auto_tab_range){ case par_clear_blank_lines: clear_blank_lines = dynamic_to_bool(¶m->param.value); break; + + case par_use_tabs: + use_tabs = dynamic_to_bool(¶m->param.value); + break; } } @@ -1258,7 +1263,7 @@ COMMAND_DECL(auto_tab_range){ Range range = make_range(view->cursor.pos, view->mark); if (start_set) range.start = r_start; if (end_set) range.end = r_end; - view_auto_tab_tokens(system, models, view, range.start, range.end, clear_blank_lines); + view_auto_tab_tokens(system, models, view, range.start, range.end, clear_blank_lines, use_tabs); } } @@ -1728,6 +1733,7 @@ COMMAND_DECL(command_line){ CLI_Process *procs = vars->cli_processes.procs, *proc = 0; Editing_File *file = 0; b32 bind_to_new_view = !do_in_background; + General_Memory *general = &models->mem.general; if (vars->cli_processes.count < vars->cli_processes.max){ if (buffer_id){ @@ -1746,10 +1752,10 @@ COMMAND_DECL(command_line){ } } else{ - file = working_set_alloc_always(working_set, &models->mem.general); + file = working_set_alloc_always(working_set, general); file_create_read_only(system, models, file, buffer_name); - working_set_add(system, working_set, file); + working_set_add(system, working_set, file, general); if (file == 0){ // TODO(allen): feedback message - no available file @@ -2413,7 +2419,19 @@ extern "C"{ return(result); } - + + GET_COMMAND_INPUT_SIG(external_get_command_input){ + Command_Data *cmd = (Command_Data*)app->cmd_context; + User_Input result; + + result.type = UserInputKey; + result.abort = 0; + result.key = cmd->key; + result.command = 0; + + return(result); + } + START_QUERY_BAR_SIG(external_start_query_bar){ Command_Data *cmd = (Command_Data*)app->cmd_context; Query_Slot *slot = 0; @@ -2541,6 +2559,7 @@ app_links_init(System_Functions *system, void *data, int size){ app_links.view_set_buffer = external_view_set_buffer; app_links.get_user_input = external_get_user_input; + app_links.get_command_input = external_get_command_input; app_links.start_query_bar = external_start_query_bar; app_links.end_query_bar = external_end_query_bar; @@ -3243,18 +3262,19 @@ App_Init_Sig(app_init){ if (!did_top) setup_top_commands(&models->map_top, &models->mem.part, global); if (!did_file) setup_file_commands(&models->map_file, &models->mem.part, global); -#if !defined(FRED_SUPER) +#ifndef FRED_SUPER models->hooks[hook_start] = 0; #endif setup_ui_commands(&models->map_ui, &models->mem.part, global); + } + // NOTE(allen): font setup + { models->font_set = &target->font_set; font_set_init(models->font_set, partition, 16, 5); - } - - { + struct Font_Setup{ char *c_file_name; i32 file_name_len; @@ -3264,28 +3284,31 @@ App_Init_Sig(app_init){ }; #define LitStr(n) n, sizeof(n)-1 - + + int font_size = 16; + + if (font_size < 8) font_size = 8; + Font_Setup font_setup[] = { {LitStr("LiberationSans-Regular.ttf"), LitStr("liberation sans"), - 16}, + font_size}, {LitStr("liberation-mono.ttf"), LitStr("liberation mono"), - 16}, + font_size}, {LitStr("Hack-Regular.ttf"), LitStr("hack"), - 16}, + font_size}, {LitStr("CutiveMono-Regular.ttf"), LitStr("cutive mono"), - 16}, + font_size}, {LitStr("Inconsolata-Regular.ttf"), LitStr("inconsolata"), - 16}, - + font_size}, }; i32 font_count = ArrayCount(font_setup); @@ -3301,7 +3324,7 @@ App_Init_Sig(app_init){ } // NOTE(allen): file setup - working_set_init(&models->working_set, partition); + working_set_init(&models->working_set, partition, &vars->models.mem.general); // NOTE(allen): clipboard setup models->working_set.clipboard_max_size = ArrayCount(models->working_set.clipboards); @@ -3353,7 +3376,7 @@ App_Init_Sig(app_init){ vars->sys_app_bindings = (Sys_App_Binding*)push_array(partition, Sys_App_Binding, vars->sys_app_max); // NOTE(allen): parameter setup - models->buffer_param_max = 32; + models->buffer_param_max = 1; models->buffer_param_count = 0; models->buffer_param_indices = push_array(partition, i32, models->buffer_param_max); } @@ -4136,7 +4159,7 @@ App_Step_Sig(app_step){ file_init_strings(result.file); file_set_name(working_set, result.file, filename.str); file_set_to_loading(result.file); - working_set_add(system, working_set, result.file); + working_set_add(system, working_set, result.file, general); result.sys_id = file_id; result.file_index = result.file->id.id; @@ -4241,7 +4264,7 @@ App_Step_Sig(app_step){ { Editing_File *file = working_set_alloc_always(working_set, general); file_create_empty(system, models, file, string.str); - working_set_add(system, working_set, file); + working_set_add(system, working_set, file, general); View *view = panel->view; diff --git a/4ed_file.cpp b/4ed_file.cpp index 1cb0f97b..8d8199af 100644 --- a/4ed_file.cpp +++ b/4ed_file.cpp @@ -361,7 +361,11 @@ internal Editing_File* working_set_alloc_always(Working_Set *working_set, General_Memory *general){ Editing_File *result = 0; Editing_File *new_chunk; - i16 new_count = 128; + i32 full_new_count = working_set->file_max; + i16 new_count; + + if (full_new_count > max_i16) new_count = max_i16; + else new_count = (i16)full_new_count; if (working_set->file_count == working_set->file_max && working_set->array_count < working_set->array_max){ @@ -465,17 +469,19 @@ working_set_contains(System_Functions *system, Working_Set *working_set, String } internal void -working_set_init(Working_Set *working_set, Partition *partition){ +working_set_init(Working_Set *working_set, Partition *partition, General_Memory *general){ + i16 init_count = 16; + i16 array_init_count = 256; + Editing_File *files, *null_file; void *mem; i32 mem_size, table_size; - i16 init_count = 128; dll_init_sentinel(&working_set->free_sentinel); dll_init_sentinel(&working_set->used_sentinel); - working_set->array_max = 128; - working_set->file_arrays = push_array(partition, File_Array, working_set->array_max); + working_set->array_max = array_init_count; + working_set->file_arrays = push_array(partition, File_Array, array_init_count); files = push_array(partition, Editing_File, init_count); working_set_extend_memory(working_set, files, init_count); @@ -485,45 +491,65 @@ working_set_init(Working_Set *working_set, Partition *partition){ null_file->state.is_dummy = 1; ++working_set->file_count; - table_size = working_set->file_max * 3 / 2; + table_size = working_set->file_max; mem_size = table_required_mem_size(table_size, sizeof(File_Table_Entry)); - mem = push_block(partition, mem_size); + mem = general_memory_allocate(general, mem_size, 0); memset(mem, 0, mem_size); table_init_memory(&working_set->table, mem, table_size, sizeof(File_Table_Entry)); - table_size = working_set->file_max / 4; + table_size = working_set->file_max; // / 4; mem_size = table_required_mem_size(table_size, sizeof(Non_File_Table_Entry)); - mem = push_block(partition, mem_size); + mem = general_memory_allocate(general, mem_size, 0); memset(mem, 0, mem_size); table_init_memory(&working_set->non_file_table, mem, table_size, sizeof(Non_File_Table_Entry)); } inline void -working_set_add_file(Working_Set *working_set, Unique_Hash key, File_ID file_id){ +working_set_grow_if_needed(Table *table, General_Memory *general, void *arg, Hash_Function *hash_func, Compare_Function *comp_func){ + Table btable; + i32 new_max, mem_size; + void *mem; + + if (table_at_capacity(table)){ + new_max = table->max * 2; + mem_size = table_required_mem_size(new_max, table->item_size); + mem = general_memory_allocate(general, mem_size, 0); + table_init_memory(&btable, mem, new_max, table->item_size); + table_clear(&btable); + table_rehash(table, &btable, 0, hash_func, comp_func); + general_memory_free(general, table->hash_array); + *table = btable; + } +} + +inline void +working_set_add_file(Working_Set *working_set, Unique_Hash key, File_ID file_id, General_Memory *general){ File_Table_Entry entry; entry.key = key; entry.id = file_id; + working_set_grow_if_needed(&working_set->table, general, 0, tbl_file_hash, tbl_file_compare); table_add(&working_set->table, &entry, 0, tbl_file_hash, tbl_file_compare); } inline void -working_set_add_non_file(Working_Set *working_set, String filename, File_ID file_id){ +working_set_add_non_file(Working_Set *working_set, String filename, File_ID file_id, General_Memory *general){ Non_File_Table_Entry entry; entry.name = filename; entry.id = file_id; + working_set_grow_if_needed(&working_set->table, general, 0, tbl_string_hash, tbl_string_compare); table_add(&working_set->non_file_table, &entry, 0, tbl_string_hash, tbl_string_compare); } inline void -working_set_add(System_Functions *system, Working_Set *working_set, Editing_File *file){ +working_set_add(System_Functions *system, Working_Set *working_set, Editing_File *file, General_Memory *general){ Unique_Hash file_hash; b32 success = 0; file_hash = system->file_unique_hash(file->name.source_path, &success); if (success){ - working_set_add_file(working_set, file_hash, file->id); + working_set_add_file(working_set, file_hash, file->id, general); } else{ - working_set_add_non_file(working_set, file->name.source_path, file->id); + working_set_add_non_file(working_set, file->name.source_path, file->id, general); } } diff --git a/4ed_file_view.cpp b/4ed_file_view.cpp index 9cec9e44..bb258780 100644 --- a/4ed_file_view.cpp +++ b/4ed_file_view.cpp @@ -2220,7 +2220,7 @@ view_clean_whitespace(System_Functions *system, Models *models, View *view){ internal void view_auto_tab_tokens(System_Functions *system, Models *models, View *view, - i32 start, i32 end, b32 empty_blank_lines){ + i32 start, i32 end, b32 empty_blank_lines, b32 use_tabs){ #if BUFFER_EXPERIMENT_SCALPEL <= 0 Editing_File *file = view->file; Mem_Options *mem = &models->mem; @@ -2370,9 +2370,10 @@ view_auto_tab_tokens(System_Functions *system, i32 correct_indentation; b32 all_whitespace = 0; b32 all_space = 0; + i32 tab_width = 4; i32 hard_start = buffer_find_hard_start(&file->state.buffer, start, &all_whitespace, &all_space, - &preferred_indentation, 4); + &preferred_indentation, tab_width); correct_indentation = indent_marks[line_i]; if (all_whitespace && empty_blank_lines) correct_indentation = 0; @@ -2383,8 +2384,16 @@ view_auto_tab_tokens(System_Functions *system, new_edit.str_start = str_size; str_size += correct_indentation; char *str = push_array(part, char, correct_indentation); - for (i32 j = 0; j < correct_indentation; ++j) str[j] = ' '; - new_edit.len = correct_indentation; + i32 j = 0; + if (use_tabs){ + i32 i = 0; + for (; i + tab_width <= correct_indentation; i += tab_width) str[j++] = '\t'; + for (; i < correct_indentation; ++i) str[j++] = ' '; + } + else{ + for (; j < correct_indentation; ++j) str[j] = ' '; + } + new_edit.len = j; new_edit.start = start; new_edit.end = hard_start; edits[edit_count++] = new_edit; @@ -3988,7 +3997,12 @@ search_hits_table_alloc(General_Memory *general, Table *hits, i32 table_size){ i32 mem_size; mem_size = table_required_mem_size(table_size, sizeof(Offset_String)); - mem = general_memory_reallocate_nocopy(general, hits->hash_array, mem_size, 0); + if (hits->hash_array == 0){ + mem = general_memory_allocate(general, mem_size, 0); + } + else{ + mem = general_memory_reallocate_nocopy(general, hits->hash_array, mem_size, 0); + } table_init_memory(hits, mem, table_size, sizeof(Offset_String)); } diff --git a/4tech_table.cpp b/4tech_table.cpp index ceab37da..fd92bd6f 100644 --- a/4tech_table.cpp +++ b/4tech_table.cpp @@ -90,6 +90,8 @@ table_find_pos(Table *table, void *search_key, void *arg, i32 *pos, i32 *index, u32 hash, *inspect; i32 i; + Assert((table->count - 1) * 8 < table->max * 7); + hash = (hash_func(search_key, arg) | TableHashMin); i = hash % table->max; inspect = table->hash_array + i; diff --git a/TODO.txt b/TODO.txt index 22e1380e..0cb1297d 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,4 +1,7 @@ +; before shipping: +; [] make sure 4coder_handmade_hero.cpp works + ; Started this list on: (18.01.2016)(dd.mm.yyyy) ; This list is an informal todo list, it may very well miss items ; checked or unchecked, that I inted to do some day. It is included @@ -66,8 +69,10 @@ ; [] tab character wrong width ; [] bouncing when scrolling down ; [] miblo's off screen cursor thing +; [] linux save jankieness ; -; [] open empty file bug ~ possibly a win10 issue? +; [] open empty file bug +; [] chronal's map setting issue ; ; @@ -88,11 +93,19 @@ ; [X] Seek string instead of delimiter ; [X] hook parameters ; [X] API based themes +; [X] improve file limit (now is > 8 million I think) +; [X] get key stroke in custom callback +; [X] tab option for auto-indent ; ; [] File status (saved, needs saving, out of sync) +; [] user file bar string +; [] catch unsaved files on close +; ; [] feedback messages ; [] command meta data ; [] additional hooks +; [] new file +; [] file out of sync ; [] double binding warnings ; [] kill rect ; [] simple multi-line @@ -145,6 +158,8 @@ ; [] error text at line ; [] word complete ghosting ; +; [] main_4coder experiment +; ; INTERNAL TODOS ; [X] switch building non-extensible version by statically linking to custom.cpp diff --git a/buffer/4coder_buffer_abstract.cpp b/buffer/4coder_buffer_abstract.cpp index a4bebeb4..ffd085e7 100644 --- a/buffer/4coder_buffer_abstract.cpp +++ b/buffer/4coder_buffer_abstract.cpp @@ -472,7 +472,8 @@ buffer_seek_alphanumeric_or_camel_left(Buffer_Type *buffer, int pos){ internal_4tech int buffer_find_hard_start(Buffer_Type *buffer, int line_start, int *all_whitespace, - int *all_space, int *preferred_indent, int tab_width){ + int *all_space, int *preferred_indent, int tab_width){ + Buffer_Stringify_Type loop; char *data; int size, end; diff --git a/test/4cpp_new_lexer.h b/test/4cpp_new_lexer.h index 1622028d..fcb8816c 100644 --- a/test/4cpp_new_lexer.h +++ b/test/4cpp_new_lexer.h @@ -11,6 +11,260 @@ namespace new_lex{ #define lexer_link static +// 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; +}; + + +// TODO(allen): shift towards storing in a context +static String_And_Flag int_suf_strings[] = { + {"ull"}, {"ULL"}, + {"llu"}, {"LLU"}, + {"ll"}, {"LL"}, + {"l"}, {"L"}, + {"u"}, {"U"} +}; + +#define lexer_string_list(x) {x, (sizeof(x)/sizeof(*x))} + +static String_List int_sufs = lexer_string_list(int_suf_strings); + +static String_And_Flag float_suf_strings[] = { + {"f"}, {"F"}, + {"l"}, {"L"} +}; +static String_List float_sufs = lexer_string_list(float_suf_strings); + +static String_And_Flag bool_lit_strings[] = { + {"true"}, {"false"} +}; +static String_List bool_lits = lexer_string_list(bool_lit_strings); + +static 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}, + {"unsigned", 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}, +}; +static String_List keywords = lexer_string_list(keyword_strings); + +static 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}, +}; +static String_List ops = lexer_string_list(op_strings); + +static String_And_Flag pp_op_strings[] = { + {"##", CPP_PP_CONCAT}, + {"#", CPP_PP_STRINGIFY}, +}; +static String_List pp_ops = lexer_string_list(pp_op_strings); + +static 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}, +}; +static String_List preprops = lexer_string_list(preprop_strings); + +lexer_link Sub_Match_List_Result +sub_match_list(char *chunk, int size, 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(chunk + pos, 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; +} + lexer_link Cpp_Get_Token_Result cpp_get_token(Cpp_Token_Stack *token_stack, int pos){ @@ -81,12 +335,19 @@ cpp_shift_token_starts(Cpp_Token_Stack *stack, int from_token_i, int shift_amoun enum Lex_State{ LS_default, LS_identifier, + LS_pound, + LS_pp, LS_char, + LS_char_slashed, LS_string, + LS_string_slashed, LS_number, + LS_number0, LS_float, + LS_hex, LS_comment_pre, LS_comment, + LS_comment_slashed, LS_comment_block, LS_comment_block_ending, LS_dot, @@ -106,14 +367,129 @@ enum Lex_State{ LS_caret, LS_eq, LS_bang, + LS_error_message, + // + LS_count +}; + +enum Lex_INC_State{ + LSINC_default, + LSINC_quotes, + LSINC_pointy, + LSINC_junk, +}; + +enum Lex_PP_State{ + LSPP_default, + LSPP_include, + LSPP_macro_identifier, + LSPP_identifier, + LSPP_body_if, + LSPP_body, + LSPP_number, + LSPP_error, + LSPP_junk, + // + LSPP_count +}; + +struct Lex_FSM{ + unsigned short state; + char pp_state; + char emit_token; + char multi_line; + char completed; }; struct Lex_Data{ + Lex_FSM fsm; + char pp_state; + char completed; int token_start; - int token_end; - int completed; }; +lexer_link Lex_PP_State +cpp_pp_directive_to_state(Cpp_Token_Type type){ + Lex_PP_State result = LSPP_default; + switch (type){ + case CPP_PP_INCLUDE: + case CPP_PP_IMPORT: + case CPP_PP_USING: + result = LSPP_include; + break; + + case CPP_PP_DEFINE: + result = LSPP_macro_identifier; + break; + + case CPP_PP_UNDEF: + case CPP_PP_IFDEF: + case CPP_PP_IFNDEF: + result = LSPP_identifier; + break; + + case CPP_PP_IF: + case CPP_PP_ELIF: + result = LSPP_body_if; + break; + + case CPP_PP_PRAGMA: + result = LSPP_body; + break; + + case CPP_PP_LINE: + result = LSPP_number; + break; + + case CPP_PP_ERROR: + result = LSPP_error; + break; + + case CPP_PP_UNKNOWN: + case CPP_PP_ELSE: + case CPP_PP_ENDIF: + result = LSPP_junk; + break; + } + return(result); +} + +lexer_link Cpp_Token_Merge +cpp_attempt_token_merge(Cpp_Token prev_token, Cpp_Token next_token){ + Cpp_Token_Merge result = {(Cpp_Token_Type)0}; + 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; +} + +lexer_link void +cpp_push_token_nonalloc(Cpp_Token *out_tokens, int *token_i, Cpp_Token token){ + Cpp_Token_Merge merge = {(Cpp_Token_Type)0}; + Cpp_Token prev_token = {(Cpp_Token_Type)0}; + + if (*token_i > 0){ + prev_token = out_tokens[*token_i - 1]; + merge = new_lex::cpp_attempt_token_merge(prev_token, token); + if (merge.did_merge){ + out_tokens[*token_i - 1] = merge.new_token; + } + } + + if (!merge.did_merge){ + out_tokens[(*token_i)++] = token; + } +} + lexer_link Lex_Data cpp_lex_nonalloc(char *chunk, int file_absolute_pos, int size, Cpp_Token_Stack *token_stack_out){ Cpp_Token *out_tokens = token_stack_out->tokens; @@ -124,12 +500,9 @@ cpp_lex_nonalloc(char *chunk, int file_absolute_pos, int size, Cpp_Token_Stack * int pos = file_absolute_pos; int end_pos = size + file_absolute_pos; - unsigned short state = LS_default; - unsigned short pp_state = 0; - Lex_Data lex_data = {}; - - int emit_token = 0; + Lex_Data lex_data = {0}; + Lex_FSM fsm = {0}; char c = 0; @@ -142,6 +515,9 @@ cpp_lex_nonalloc(char *chunk, int file_absolute_pos, int size, Cpp_Token_Stack * if (c == ' ' || c == '\n' || c == '\t' || c == '\r' || c == '\f' || c == '\v'){ for (; pos < end_pos;){ c = chunk[pos++]; + if (lex_data.pp_state != LSPP_default){ + if (c == '\n') lex_data.pp_state = LSPP_default; + } if (!(c == ' ' || c == '\n' || c == '\t' || c == '\r' || c == '\f' || c == '\v')) break; } --pos; @@ -149,9 +525,9 @@ cpp_lex_nonalloc(char *chunk, int file_absolute_pos, int size, Cpp_Token_Stack * lex_data.token_start = pos; - state = LS_default; - emit_token = 0; - for (; emit_token == 0 && pos <= end_pos;){ + fsm = {0}; + fsm.pp_state = lex_data.pp_state; + for (; fsm.emit_token == 0 && pos <= end_pos;){ if (pos < end_pos){ c = chunk[pos++]; } @@ -159,244 +535,401 @@ cpp_lex_nonalloc(char *chunk, int file_absolute_pos, int size, Cpp_Token_Stack * c = 0; ++pos; } - - switch (state){ - case LS_default: - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'){ - state = LS_identifier; - } - else if (c >= '1' && c <= '9'){ - state = LS_number; - } - else - switch (c){ - case '\'': state = LS_char; break; - case '"': state = LS_string; break; - - case '/': state = LS_comment_pre; break; - - case '.': state = LS_dot; break; - - case '<': state = LS_less; break; - case '>': state = LS_more; break; - - case '-': state = LS_minus; break; - - case '&': state = LS_and; break; - case '|': state = LS_or; break; - - case '+': state = LS_plus; break; - - case ':': state = LS_colon; break; - - case '*': state = LS_star; break; - - case '%': state = LS_modulo; break; - case '^': state = LS_caret; break; - - case '=': state = LS_eq; break; - case '!': state = LS_bang; break; - + + { + unsigned short state = fsm.state; + char pp_state = fsm.pp_state; + char emit_token = fsm.emit_token; + char multi_line = fsm.multi_line; + + switch (pp_state){ + case LSPP_error: + state = LS_error_message; + if (c == '\n') emit_token = 1; + break; + + case LSPP_include: + switch (state){ + case LSINC_default: + switch (c){ + case '"': state = LSINC_quotes; break; + case '<': state = LSINC_pointy; break; + default: state = LSINC_junk; break; + } + break; + + case LSINC_quotes: + if (c == '"') emit_token = 1; + break; + + case LSINC_pointy: + if (c == '>') emit_token = 1; + break; + + case LSINC_junk: + if (c == '\n') emit_token = 1; + break; + } + break; + + default: + switch (state){ + case LS_default: + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'){ + state = LS_identifier; + } + else if (c >= '1' && c <= '9'){ + state = LS_number; + } + else if (c == '0'){ + state = LS_number0; + } + else switch (c){ + case '\'': state = LS_char; break; + case '"': state = LS_string; break; + + case '/': state = LS_comment_pre; break; + + case '.': state = LS_dot; break; + + case '<': state = LS_less; break; + case '>': state = LS_more; break; + + case '-': state = LS_minus; break; + + case '&': state = LS_and; break; + case '|': state = LS_or; break; + + case '+': state = LS_plus; break; + + case ':': state = LS_colon; break; + + case '*': state = LS_star; break; + + case '%': state = LS_modulo; break; + case '^': state = LS_caret; break; + + case '=': state = LS_eq; break; + case '!': state = LS_bang; break; + + case '#': state = LS_pound; break; + #define OperCase(op,type) case op: emit_token = 1; break; - OperCase('{', CPP_TOKEN_BRACE_OPEN); - OperCase('}', CPP_TOKEN_BRACE_CLOSE); + OperCase('{', CPP_TOKEN_BRACE_OPEN); + OperCase('}', CPP_TOKEN_BRACE_CLOSE); - OperCase('[', CPP_TOKEN_BRACKET_OPEN); - OperCase(']', CPP_TOKEN_BRACKET_CLOSE); + OperCase('[', CPP_TOKEN_BRACKET_OPEN); + OperCase(']', CPP_TOKEN_BRACKET_CLOSE); - OperCase('(', CPP_TOKEN_PARENTHESE_OPEN); - OperCase(')', CPP_TOKEN_PARENTHESE_CLOSE); + OperCase('(', CPP_TOKEN_PARENTHESE_OPEN); + OperCase(')', CPP_TOKEN_PARENTHESE_CLOSE); - OperCase('~', CPP_TOKEN_TILDE); - OperCase(',', CPP_TOKEN_COMMA); - OperCase(';', CPP_TOKEN_SEMICOLON); - OperCase('?', CPP_TOKEN_TERNARY_QMARK); + OperCase('~', CPP_TOKEN_TILDE); + OperCase(',', CPP_TOKEN_COMMA); + OperCase(';', CPP_TOKEN_SEMICOLON); + OperCase('?', CPP_TOKEN_TERNARY_QMARK); + + OperCase('@', CPP_TOKEN_JUNK); + OperCase('$', CPP_TOKEN_JUNK); + OperCase('\\', CPP_TOKEN_JUNK); #undef OperCase - } - break; - - case LS_identifier: - if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')){ - emit_token = 1; - } - break; - - case LS_char: - // TODO - break; - - case LS_string: - // TODO - break; - - case LS_number: - if (c >= '0' && c <= '9'){ - state = LS_number; - } - else{ - emit_token = 1; - } - break; - - case LS_dot: - switch (c){ - case '.': state = LS_ellipsis; break; - case '*': emit_token = 1; break; - default: emit_token = 1; break; - } - break; - - case LS_ellipsis: - emit_token = 1; - break; - - case LS_less: - switch (c){ - case '<': state = LS_less_less; break; - case '=': emit_token = 1; break; - default: emit_token = 1; break; - } - break; - - case LS_less_less: - switch (c){ - case '=': emit_token = 1; break; - default: emit_token = 1; break; - } - break; - - case LS_more: - switch (c){ - case '>': state = LS_more_more; break; - case '=': emit_token = 1; break; - default: emit_token = 1; break; - } - break; - - case LS_more_more: - switch (c){ - case '=': emit_token = 1; break; - default: emit_token = 1; break; - } - break; - - case LS_comment_pre: - switch (c){ - case '/': state = LS_comment; break; - case '*': state = LS_comment_block; break; - case '=': emit_token = 1; break; - default: emit_token = 1; break; - } - break; + } + break; - case LS_comment: - switch (c){ - case '\n': emit_token = 1; break; - } - break; + case LS_identifier: + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')){ + emit_token = 1; + } + break; - case LS_comment_block: - switch (c){ - case '*': state = LS_comment_block_ending; break; - } - break; + case LS_pound: + if (pp_state == LSPP_default){ + if (c == ' ' || c == '\t' || c == '\r' || c == '\f' || c == '\v'){ + state = LS_pound; + } + else if (c == '\n'){ + emit_token = 1; + } + else{ + state = LS_pp; + } + } + else{ + switch (c){ + case '#': emit_token = 1; break; + default: emit_token = 1; break; + } + } + break; - case LS_comment_block_ending: - switch (c){ - case '*': state = LS_comment_block_ending; break; - case '/': emit_token = 1; break; - default: state = LS_comment_block; break; + case LS_pp: + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')){ + emit_token = 1; + } + break; + + case LS_char: + switch(c){ + case '\'': emit_token = 1; break; + case '\\': state = LS_char_slashed; multi_line |= 1; break; + } + break; + + case LS_char_slashed: + switch (c){ + case '\r': case '\f': case '\v': break; + default: state = LS_char; break; + } + break; + + case LS_string: + switch(c){ + case '\"': emit_token = 1; break; + case '\\': state = LS_string_slashed; multi_line |= 1; break; + } + break; + + case LS_string_slashed: + switch (c){ + case '\r': case '\f': case '\v': break; + default: state = LS_string; break; + } + break; + + case LS_number: + if (c >= '0' && c <= '9'){ + state = LS_number; + } + else{ + switch (c){ + case '.': state = LS_float; break; + default: emit_token = 1; break; + } + } + break; + + case LS_number0: + if (c >= '0' && c <= '9'){ + state = LS_number; + } + else if (c == 'x'){ + state = LS_hex; + } + else if (c == '.'){ + state = LS_float; + } + else{ + emit_token = 1; + } + break; + + case LS_float: + if (!(c >= '0' && c <= '9')){ + switch (c){ + case 'f': emit_token = 1; break; + default: emit_token = 1; break; + } + } + break; + + case LS_hex: + if (!(c >= '0' && c <= '9' || c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F')){ + emit_token = 1; + } + break; + + case LS_dot: + if (c >= '0' && c <= '9'){ + state = LS_float; + } + else + switch (c){ + case '.': state = LS_ellipsis; break; + case '*': emit_token = 1; break; + default: emit_token = 1; break; + } + break; + + case LS_ellipsis: emit_token = 1; break; + + case LS_less: + switch (c){ + case '<': state = LS_less_less; break; + case '=': emit_token = 1; break; + default: emit_token = 1; break; + } + break; + + case LS_less_less: + switch (c){ + case '=': emit_token = 1; break; + default: emit_token = 1; break; + } + break; + + case LS_more: + switch (c){ + case '>': state = LS_more_more; break; + case '=': emit_token = 1; break; + default: emit_token = 1; break; + } + break; + + case LS_more_more: + switch (c){ + case '=': emit_token = 1; break; + default: emit_token = 1; break; + } + break; + + case LS_comment_pre: + switch (c){ + case '/': state = LS_comment; break; + case '*': state = LS_comment_block; break; + case '=': emit_token = 1; break; + default: emit_token = 1; break; + } + break; + + case LS_comment: + switch (c){ + case '\\': state = LS_comment_slashed; break; + case '\n': emit_token = 1; break; + } + break; + + case LS_comment_slashed: + switch (c){ + case '\r': case '\f': case '\v': break; + default: state = LS_comment; break; + } + break; + + case LS_comment_block: + switch (c){ + case '*': state = LS_comment_block_ending; break; + } + break; + + case LS_comment_block_ending: + switch (c){ + case '*': state = LS_comment_block_ending; break; + case '/': emit_token = 1; break; + default: state = LS_comment_block; break; + } + break; + + case LS_minus: + switch (c){ + case '>': state = LS_arrow; break; + case '-': emit_token = 1; break; + case '=': emit_token = 1; break; + default: emit_token = 1; break; + } + break; + + case LS_arrow: + switch (c){ + case '*': emit_token = 1; break; + default: emit_token = 1; break; + } + break; + + case LS_and: + switch (c){ + case '&': emit_token = 1; break; + case '=': emit_token = 1; break; + default: emit_token = 1; break; + } + break; + + case LS_or: + switch (c){ + case '|': emit_token = 1; break; + case '=': emit_token = 1; break; + default: emit_token = 1; break; + } + break; + + case LS_plus: + switch (c){ + case '+': emit_token = 1; break; + case '=': emit_token = 1; break; + default: emit_token = 1; break; + } + break; + + case LS_colon: + switch (c){ + case ':': emit_token = 1; break; + default: emit_token = 1; break; + } + break; + + case LS_star: + switch (c){ + case '=': emit_token = 1; break; + default: emit_token = 1; break; + } + break; + + case LS_modulo: + switch (c){ + case '=': emit_token = 1; break; + default: emit_token = 1; break; + } + break; + + case LS_caret: + switch (c){ + case '=': emit_token = 1; break; + default: emit_token = 1; break; + } + break; + + case LS_eq: + switch (c){ + case '=': emit_token = 1; break; + default: emit_token = 1; break; + } + break; + + case LS_bang: + switch (c){ + case '=': emit_token = 1; break; + default: emit_token = 1; break; + } + break; + } + break; } - break; - - case LS_minus: - switch (c){ - case '>': state = LS_arrow; break; - case '-': emit_token = 1; break; - case '=': emit_token = 1; break; - default: emit_token = 1; break; - } - break; - - case LS_arrow: - switch (c){ - case '*': emit_token = 1; break; - default: emit_token = 1; break; - } - break; - - case LS_and: - switch (c){ - case '&': emit_token = 1; break; - case '=': emit_token = 1; break; - default: emit_token = 1; break; - } - break; - - case LS_or: - switch (c){ - case '|': emit_token = 1; break; - case '=': emit_token = 1; break; - default: emit_token = 1; break; - } - break; - - case LS_plus: - switch (c){ - case '+': emit_token = 1; break; - case '=': emit_token = 1; break; - default: emit_token = 1; break; - } - break; - - case LS_colon: - switch (c){ - case ':': emit_token = 1; break; - default: emit_token = 1; break; - } - break; - - case LS_star: - switch (c){ - case '=': emit_token = 1; break; - default: emit_token = 1; break; - } - break; - - case LS_modulo: - switch (c){ - case '=': emit_token = 1; break; - default: emit_token = 1; break; - } - break; - - case LS_caret: - switch (c){ - case '=': emit_token = 1; break; - default: emit_token = 1; break; - } - break; - - case LS_eq: - switch (c){ - case '=': emit_token = 1; break; - default: emit_token = 1; break; - } - break; - - case LS_bang: - switch (c){ - case '=': emit_token = 1; break; - default: emit_token = 1; break; - } - break; + + + fsm.state = state; + fsm.pp_state = pp_state; + fsm.emit_token = emit_token; + fsm.multi_line = multi_line; } } - - if (emit_token){ - lex_data.token_end = pos; - switch (state){ + if (fsm.emit_token){ + if (lex_data.pp_state == LSPP_include){ + switch (fsm.state){ + case LSINC_default:break; + + case LSINC_quotes: + case LSINC_pointy: + token.type = CPP_TOKEN_INCLUDE_FILE; + token.flags = 0; + break; + + case LSINC_junk: + token.type = CPP_TOKEN_JUNK; + token.flags = 0; + break; + } + } + else switch (fsm.state){ case LS_default: switch (c){ #define OperCase(op,t) case op: token.type = t; break; @@ -413,123 +946,221 @@ cpp_lex_nonalloc(char *chunk, int file_absolute_pos, int size, Cpp_Token_Stack * OperCase(',', CPP_TOKEN_COMMA); OperCase(';', CPP_TOKEN_SEMICOLON); OperCase('?', CPP_TOKEN_TERNARY_QMARK); + + OperCase('@', CPP_TOKEN_JUNK); + OperCase('$', CPP_TOKEN_JUNK); + OperCase('\\', CPP_TOKEN_JUNK); #undef OperCase } - token.flags = CPP_TFLAG_IS_OPERATOR; + if (c != '@' && c != '$' && c != '\\'){ + token.flags = CPP_TFLAG_IS_OPERATOR; + } break; - + case LS_identifier: - token.type = CPP_TOKEN_IDENTIFIER; - token.flags = 0; - --lex_data.token_end; - --pos; + { + --pos; + + int start = lex_data.token_start; + int word_size = pos - lex_data.token_start; + + + if (lex_data.pp_state == LSPP_body_if){ + if (match(make_string(chunk + start, word_size), make_lit_string("defined"))){ + token.type = CPP_TOKEN_DEFINED; + token.flags = CPP_TFLAG_IS_OPERATOR | CPP_TFLAG_IS_KEYWORD; + break; + } + } + + Sub_Match_List_Result sub_match; + sub_match = sub_match_list(chunk, size, start, bool_lits, word_size); + + if (sub_match.index != -1){ + token.type = CPP_TOKEN_BOOLEAN_CONSTANT; + token.flags = CPP_TFLAG_IS_KEYWORD; + } + else{ + sub_match = sub_match_list(chunk, size, start, keywords, word_size); + + if (sub_match.index != -1){ + String_And_Flag data = keywords.data[sub_match.index]; + token.type = (Cpp_Token_Type)data.flags; + token.flags = CPP_TFLAG_IS_KEYWORD; + } + else{ + token.type = CPP_TOKEN_IDENTIFIER; + token.flags = 0; + } + } + }break; + + case LS_pound: + token.flags = CPP_TFLAG_IS_OPERATOR; + switch (c){ + case '=': token.type = CPP_TOKEN_LESSEQ; break; + default: + token.type = CPP_TOKEN_LESS; + --pos; + break; + } break; - + + case LS_pp: + { + --pos; + int start = lex_data.token_start + 1; + + c = chunk[start]; + while (start < pos && (c == ' ' || c == '\n' || c == '\t' || c == '\r' || c == '\v' || c == '\f')){ + ++start; + c = chunk[start]; + } + + int word_size = pos - start; + Sub_Match_List_Result match; + match = sub_match_list(chunk, size, start, preprops, word_size); + + if (match.index != -1){ + String_And_Flag data = preprops.data[match.index]; + token.type = (Cpp_Token_Type)data.flags; + token.flags = CPP_TFLAG_PP_DIRECTIVE; + lex_data.pp_state = (char)cpp_pp_directive_to_state(token.type); + } + else{ + token.type = CPP_TOKEN_JUNK; + token.flags = 0; + } + }break; + case LS_number: + case LS_number0: + case LS_hex: token.type = CPP_TOKEN_INTEGER_CONSTANT; token.flags = 0; - --lex_data.token_end; --pos; break; - + + case LS_float: + token.type = CPP_TOKEN_FLOATING_CONSTANT; + token.flags = 0; + if (c != 'f'){ + --pos; + } + break; + + case LS_char: + token.type = CPP_TOKEN_CHARACTER_CONSTANT; + token.flags = 0; + break; + + case LS_string: + token.type = CPP_TOKEN_STRING_CONSTANT; + token.flags = 0; + break; + case LS_comment_pre: token.flags = CPP_TFLAG_IS_OPERATOR; switch (c){ case '=': token.type = CPP_TOKEN_DIVEQ; break; default: token.type = CPP_TOKEN_DIV; - --lex_data.token_end; --pos; break; } break; - + case LS_comment: case LS_comment_block_ending: token.type = CPP_TOKEN_COMMENT; token.flags = 0; - c = chunk[--lex_data.token_end]; + c = chunk[--pos]; while (c == ' ' || c == '\n' || c == '\t' || c == '\r' || c == '\v' || c == '\f'){ - --lex_data.token_end; - c = chunk[lex_data.token_end]; + --pos; + c = chunk[pos]; } - ++lex_data.token_end; + ++pos; break; - + + case LS_error_message: + token.type = CPP_TOKEN_ERROR_MESSAGE; + token.flags = 0; + c = chunk[--pos]; + while (c == ' ' || c == '\n' || c == '\t' || c == '\r' || c == '\v' || c == '\f'){ + --pos; + c = chunk[pos]; + } + ++pos; + break; + case LS_dot: token.flags = CPP_TFLAG_IS_OPERATOR; switch (c){ case '*': token.type = CPP_TOKEN_PTRDOT; break; default: token.type = CPP_TOKEN_DOT; - --lex_data.token_end; --pos; break; } break; - + case LS_ellipsis: switch (c){ case '.': token.flags = CPP_TFLAG_IS_OPERATOR; token.type = CPP_TOKEN_ELLIPSIS; break; - + default: token.type = CPP_TOKEN_JUNK; - --lex_data.token_end; --pos; break; } break; - + case LS_less: token.flags = CPP_TFLAG_IS_OPERATOR; switch (c){ case '=': token.type = CPP_TOKEN_LESSEQ; break; default: token.type = CPP_TOKEN_LESS; - --lex_data.token_end; --pos; break; } break; - + case LS_less_less: token.flags = CPP_TFLAG_IS_OPERATOR; switch (c){ case '=': token.type = CPP_TOKEN_LSHIFTEQ; break; default: token.type = CPP_TOKEN_LSHIFT; - --lex_data.token_end; --pos; break; } break; - + case LS_more: token.flags = CPP_TFLAG_IS_OPERATOR; switch (c){ case '=': token.type = CPP_TOKEN_GRTREQ; break; default: token.type = CPP_TOKEN_GRTR; - --lex_data.token_end; --pos; break; } break; - + case LS_more_more: token.flags = CPP_TFLAG_IS_OPERATOR; switch (c){ case '=': token.type = CPP_TOKEN_RSHIFTEQ; break; default: token.type = CPP_TOKEN_RSHIFT; - --lex_data.token_end; --pos; break; } break; - + case LS_minus: token.flags = CPP_TFLAG_IS_OPERATOR; switch (c){ @@ -537,24 +1168,22 @@ cpp_lex_nonalloc(char *chunk, int file_absolute_pos, int size, Cpp_Token_Stack * case '=': token.type = CPP_TOKEN_SUBEQ; break; default: token.type = CPP_TOKEN_MINUS; - --lex_data.token_end; --pos; break; } break; - + case LS_arrow: token.flags = CPP_TFLAG_IS_OPERATOR; switch (c){ case '*': token.type = CPP_TOKEN_PTRARROW; break; default: token.type = CPP_TOKEN_ARROW; - --lex_data.token_end; --pos; break; } break; - + case LS_and: token.flags = CPP_TFLAG_IS_OPERATOR; switch (c){ @@ -562,12 +1191,11 @@ cpp_lex_nonalloc(char *chunk, int file_absolute_pos, int size, Cpp_Token_Stack * case '=': token.type = CPP_TOKEN_ANDEQ; break; default: token.type = CPP_TOKEN_AMPERSAND; - --lex_data.token_end; --pos; break; } break; - + case LS_or: token.flags = CPP_TFLAG_IS_OPERATOR; switch (c){ @@ -575,12 +1203,11 @@ cpp_lex_nonalloc(char *chunk, int file_absolute_pos, int size, Cpp_Token_Stack * case '=': token.type = CPP_TOKEN_OREQ; break; default: token.type = CPP_TOKEN_BIT_OR; - --lex_data.token_end; --pos; break; } break; - + case LS_plus: token.flags = CPP_TFLAG_IS_OPERATOR; switch (c){ @@ -588,94 +1215,132 @@ cpp_lex_nonalloc(char *chunk, int file_absolute_pos, int size, Cpp_Token_Stack * case '=': token.type = CPP_TOKEN_ADDEQ; break; default: token.type = CPP_TOKEN_PLUS; - --lex_data.token_end; --pos; break; } break; - + case LS_colon: token.flags = CPP_TFLAG_IS_OPERATOR; switch (c){ case ':': token.type = CPP_TOKEN_SCOPE; break; default: token.type = CPP_TOKEN_COLON; - --lex_data.token_end; --pos; break; } break; - + case LS_star: token.flags = CPP_TFLAG_IS_OPERATOR; switch (c){ case '=': token.type = CPP_TOKEN_MULEQ; break; default: token.type = CPP_TOKEN_STAR; - --lex_data.token_end; --pos; break; } break; - + case LS_modulo: token.flags = CPP_TFLAG_IS_OPERATOR; switch (c){ case '=': token.type = CPP_TOKEN_MODEQ; break; default: token.type = CPP_TOKEN_MOD; - --lex_data.token_end; --pos; break; } break; - + case LS_caret: token.flags = CPP_TFLAG_IS_OPERATOR; switch (c){ case '=': token.type = CPP_TOKEN_XOREQ; break; default: token.type = CPP_TOKEN_BIT_XOR; - --lex_data.token_end; --pos; break; } break; - + case LS_eq: token.flags = CPP_TFLAG_IS_OPERATOR; switch (c){ case '=': token.type = CPP_TOKEN_EQEQ; break; default: token.type = CPP_TOKEN_EQ; - --lex_data.token_end; --pos; break; } break; - + case LS_bang: token.flags = CPP_TFLAG_IS_OPERATOR; switch (c){ case '=': token.type = CPP_TOKEN_NOTEQ; break; default: token.type = CPP_TOKEN_BIT_NOT; - --lex_data.token_end; --pos; break; } break; } - + + if ((token.flags & CPP_TFLAG_PP_DIRECTIVE) == 0){ + switch (lex_data.pp_state){ + case LSPP_include: + if (token.type != CPP_TOKEN_INCLUDE_FILE){ + token.type = CPP_TOKEN_JUNK; + } + lex_data.pp_state = LSPP_junk; + break; + + case LSPP_macro_identifier: + if (fsm.state != LS_identifier){ + token.type = CPP_TOKEN_JUNK; + lex_data.pp_state = LSPP_junk; + } + else{ + lex_data.pp_state = LSPP_body; + } + break; + + case LSPP_identifier: + if (fsm.state != LS_identifier){ + token.type = CPP_TOKEN_JUNK; + } + lex_data.pp_state = LSPP_junk; + break; + + case LSPP_number: + if (token.type != CPP_TOKEN_INTEGER_CONSTANT){ + token.type = CPP_TOKEN_JUNK; + lex_data.pp_state = LSPP_junk; + } + else{ + lex_data.pp_state = LSPP_include; + } + break; + + case LSPP_junk: + token.type = CPP_TOKEN_JUNK; + break; + } + } + token.start = lex_data.token_start; - token.size = lex_data.token_end - lex_data.token_start; - token.state_flags = pp_state; - out_tokens[token_i++] = token; + token.size = pos - lex_data.token_start; + token.flags |= (fsm.multi_line)?(CPP_TFLAG_MULTILINE):(0); + token.flags |= (fsm.pp_state != LSPP_default)?(CPP_TFLAG_PP_BODY):(0); + token.state_flags = fsm.pp_state; + + cpp_push_token_nonalloc(out_tokens, &token_i, token); } } - + token_stack_out->count = token_i; - + if (pos == end_pos) lex_data.completed = 1; return(lex_data); } diff --git a/test/experiment.cpp b/test/experiment.cpp index 7a635e54..fa3e10ab 100644 --- a/test/experiment.cpp +++ b/test/experiment.cpp @@ -181,7 +181,7 @@ struct Experiment{ }; static void -run_experiment(Experiment *exp, char *filename){ +run_experiment(Experiment *exp, char *filename, int verbose){ String extension = {}; Data file_data; Cpp_File file_cpp; @@ -190,12 +190,10 @@ run_experiment(Experiment *exp, char *filename){ extension = file_extension(make_string_slowly(filename)); if (match(extension, "cpp") || match(extension, "h")){ - - pass = 1; - printf("testing on file: %s\n", filename); file_data = dump_file(filename); - if (file_data.size < (100 << 10)){ + pass = 1; + printf("testing on file: %s\n", filename); exp->test_total++; exp->correct_stack.count = 0; @@ -226,23 +224,25 @@ run_experiment(Experiment *exp, char *filename){ if (correct->type != testing->type){ pass = 0; - printf("type mismatch at token %d\n", j); + if (verbose) printf("type mismatch at token %d\n", j); } if (correct->start != testing->start || correct->size != testing->size){ pass = 0; - printf("token range mismatch at token %d\n" - " %d:%d original %d:%d testing\n" - " %.*s original %.*s testing\n", - j, - correct->start, correct->size, testing->start, testing->size, - correct->size, file_cpp.data + correct->start, - testing->size, file_cpp.data + testing->start); + if (verbose){ + printf("token range mismatch at token %d\n" + " %d:%d original %d:%d testing\n" + " %.*s original %.*s testing\n", + j, + correct->start, correct->size, testing->start, testing->size, + correct->size, file_cpp.data + correct->start, + testing->size, file_cpp.data + testing->start); + } } if (correct->flags != testing->flags){ pass = 0; - printf("token flag mismatch at token %d\n", j); + if (verbose) printf("token flag mismatch at token %d\n", j); } } @@ -272,14 +272,14 @@ int main(){ AllowLocal(test_directory); AllowLocal(all_files); - run_experiment(&exp, BASE_DIR "autotab.cpp"); - #if 0 + run_experiment(&exp, BASE_DIR "lexer_test.cpp", 1); +#else system_set_file_list(&all_files, make_lit_string(test_directory)); for (int i = 0; i < all_files.count; ++i){ if (all_files.infos[i].folder == 0){ - run_experiment(&exp, all_files.infos[i].filename.str); + run_experiment(&exp, all_files.infos[i].filename.str, 0); } } #endif @@ -290,4 +290,3 @@ int main(){ } // BOTTOM - diff --git a/win32_4ed.cpp b/win32_4ed.cpp index 51925479..891dbf1c 100644 --- a/win32_4ed.cpp +++ b/win32_4ed.cpp @@ -1250,15 +1250,25 @@ Win32Callback(HWND hwnd, UINT uMsg, UINT vk = (UINT)wParam; UINT scan = (UINT)((lParam >> 16) & 0x7F); BYTE state[256]; - WORD x; - int result; + WORD x1 = 0, x2 = 0, x = 0; + int result1 = 0, result2 = 0, result = 0; GetKeyboardState(state); - if (control_keys[MDFR_CONTROL_INDEX] && - !control_keys[MDFR_ALT_INDEX]) - state[VK_CONTROL] = 0; - x = 0; - result = ToAscii(vk, scan, state, &x, 0); + x1 = 0; + result1 = ToAscii(vk, scan, state, &x1, 0); + state[VK_CONTROL] = 0; + x2 = 0; + result2 = ToAscii(vk, scan, state, &x2, 0); + + if (result1){ + x = x1; + result = 1; + } + else if (result2){ + x = x2; + result = 1; + } + if (result == 1 && x < 128){ key = (u8)x; if (key == '\r') key = '\n'; @@ -1959,7 +1969,7 @@ main(int argc, char **argv){ } - File_Slot file_slots[4]; + File_Slot file_slots[32]; sysshared_init_file_exchange(&exchange_vars, file_slots, ArrayCount(file_slots), 0); Font_Load_Parameters params[32];