se

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit b27ab5b198ba5cdf55e1cfe91bf43a3c77e56a87
parent 38c9b0a692b5eeb26b9bb0e1f1e614008378084a
Author: Samdal <samdal@protonmail.com>
Date:   Thu, 29 Jun 2023 20:11:18 +0200

add my config personal config because it's WIP

Diffstat:
M.gitignore | 2+-
Aconfig.c | 1824+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mextensions/startup_message.h | 1-
Mx.c | 10----------
4 files changed, 1825 insertions(+), 12 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,4 +1,4 @@ *.o *.out se -config.c +#config.c diff --git a/config.c b/config.c @@ -0,0 +1,1824 @@ +#include "config.h" +#include "extension.h" +#include <ctype.h> + +#define MODKEY Mod1Mask + +// TODO: search hilight no longer works because syntax is ran after drawing search highlight + +//////////////////////////////////////// +// apperance +// + +// font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html +char *fontconfig = "Liberation Mono:pixelsize=21:antialias=true:autohint=true"; +//char *fontconfig = "Iosevka:pixelsize=20:antialias=true:autohint=true"; + +// pixels of border around the window +int border_px = 2; + +// default size of the editor +unsigned int default_cols = 80; +unsigned int default_rows = 24; + +// Kerning / character bounding-box multipliers +float cw_scale = 1.0; +float ch_scale = 1.0; + +int wrap_buffer = 0; + +// spaces per tab (tabs will self align) +unsigned int tabspaces = 8; +unsigned int default_indent_len = 0; // 0 means tab + +// Default shape of cursor +// 2: block ("█") +// 4: underline ("_") +// 6: bar ("|") +// TODO: half_block +// TODO: enums +unsigned int cursor_shape = 2; + +// thickness of underline and bar cursors +unsigned int cursor_thickness = 2; + +/////////////////////////////////////////// +// custom window modes +// + +enum window_modes { + WB_MODES_START = WB_MODES_DEFAULT_END, + #include "extensions/window_modes/choose_one_of_selection.enums" + WB_MODES_END +}; + +/////////////////////////////////////////// +// color scheme +// the syntax highlighting is applied in one pass, +// so you can't have nested syntax highlighting +// + +#include "extensions/syntax/syntax.h" + +#include "extensions/syntax/schemes/gruvbox.h" + +// disable coloring functions for the syntax schemes below +#undef function_color +//#define function_color normal_color + +#include "extensions/syntax/gd.h" +#include "extensions/syntax/c.h" + +const struct syntax_scheme syntax[] = { + {".c", c_word_seperators, c_syntax, LEN(c_syntax), c_indent, LEN(c_indent)}, + {".h", c_word_seperators, c_syntax, LEN(c_syntax), c_indent, LEN(c_indent)}, + {".gd", gd_word_seperators, gd_syntax, LEN(gd_syntax), gd_indent, LEN(gd_indent)}, + + {0}, +}; + +const struct syntax_scheme* syntax_schemes = syntax; + +///////////////////////////////////////// +// Shortcuts +// + +enum vim_modes { + VIM_NORMAL, + VIM_INSERT, + VIM_REPLACE, + VIM_SINGULAR_REPLACE, + VIM_VISUAL, + VIM_VISUAL_LINE, + VIM_VISUAL_BLOCK, +}; + +static int vim_mode = VIM_NORMAL; + +enum inside_around_delimiters { + VIM_INSIDE_ROUND_BRACKETS, + VIM_INSIDE_SQUARE_BRACKETS, + VIM_INSIDE_CURLY_BRACKETS, + VIM_INSIDE_ANGLE_BRACKET, + VIM_INSIDE_DOUBLE_QUOTES, + VIM_INSIDE_SINGLE_QUOTES, + VIM_INSIDE_BACK_QUOTES, + VIM_INSIDE_PARAGRAPH, + + VIM_INSIDE_COUNT, + VIM_AROUND_START = VIM_INSIDE_COUNT, + + VIM_AROUND_ROUND_BRACKETS = VIM_AROUND_START, + VIM_AROUND_SQUARE_BRACKETS, + VIM_AROUND_CURLY_BRACKETS, + VIM_AROUND_ANGLE_BRACKET, + VIM_AROUND_DOUBLE_QUOTES, + VIM_AROUND_SINGLE_QUOTES, + VIM_AROUND_BACK_QUOTES, + VIM_AROUND_PARAGRAPH, + + VIM_AROUND_END = VIM_AROUND_PARAGRAPH, +}; + +_Static_assert(VIM_AROUND_END - VIM_AROUND_START + 1 == VIM_INSIDE_COUNT, "Bracket count does not match"); + + +const struct delimiter vim_delimiters[VIM_INSIDE_COUNT] = { + {"(", ")"}, + {"[", "]"}, + {"{", "}"}, + {"<", ">"}, + {"\"", "\""}, + {"'", "'"}, + {"`", "`"}, + {"\n\n", "\n\n"}, // TODO: this works poorly +}; + +struct delimiter ignore_delimiters[] = { + + // C comments + {"//", "\n"}, + {"/*", "*/"}, + + // Python multiline strings + {"\"\"\"", "\"\"\""}, + + // Strings + {"\"", "\""}, + {"'", "''"}, + {"'", "''"}, + {"`", "`"}, + + {0}, +}; + +// TODO: create a "delete in comments" function that +// selects around the current ignore delimiter + +enum misc_delimiters { + VIM_MISC_START = VIM_AROUND_END + 1, + + VIM_CURRENT_WORD = VIM_MISC_START, + VIM_CURRENT_LINE, + //VIM_CURRENT_LINE_INSIDE, + VIM_CURRENT_WORD_AND_SURROUNDING_WHITESPACE, + VIM_NEXT_QUOUTE, + + VIM_PREV_WORD_START, + VIM_TO_START_OF_WORD, + VIM_TO_END_OF_WORD, + + VIM_PREV_STRING_START, + VIM_TO_START_OF_STRING, + VIM_TO_END_OF_STRING, + + VIM_TO_START_OF_LINE, + VIM_TO_END_OF_INDENT, + VIM_TO_END_OF_LINE, + VIM_TO_START_OF_FILE, + VIM_TO_END_OF_FILE, + + VIM_SAME_INDENT_PLUS, + + VIM_CURRENT_SELECTION, + + VIM_MISC_COUNT, +}; + + +const char* vim_default_word_seperators = "., '\n\t*+-/%!~<>=(){}[]\"^&|\\`´?:;"; +const char* vim_override_word_seperators = "., '\n\t*+-/%!~<>=(){}[]\"^&|\\`´?:;_"; + +struct chained_keybind { + unsigned int mod; + KeySym keysym; + // 0 return value means it will keep this command level + // +1 return value means it will move into this commands next_keybinds + // +2 return value means it will continue looking another command + // -1 return value means it will reset the chained keybind + // -2 return value means it will reset the chained keybind (special case, e.g not reset state) + int(*func)(int custom_mode); + int custom_mode; + const char* description; + struct chained_keybind* next_keybinds; + int keybind_count; +}; + +#define CHAINED_KEYBIND_LEN 1024 +struct keypress_logg { + KeySym ksym; + unsigned int modkey; + int len; + char* buf; +}; +struct keypress_logg* previous_chain; +struct keypress_logg current_chain[CHAINED_KEYBIND_LEN] = {0}; +int previous_chain_len; +int chain_len; + +int last_used_command_index; + +static void +vim_copy_log(struct keypress_logg** dest, int dest_len, const struct keypress_logg* src, int src_len) +{ + *dest = xrealloc(*dest, src_len * sizeof(struct keypress_logg)); + if (dest_len == 0) + (*dest)->buf = NULL; + + int total_buf_len = 0; + for (int i = 0; i < src_len; i++) + total_buf_len += src[i].len; + char* chars = xrealloc((*dest)->buf, total_buf_len); + + for (int i = 0; i < src_len; i++) { + struct keypress_logg* t = (*dest) + i; + struct keypress_logg s = src[i]; + t->buf = chars; + memcpy(t->buf, s.buf, s.len); + t->ksym = s.ksym; + t->modkey = s.modkey; + t->len = s.len; + chars += s.len; + } +} + +static int +vim_chain_parse_count_raw() +{ + int last_ksym_was_num = 0; + int sum = 0; + int current_num = 0; + for (int i = last_used_command_index; i < chain_len; i++) { + int current_val; + switch(current_chain[i].ksym) { + case XK_0: current_val = 0; break; + case XK_1: current_val = 1; break; + case XK_2: current_val = 2; break; + case XK_3: current_val = 3; break; + case XK_4: current_val = 4; break; + case XK_5: current_val = 5; break; + case XK_6: current_val = 6; break; + case XK_7: current_val = 7; break; + case XK_8: current_val = 8; break; + case XK_9: current_val = 9; break; + default: current_val =-1; break; + } + if (current_val < 0) { + if (last_ksym_was_num) { + sum += current_num; + current_num = 0; + last_ksym_was_num = 0; + } + continue; + } + if (last_ksym_was_num) + current_num *= 10; + else + last_ksym_was_num = 1; + current_num += current_val; + } + if (last_ksym_was_num) + sum += current_num; + + return sum; +} + +static int +vim_chain_parse_count(int custom_mode) +{ + if (custom_mode == VIM_TO_END_OF_FILE || custom_mode == VIM_TO_START_OF_FILE) + return 1; + int sum = vim_chain_parse_count_raw(); + if (sum == 0) + sum = 1; + return sum; +} + +static int +vim_get_delimiter(int delimiter_type, int offset, int* start, int* end) +{ + if (delimiter_type < 0) + return 0; + soft_assert(start, static int tmp; start = &tmp;); + soft_assert(end, static int tmp; end = &tmp;); + + struct file_buffer* fb = get_fb(focused_window); + LIMIT(offset, 0, fb->len-1); + + int count; + + if (delimiter_type < VIM_MISC_START) { + int around = delimiter_type >= VIM_AROUND_START ? 1 : 0; + if (around) + delimiter_type -= VIM_AROUND_START; + + soft_assert(delimiter_type < VIM_INSIDE_COUNT, return 0;); + if (!fb_get_delimiter(fb, offset, vim_delimiters[delimiter_type], ignore_delimiters, start, end)) { + int next_offset = fb_seek_string(fb, offset, vim_delimiters[delimiter_type].start); + if (next_offset < 0 || next_offset - offset >= 115) + return 0; + if (!fb_get_delimiter(fb, next_offset, vim_delimiters[delimiter_type], ignore_delimiters, start, end)) + return 0; + } + + if (!around) + *start += strlen(vim_delimiters[delimiter_type].start); + else + *end += strlen(vim_delimiters[delimiter_type].end); + + return 1; + } + + +#ifdef SYNTAX_H_ + const struct syntax_scheme* cs = fb_get_syntax_scheme(fb); + const char* word_seperators = cs ? cs->word_seperators : vim_default_word_seperators; +#else + const char* word_seperators = vim_default_word_seperators; +#endif + if (vim_override_word_seperators) + word_seperators = vim_override_word_seperators; + + switch(delimiter_type) { + case VIM_CURRENT_WORD: + if (isspace(fb->contents[offset])) { + int not_whitespace = fb_seek_not_whitespace_backwards(fb, offset); + if (not_whitespace >= 0) + not_whitespace += 1; + int line_start = fb_seek_char_backwards(fb, offset, '\n'); + *start = MAX(not_whitespace, line_start); + not_whitespace = fb_seek_not_whitespace(fb, offset); + line_start = fb_seek_char(fb, offset, '\n'); + *end = MIN(not_whitespace, line_start); + if (*end < 0) + *end = MAX(not_whitespace, line_start); + } else if (fb_is_on_a_word(fb, offset, word_seperators)) { + *start = fb_seek_start_of_word_backwards(fb, offset, word_seperators); + *end = fb_seek_word_end(fb, offset, word_seperators); + } else { + const char* word_seperators_no_newline = "., '\t*+-/%!~<>=(){}[]\"^&|\\`´?:;"; + *start = fb_seek_word_backwards(fb, offset, word_seperators_no_newline); + if (*start >= 0) + *start += 1; + *end = fb_seek_word(fb, offset, word_seperators_no_newline); + } + if (*start < 0 || *end < 0) + return 0; + return 1; + case VIM_CURRENT_LINE: + *start = fb_seek_char_backwards(fb, offset, '\n'); + *end = fb_seek_char(fb, offset, '\n'); + if (*start < 0) + *start = 0; + if (*end < 0) + *end = fb->len-1; + if (*end > 0) + *end += 1; + return 1; + case VIM_TO_END_OF_LINE: + *end = fb_seek_char(fb, offset, '\n'); + if (*end < 0) + return 0; + *start = offset; + return 1; + case VIM_TO_START_OF_LINE: + *start = fb_seek_char_backwards(fb, offset-1, '\n'); + if (*start < 0) + *start = 0; + *end = offset; + return 1; + case VIM_TO_END_OF_INDENT: + *start = fb_seek_char_backwards(fb, offset, '\n'); + if (*start < 0) + *start = 0; + if (*start + 1 < fb->len-1 && isspace(fb->contents[*start + 1])) { + int not_whitespace = fb_seek_not_whitespace(fb, *start + 1); + int line_end = fb_seek_char(fb, *start + 1, '\n'); + if (line_end < not_whitespace) + return 0; + if (not_whitespace >= 0) + *start = not_whitespace; + } + *end = offset; + return 1; + case VIM_TO_START_OF_FILE: + count = vim_chain_parse_count_raw(); + if (count) { + count--; + int old_offset = focused_window->cursor_offset; + + focused_window->cursor_offset = 0; + wb_move_lines(focused_window, count, 0); + int new_offset = fb_seek_char_backwards(fb, focused_window->cursor_offset, '\n'); + if (new_offset < 0) + new_offset = focused_window->cursor_offset; + + focused_window->cursor_offset = old_offset; + + if (new_offset < old_offset) { + *start = new_offset; + *end = old_offset; + } else { + *end = new_offset; + *start = old_offset; + } + printf("%d %d\n", *start, *end); + } else { + *start = 0; + *end = offset; + } + return 1; + case VIM_TO_END_OF_FILE: + count = vim_chain_parse_count_raw(); + if (count) { + count--; + int old_offset = focused_window->cursor_offset; + focused_window->cursor_offset = 0; + wb_move_lines(focused_window, count, 0); + focused_window->cursor_offset = old_offset; + int new_offset = fb_seek_char_backwards(fb, focused_window->cursor_offset, '\n'); + if (new_offset < old_offset) { + *start = new_offset; + *end = old_offset; + } else { + *end = new_offset; + *start = old_offset; + } + } else { + *start = offset; + *end = fb->len-1; + } + return 1; + case VIM_TO_START_OF_WORD: + *end = fb_seek_word(fb, offset, word_seperators); + *start = offset; + if (*end < 0) + return 0; + return 1; + case VIM_PREV_WORD_START: + *start = fb_seek_start_of_word_backwards(fb, offset-1, word_seperators); + *end = offset; + if (*start < 0) + return 0; + return 1; + case VIM_PREV_STRING_START: + *end = offset; + offset--; + if (isspace(fb->contents[offset])) + offset = fb_seek_not_whitespace_backwards(fb, offset); + *start = fb_seek_whitespace_backwards(fb, offset-1); + if (*start < 0) + return 0; + *start += 1; + return 1; + case VIM_TO_END_OF_WORD: + *end = fb_seek_word_end(fb, offset, word_seperators); + *start = offset; + if (*end < 0) + return 0; + return 1; + case VIM_TO_START_OF_STRING: + *start = offset; + if (!isspace(fb->contents[offset])) + offset = fb_seek_whitespace(fb, offset); + *end = fb_seek_not_whitespace(fb, offset); + if (*end < 0) + return 0; + return 1; + case VIM_TO_END_OF_STRING: + *start = offset; + if (isspace(fb->contents[offset])) + offset = fb_seek_not_whitespace(fb, offset); + *end = fb_seek_whitespace(fb, offset); + if (*end < 0) + return 0; + return 1; + case VIM_CURRENT_SELECTION: + if (fb_is_selection_start_top_left(fb)) { + *start = fb->s1o; + *end = fb->s2o + 1; + } else { + *start = fb->s2o; + *end = fb->s1o + 1; + } + return 1; + default: + return 0; + } +} + +static int +vim_change_mode(int custom_mode) +{ + int previous_mode = vim_mode; + + if (custom_mode == VIM_NORMAL) { + if (vim_mode == VIM_INSERT) + wb_move_on_line(focused_window, -1, CURSOR_COMMAND_MOVEMENT); + struct file_buffer* fb = get_fb(focused_window); + cursor_shape = 2; + fb->mode &= ~FB_SELECT_MASK; + fb->mode &= ~FB_SEARCH_BLOCKING_MASK; + fb->mode &= ~FB_SEARCH_NON_BLOCKING; + fb->mode &= ~FB_SEARCH_NON_BLOCKING_BACKWARDS; + fb->mode &= ~FB_SEARCH_BLOCKING_BACKWARDS; + writef_to_status_bar("Escape"); + } else if (custom_mode == VIM_INSERT) { + cursor_shape = 6; + writef_to_status_bar("-- INSERT --"); + } else if (custom_mode == VIM_REPLACE) { + cursor_shape = 4; + } else if (custom_mode == VIM_VISUAL || VIM_VISUAL_LINE) { + struct file_buffer* fb = get_fb(focused_window); + fb->mode |= FB_SELECTION_ON; + if (custom_mode == VIM_VISUAL) { + fb->s1o = fb->s2o = focused_window->cursor_offset; + writef_to_status_bar("-- VISUAL --"); + } else if (custom_mode == VIM_VISUAL_LINE) { + fb->s1o = fb_seek_char(fb, focused_window->cursor_offset, '\n'); + fb->s2o = fb_seek_char_backwards(fb, focused_window->cursor_offset, '\n'); + fb->mode |= FB_LINE_SELECT; + writef_to_status_bar("-- VISUAL LINE --"); + + custom_mode = VIM_VISUAL; + } + } + vim_mode = custom_mode; + if (custom_mode == VIM_VISUAL || (vim_mode == previous_mode)) + return -2; + return -1; +} + +static int +vim_exit(int custom_mode) { + exit(0); +} + +static int +vim_move_on_line(int custom_mode) +{ + if (focused_window->mode != WB_FILE_BROWSER) + wb_move_on_line(focused_window, custom_mode * vim_chain_parse_count(0), CURSOR_RIGHT_LEFT_MOVEMENT); + return -2; +} + +static int +vim_move_lines(int custom_mode) +{ + wb_move_lines(focused_window, custom_mode * vim_chain_parse_count(0), 0); + wb_move_to_x(focused_window, focused_window->cursor_col, CURSOR_UP_DOWN_MOVEMENT); + return -2; +} + +static int +vim_change_window(int custom_mode) +{ + int count = vim_chain_parse_count(0); + while(count--) { + focused_node = window_switch_to_window(focused_node, custom_mode); + focused_window = &focused_node->wb; + } + return -2; +} + +static int +vim_resize_window(int custom_mode) +{ + float amount = (custom_mode == MOVE_RIGHT || custom_mode == MOVE_LEFT) ? 0.1f : 0.05f; + window_node_resize(focused_node, custom_mode, amount * vim_chain_parse_count(0)); + return -2; +} + +static int +vim_swap_to_next_file_buffer(int custom_mode) +{ + focused_window->fb_index += vim_chain_parse_count(0); + return -2; +} + +static int +vim_split_window(int custom_mode) +{ + int count = vim_chain_parse_count(0); + while(count--) { + window_node_split(focused_node, 0.5, custom_mode); +#if 1 + if (focused_node->node2) { + focused_node = focused_node->node2; + focused_window = &focused_node->wb; + } +#else + if (focused_node->node1) { + focused_node = focused_node->node1; + focused_window = &focused_node->wb; + } +#endif + } + return -2; +} + +static int +vim_delete_window(int custom_mode) +{ + int count = vim_chain_parse_count(0); + while(count--) { + struct window_split_node* new_node = window_node_delete(focused_node); + while (new_node->mode != WINDOW_SINGULAR) + new_node = new_node->node1; + focused_node = new_node; + focused_window = &focused_node->wb; + } + return -2; +} + +static int +vim_kill_buffer(int custom_mode) +{ + int count = vim_chain_parse_count(0); + while(count--) + destroy_fb_entry(focused_node, &root_node); + return -2; +} + +static int +vim_save_buffer(int custom_mode) +{ + fb_write_to_filepath(get_fb(focused_window)); + return -2; +} + +static int +vim_zoomabs(int custom_mode) +{ + xunloadfonts(); + xloadfonts(fontconfig, custom_mode); + cresize(0, 0); + xhints(); + return -2; +} + +static int +vim_zoom(int custom_mode) +{ + vim_zoomabs(usedfontsize + custom_mode); + return -2; +} + +static int +vim_zoomreset(int custom_mode) +{ + if (defaultfontsize > 0) + vim_zoomabs(defaultfontsize); + return -2; +} + +static int +vim_zero(int custom_mode) +{ + if (vim_chain_parse_count_raw()) + return 0; + return 2; +} + +static int +vim_yank(int custom_mode) +{ + struct file_buffer* fb = get_fb(focused_window); + char* buf; + int len; + if (custom_mode == VIM_CURRENT_SELECTION || fb->mode & FB_SELECTION_ON) { + buf = fb_get_selection(fb, &len); + wb_move_cursor_to_selection_start(focused_window); + fb->mode &= ~FB_SELECT_MASK; + vim_change_mode(VIM_NORMAL); + } else { + int start, end; + if (!vim_get_delimiter(custom_mode, focused_window->cursor_offset, &start, &end)) { + writef_to_status_bar("unable to yank area"); + return -1; + } + buf = fb_get_string_between_offsets(fb, start, end); + len = end - start; + } + set_clipboard_copy(buf, len); + writef_to_status_bar("area yanked"); + return -2; +} + +static int paste_behind = 0; + +static int +vim_paste(int custom_mode) +{ + custom_mode = paste_behind; + execute_clipbaord_event(); + return -1; +} + +static int +vim_auto_indent_current_line(int custom_mode) +{ + struct file_buffer* fb = get_fb(focused_window); + focused_window->cursor_offset = fb_seek_char_backwards(fb, focused_window->cursor_offset, '\n'); + int indent_size = fb_auto_indent(fb, focused_window->cursor_offset); + + focused_window->cursor_offset = MIN(fb_seek_not_whitespace(fb, focused_window->cursor_offset), + fb_seek_char(fb, focused_window->cursor_offset, '\n')); + + window_node_move_all_cursors_on_same_fb(&root_node, focused_node, focused_window->fb_index, focused_window->cursor_offset, + wb_move_offset_relative, indent_size, CURSOR_COMMAND_MOVEMENT); + return -2; +} + +static int +vim_open_file_browser(int custom_mode) +{ + int last_fb = focused_window->fb_index; + struct file_buffer* fb = get_fb(focused_window); + + char* path = file_path_get_path(fb->file_path); + *focused_window = wb_new(fb_new_entry(path)); + focused_window->cursor_col = last_fb; + free(path); + return -1; +} + +int +vim_search_for_buffer(int custom_mode) +{ + if (focused_window->mode != WB_NORMAL) + return -2; + *focused_node->search = 0; + focused_node->selected = 0; + focused_window->mode = WB_SEARCH_FOR_BUFFERS; + return -2; +} + +int +vim_search_keyword_in_buffers(int custom_mode) +{ + if (focused_window->mode != WB_NORMAL) + return -2; + *focused_node->search = 0; + focused_node->selected = 0; + focused_window->mode = WB_SEARCH_KEYWORD_ALL_BUFFERS; + return -2; +} + +static int +vim_delete(int custom_mode) +{ + int count = vim_chain_parse_count(custom_mode); + while (count--) { + int start, end; + if (!vim_get_delimiter(custom_mode, focused_window->cursor_offset, &start, &end)) { + writef_to_status_bar("unable to find section to delete"); + return -1; + } + if (start - end == 0) + return -2; + struct file_buffer* fb = get_fb(focused_window); + fb_remove(fb, start, end - start, 1, 1); + call_extension(fb_contents_updated, fb, focused_window->cursor_offset, FB_CONTENT_BIG_CHANGE); + wb_move_to_offset(focused_window, start, CURSOR_COMMAND_MOVEMENT); + window_node_move_all_cursors_on_same_fb(&root_node, focused_node, focused_window->fb_index, end, + wb_move_offset_relative, end - start, CURSOR_COMMAND_MOVEMENT); + if (custom_mode == VIM_CURRENT_SELECTION) { + vim_change_mode(VIM_NORMAL); + break; + } + } + return -1; +} + +static int +vim_change(int custom_mode) +{ + int count = vim_chain_parse_count(custom_mode); + while (count--) { + int start, end; + if (!vim_get_delimiter(custom_mode, focused_window->cursor_offset, &start, &end)) { + writef_to_status_bar("unable to find section to change"); + return -1; + } + if (start - end == 0) + return -2; + struct file_buffer* fb = get_fb(focused_window); + fb_remove(fb, start, end - start, 1, 1); + call_extension(fb_contents_updated, fb, focused_window->cursor_offset, FB_CONTENT_BIG_CHANGE); + wb_move_to_offset(focused_window, start, CURSOR_COMMAND_MOVEMENT); + window_node_move_all_cursors_on_same_fb(&root_node, focused_node, focused_window->fb_index, end, + wb_move_offset_relative, end - start, CURSOR_COMMAND_MOVEMENT); + if (custom_mode == VIM_CURRENT_SELECTION) + break; + + } + vim_change_mode(VIM_INSERT); + return -1; +} + +static int +vim_visual_delimit(int custom_mode) +{ + int start, end; + if (!vim_get_delimiter(custom_mode, focused_window->cursor_offset, &start, &end)) { + writef_to_status_bar("unable to find section to highlight"); + return -1; + } + + end--; + struct file_buffer* fb = get_fb(focused_window); + fb->mode |= FB_SELECTION_ON; + fb->mode &= ~FB_SELECT_MODE_MASK; + writef_to_status_bar("-- VISUAL --"); + + if (end == focused_window->cursor_offset) { + fb->s1o = end; + fb->s2o = start; + + focused_window->cursor_offset = start; + return -1; + } + + fb->s1o = start; + fb->s2o = end; + + focused_window->cursor_offset = end; + return -2; +} + +static int vim_enter(int custom_mode) {return 1;} + +static int +vim_append(int custom_mode) +{ + vim_change_mode(VIM_INSERT); + wb_move_on_line(focused_window, 1, CURSOR_COMMAND_MOVEMENT); + return -1; +} + +static int +vim_insert_start(int custom_mode) +{ + vim_change_mode(VIM_INSERT); + int start, tmp; + if (vim_get_delimiter(VIM_TO_END_OF_INDENT, focused_window->cursor_offset, &start, &tmp)) + wb_move_to_offset(focused_window, start, CURSOR_COMMAND_MOVEMENT); + return -1; +} + +static int +vim_insert_end(int custom_mode) +{ + vim_change_mode(VIM_INSERT); + int end, tmp; + if (vim_get_delimiter(VIM_TO_END_OF_LINE, focused_window->cursor_offset, &tmp, &end)) + wb_move_to_offset(focused_window, end, CURSOR_COMMAND_MOVEMENT); + return -1; +} + +static int +vim_insert_new_line(int custom_mode) +{ + vim_change_mode(VIM_INSERT); + struct file_buffer* fb = get_fb(focused_window); + + int offset = focused_window->cursor_offset; + offset = fb_seek_char(fb, offset, '\n'); + if (offset < 0) + offset = fb->len-1; + focused_window->cursor_offset = offset; + wb_move_offset_relative(focused_window, 1, CURSOR_COMMAND_MOVEMENT); + + fb_insert(fb, "\n", 1, offset, 0); + window_node_move_all_cursors_on_same_fb(&root_node, focused_node, focused_window->fb_index, focused_window->cursor_offset, + wb_move_offset_relative, 1, CURSOR_COMMAND_MOVEMENT); + vim_auto_indent_current_line(0); + + return -1; +} + +static int +vim_remove_newline_at_end(int custom_mode) +{ + int count = vim_chain_parse_count(0); + struct file_buffer* fb = get_fb(focused_window); + while (count--) { + int offset = fb_seek_char(fb, focused_window->cursor_offset, '\n'); + fb_remove(fb, offset, 1, 1, 0); + wb_move_to_offset(focused_window, offset, CURSOR_COMMAND_MOVEMENT); + window_node_move_all_cursors_on_same_fb(&root_node, focused_node, focused_window->fb_index, offset, + wb_move_offset_relative, -1, CURSOR_COMMAND_MOVEMENT); + + LIMIT(offset, 0, fb->len); + if (isspace(fb->contents[offset])) { + int start, end; + int not_whitespace = fb_seek_not_whitespace_backwards(fb, offset); + if (not_whitespace >= 0) + not_whitespace += 1; + int line_start = fb_seek_char_backwards(fb, offset, '\n'); + start = MAX(not_whitespace, line_start); + + not_whitespace = fb_seek_not_whitespace(fb, offset); + line_start = fb_seek_char(fb, offset, '\n'); + end = MIN(not_whitespace, line_start); + if (end < 0) + end = MAX(not_whitespace, line_start); + + if (end < 0 || start < 0) + return -1; + + if (fb->contents[offset] != '\n') { + fb_remove(fb, start, end - start, 1, 0); + fb_insert(fb, " ", 1, start, 1); + start++; + wb_move_to_offset(focused_window, start, CURSOR_COMMAND_MOVEMENT); + window_node_move_all_cursors_on_same_fb(&root_node, focused_node, focused_window->fb_index, end, + wb_move_offset_relative, end - start, CURSOR_COMMAND_MOVEMENT); + } + } else { + fb_insert(fb, " ", 1, offset, 1); + } + } + return -1; +} + +static int +vim_undo(int custom_mode) +{ + int count = vim_chain_parse_count(0); + while (count--) + fb_undo(get_fb(focused_window)); + return -2; +} + +static int +vim_redo(int custom_mode) +{ + int count = vim_chain_parse_count(0); + while (count--) + fb_redo(get_fb(focused_window)); + return -2; +} + +static int search_reversed = 0; +static int vim_next(int custom_mode); +static int vim_prev(int custom_mode); + +int +vim_next(int custom_mode) +{ + if (!custom_mode && search_reversed) + return vim_prev(1); + + get_fb(focused_window)->mode |= FB_SEARCH_BLOCKING_IDLE; + int new_offset = wb_seek_string_wrap(focused_window, focused_window->cursor_offset+1, + get_fb(focused_window)->search_term); + if (new_offset < 0) { + writef_to_status_bar("no results for \"%s\"", get_fb(focused_window)->search_term); + return -1; + } else if (focused_window->cursor_offset > new_offset) { + writef_to_status_bar("search wrapped"); + } + focused_window->cursor_offset = new_offset; + return -2; +} + +int +vim_prev(int custom_mode) +{ + if (!custom_mode && search_reversed) + return vim_next(1); + + get_fb(focused_window)->mode |= FB_SEARCH_BLOCKING_IDLE; + int new_offset = wb_seek_string_wrap_backwards(focused_window, focused_window->cursor_offset-1, + get_fb(focused_window)->search_term); + if (new_offset < 0) { + writef_to_status_bar("no results for \"%s\"", get_fb(focused_window)->search_term); + return -1; + } else if (focused_window->cursor_offset < new_offset) { + writef_to_status_bar("search wrapped"); + } + focused_window->cursor_offset = new_offset; + return -2; +} + +static int +vim_search(int custom_mode) +{ + search_reversed = custom_mode; + get_fb(focused_window)->mode &= ~FB_SEARCH_BLOCKING_IDLE; + get_fb(focused_window)->mode |= FB_SEARCH_BLOCKING; + writef_to_status_bar("search: %s", get_fb(focused_window)->search_term); + return -2; +} + +static int +vim_home(int custom_mode) +{ + struct file_buffer* fb = get_fb(focused_window); + int new_offset = fb_seek_char_backwards(fb, focused_window->cursor_offset, '\n'); + if (new_offset < 0) + new_offset = 0; + wb_move_to_offset(focused_window, new_offset, CURSOR_COMMAND_MOVEMENT); + return -2; +} + +static int +vim_end(int custom_mode) +{ + struct file_buffer* fb = get_fb(focused_window); + int new_offset = fb_seek_char(fb, focused_window->cursor_offset, '\n'); + if (new_offset < 0) + new_offset = fb->len-1; + wb_move_to_offset(focused_window, new_offset, CURSOR_COMMAND_MOVEMENT); + return -2; +} + +static int +vim_move_down_one_screen_devided_by(int custom_mode) +{ + soft_assert(custom_mode, return -2;); + int count = vim_chain_parse_count(0); + + wb_move_lines(focused_window, ((focused_node->maxy - focused_node->miny) / custom_mode) * count, 0); + wb_move_to_x(focused_window, focused_window->cursor_col, CURSOR_UP_DOWN_MOVEMENT); + focused_window->y_scroll += ((focused_node->maxy - focused_node->miny) / custom_mode) * count; + return -2; +} + +static int +vim_move_scroll(int custom_mode) +{ + int count = vim_chain_parse_count(0); + focused_window->y_scroll += custom_mode * count; + return -2; +} + +static int +vim_center_scroll(int custom_mode) +{ + struct file_buffer* fb = get_fb(focused_window); + int tmp, y; + fb_offset_to_xy(fb, focused_window->cursor_offset, focused_node->maxx, focused_window->y_scroll, &tmp, &y, &tmp); + focused_window->y_scroll += y - ((focused_node->maxy - focused_node->miny) / 2); + return -2; +} + +// TODO: make (x/delete) remove something to the on it when it's next to new line +// instead of doing nothing +static int +vim_remove_one_char(int custom_mode) +{ + struct window_split_node* excluded = (custom_mode == 0) ? focused_node : NULL; + + int old_offset = focused_window->cursor_offset; + if (custom_mode) + wb_move_on_line(focused_window, custom_mode, CURSOR_DO_NOT_CALLBACK); + int offset = focused_window->cursor_offset; + focused_window->cursor_offset = old_offset; + + int times = vim_chain_parse_count(0); + struct file_buffer* fb = get_fb(focused_window); + while (times-- && fb->contents[offset] != '\n') { + int len = fb_remove(fb, offset, 1, 0, 0); + window_node_move_all_cursors_on_same_fb(&root_node, excluded, focused_window->fb_index, offset, + wb_move_offset_relative, -len, CURSOR_COMMAND_MOVEMENT); + } + return -1; +} + +static int +vim_backspace(int custom_mode) +{ + struct file_buffer* fb = get_fb(focused_window); + int offset = focused_window->cursor_offset-1; + if (offset <= 0 || offset >= fb->len) + return -1; + if (fb->contents[offset] == '\n') { + fb_remove(fb, offset, 1, 1, 0); + window_node_move_all_cursors_on_same_fb(&root_node, NULL, focused_window->fb_index, offset, + wb_move_offset_relative, -1, CURSOR_COMMAND_MOVEMENT); + } else { + vim_remove_one_char(-1); + } + return -1; +} + +static int +vim_insert_return(int custom_mode) +{ + struct file_buffer* fb = get_fb(focused_window); + int offset = focused_window->cursor_offset; + + fb_insert(fb, "\n", 1, offset, 0); + int indent_size = fb_auto_indent(fb, offset); + window_node_move_all_cursors_on_same_fb(&root_node, NULL, focused_window->fb_index, offset, + wb_move_offset_relative, indent_size + 1, 0); + indent_size = fb_auto_indent(fb, offset + 1 + indent_size); + window_node_move_all_cursors_on_same_fb(&root_node, NULL, focused_window->fb_index, offset, + wb_move_offset_relative, indent_size, CURSOR_COMMAND_MOVEMENT); + window_node_move_all_yscrolls(&root_node, focused_node, focused_window->fb_index, focused_window->cursor_offset, 1); + return -1; +} + +static int +vim_insert_tab(int custom_mode) +{ + int offset = focused_window->cursor_offset; + struct file_buffer* fb = get_fb(focused_window); + + fb_insert(fb, "\t", 1, offset, 0); + window_node_move_all_cursors_on_same_fb(&root_node, NULL, focused_window->fb_index, offset, + wb_move_on_line, 1, CURSOR_COMMAND_MOVEMENT); + return -1; +} + +static int +vim_move(int custom_mode) +{ + int count = vim_chain_parse_count(custom_mode); + struct file_buffer* fb = get_fb(focused_window); + while(count--) { + int offset = focused_window->cursor_offset; + if (custom_mode != VIM_PREV_STRING_START && custom_mode != VIM_PREV_WORD_START && offset + 1 < fb->len) + offset++; + int start, end; + if(!vim_get_delimiter(custom_mode, offset, &start, &end)) + return -1; + int other; + if (abs(offset - start) > abs(offset - end)) + other = start; + else + other = end; + wb_move_to_offset(focused_window, other, CURSOR_COMMAND_MOVEMENT); + } + return -2; +} + +static int vim_repeat_last_command(int custom_mode); + +#define numbers() \ + {0, XK_0, vim_zero}, \ + {0, XK_1}, \ + {0, XK_2}, \ + {0, XK_3}, \ + {0, XK_4}, \ + {0, XK_5}, \ + {0, XK_6}, \ + {0, XK_7}, \ + {0, XK_8}, \ + {0, XK_9} +// count = 10 + +#define glue(_start, _end) _start##_end + +#define VIM_DELIMITER_REGIONS(_func, _inside_around) \ + {XK_ANY_MOD, XK_w, _func, VIM_CURRENT_WORD}, \ + {XK_ANY_MOD, XK_parenleft, _func, glue(VIM_, _inside_around##_ROUND_BRACKETS)}, \ + {XK_ANY_MOD, XK_parenright, _func, glue(VIM_, _inside_around##_ROUND_BRACKETS)}, \ + {XK_ANY_MOD, XK_b, _func, glue(VIM_, _inside_around##_ROUND_BRACKETS)}, \ + {XK_ANY_MOD, XK_bracketleft, _func, glue(VIM_, _inside_around##_SQUARE_BRACKETS)}, \ + {XK_ANY_MOD, XK_bracketright, _func, glue(VIM_, _inside_around##_SQUARE_BRACKETS)}, \ + {XK_ANY_MOD, XK_braceleft, _func, glue(VIM_, _inside_around##_CURLY_BRACKETS)}, \ + {XK_ANY_MOD, XK_braceright, _func, glue(VIM_, _inside_around##_CURLY_BRACKETS)}, \ + {XK_ANY_MOD, XK_B, _func, glue(VIM_, _inside_around##_CURLY_BRACKETS)}, \ + {XK_ANY_MOD, XK_less, _func, glue(VIM_, _inside_around##_ANGLE_BRACKET)}, \ + {XK_ANY_MOD, XK_greater, _func, glue(VIM_, _inside_around##_ANGLE_BRACKET)}, \ + {XK_ANY_MOD, XK_t, _func, glue(VIM_, _inside_around##_ANGLE_BRACKET)}, \ + {XK_ANY_MOD, XK_quotedbl, _func, glue(VIM_, _inside_around##_DOUBLE_QUOTES)}, \ + {XK_ANY_MOD, XK_apostrophe, _func, glue(VIM_, _inside_around##_SINGLE_QUOTES)}, \ + {XK_ANY_MOD, XK_p, _func, glue(VIM_, _inside_around##_PARAGRAPH)}, \ + {XK_ANY_MOD, XK_grave, _func, glue(VIM_, _inside_around##_BACK_QUOTES)} +// count = 16 + +#define CHAIN_COUNT(_x) (_x) + +struct chained_keybind normal_mode_keybinds[] = { + numbers(), + // se specific keybinds, all followed by SPC, inspired by Emacs evil-mode + {XK_ANY_MOD, XK_space, vim_enter, 0, "se commands [...]", (struct chained_keybind[]) { + {0, XK_h, vim_change_window, MOVE_LEFT}, + {0, XK_j, vim_change_window, MOVE_DOWN}, + {0, XK_k, vim_change_window, MOVE_UP}, + {0, XK_l, vim_change_window, MOVE_RIGHT}, + {XK_ANY_MOD, XK_h, vim_resize_window, MOVE_LEFT}, + {XK_ANY_MOD, XK_j, vim_resize_window, MOVE_DOWN}, + {XK_ANY_MOD, XK_k, vim_resize_window, MOVE_UP}, + {XK_ANY_MOD, XK_l, vim_resize_window, MOVE_RIGHT}, + {XK_ANY_MOD, XK_H, vim_resize_window, MOVE_LEFT}, + {XK_ANY_MOD, XK_J, vim_resize_window, MOVE_DOWN}, + {XK_ANY_MOD, XK_K, vim_resize_window, MOVE_UP}, + {XK_ANY_MOD, XK_L, vim_resize_window, MOVE_RIGHT}, + {ShiftMask, XK_W, vim_save_buffer}, + {ControlMask, XK_s, vim_save_buffer}, + {0, XK_s, vim_split_window, WINDOW_VERTICAL}, + {0, XK_v, vim_split_window, WINDOW_HORISONTAL}, + {0, XK_d, vim_delete_window}, + {0, XK_Tab, vim_swap_to_next_file_buffer}, + {XK_ANY_MOD, XK_D, vim_kill_buffer}, + {0, XK_b, vim_enter, 0, "buffer [...]", (struct chained_keybind[]) { + {0, XK_k, vim_kill_buffer}, + {0, XK_s, vim_search_for_buffer}, + {0, XK_w, vim_save_buffer}, + {0, XK_space, vim_search_for_buffer}, + {XK_ANY_MOD, XK_slash, vim_search_keyword_in_buffers}, + }, CHAIN_COUNT(5), + }, + {0, XK_space, vim_open_file_browser}, + {ControlMask, XK_space, vim_search_for_buffer}, + {0, XK_p, vim_search_for_buffer}, + {XK_ANY_MOD, XK_slash, vim_search_keyword_in_buffers}, + {XK_ANY_MOD, XK_plus, vim_zoom, +1}, + {XK_ANY_MOD, XK_minus, vim_zoom, -1}, + {XK_ANY_MOD, XK_Home, vim_zoomreset}, + numbers(), + }, CHAIN_COUNT(36), + }, + + // movement + {0, XK_h, vim_move_on_line, -1}, + {0, XK_j, vim_move_lines, +1}, + {0, XK_k, vim_move_lines, -1}, + {0, XK_l, vim_move_on_line, +1}, + {0, XK_Left, vim_move_on_line, -1}, + {0, XK_Down, vim_move_lines, +1}, + {0, XK_Up, vim_move_lines, -1}, + {0, XK_Right, vim_move_on_line, +1}, + {0, XK_BackSpace, vim_move_on_line, -1}, + {0, XK_Home, vim_home}, + {0, XK_End, vim_end}, + {0, XK_0, vim_move, VIM_TO_START_OF_LINE}, + {ControlMask, XK_Right, vim_move, VIM_TO_END_OF_STRING}, + {ControlMask, XK_Left, vim_move, VIM_PREV_STRING_START}, + {0, XK_w, vim_move, VIM_TO_START_OF_WORD}, + {ShiftMask, XK_W, vim_move, VIM_TO_START_OF_STRING}, + {0, XK_e, vim_move, VIM_TO_END_OF_WORD}, + {ShiftMask, XK_E, vim_move, VIM_TO_END_OF_STRING}, + {0, XK_b, vim_move, VIM_PREV_WORD_START}, + {ShiftMask, XK_B, vim_move, VIM_PREV_STRING_START}, + {XK_ANY_MOD, XK_dollar, vim_move, VIM_TO_END_OF_LINE}, + {XK_ANY_MOD, XK_G, vim_move, VIM_TO_END_OF_FILE}, + {0, XK_g, vim_enter, 0, "goto", (struct chained_keybind[]) { + {0, XK_g, vim_move, VIM_TO_START_OF_FILE}, + }, CHAIN_COUNT(1), + }, + + // scroll + {0, XK_Page_Down, vim_move_down_one_screen_devided_by, 1}, + {0, XK_Page_Up, vim_move_down_one_screen_devided_by, -1}, + {ControlMask, XK_d, vim_move_down_one_screen_devided_by, 2}, + {ControlMask, XK_u, vim_move_down_one_screen_devided_by, -2}, + {ControlMask, XK_f, vim_move_down_one_screen_devided_by, 1}, + {ControlMask, XK_b, vim_move_down_one_screen_devided_by, -1}, + {ControlMask, XK_e, vim_move_scroll, 1}, + {ControlMask, XK_y, vim_move_scroll, -1}, + {0, XK_z, vim_enter, 0, NULL, (struct chained_keybind[]) { + {0, XK_z, vim_center_scroll}, + }, CHAIN_COUNT(1), + }, + + // misc + {XK_ANY_MOD, XK_Escape, vim_change_mode, VIM_NORMAL}, + {0, XK_Tab, vim_auto_indent_current_line}, + {0, XK_q, vim_exit}, + {0, XK_u, vim_undo}, + {0, XK_period, vim_repeat_last_command}, + {ControlMask, XK_r, vim_redo}, + {XK_ANY_MOD, XK_slash, vim_search}, + {XK_ANY_MOD, XK_question, vim_search, 1}, + {0, XK_n, vim_next}, + {ShiftMask, XK_N, vim_prev}, + // copy / yank + {0, XK_p, vim_paste}, + {0, XK_y, vim_enter, 0, "yank", (struct chained_keybind[]) { + {0, XK_y, vim_yank, VIM_CURRENT_LINE}, + {XK_ANY_MOD, XK_I, vim_yank, VIM_TO_END_OF_INDENT}, + {XK_ANY_MOD, XK_0, vim_yank, VIM_TO_START_OF_LINE}, + {XK_ANY_MOD, XK_A, vim_yank, VIM_TO_END_OF_LINE}, + {XK_ANY_MOD, XK_G, vim_yank, VIM_TO_END_OF_FILE}, + {XK_ANY_MOD, XK_w, vim_yank, VIM_TO_START_OF_WORD}, + {XK_ANY_MOD, XK_W, vim_yank, VIM_TO_START_OF_STRING}, + {XK_ANY_MOD, XK_e, vim_yank, VIM_TO_END_OF_WORD}, + {XK_ANY_MOD, XK_E, vim_yank, VIM_TO_END_OF_STRING}, + {XK_ANY_MOD, XK_b, vim_yank, VIM_PREV_WORD_START}, + {XK_ANY_MOD, XK_B, vim_yank, VIM_PREV_STRING_START}, + {XK_ANY_MOD, XK_dollar, vim_yank, VIM_TO_END_OF_LINE}, + {0, XK_g, vim_enter, 0, NULL, (struct chained_keybind[]) { + {0, XK_g, vim_yank, VIM_TO_START_OF_FILE}, + }, CHAIN_COUNT(1), + }, + {0, XK_i, vim_enter, 0, "inside", (struct chained_keybind[]) { + VIM_DELIMITER_REGIONS(vim_yank, INSIDE) + }, CHAIN_COUNT(16), + }, + {0, XK_a, vim_enter, 0, "around", (struct chained_keybind[]) { + VIM_DELIMITER_REGIONS(vim_yank, AROUND) + }, CHAIN_COUNT(16), + }, + numbers(), + }, CHAIN_COUNT(25) + }, + + // insert + {0, XK_i, vim_change_mode, VIM_INSERT}, + {XK_ANY_MOD, XK_I, vim_insert_start}, + {0, XK_a, vim_append}, + {XK_ANY_MOD, XK_A, vim_insert_end}, + {0, XK_o, vim_insert_new_line}, + + // visual + {0, XK_v, vim_change_mode, VIM_VISUAL}, + {ShiftMask, XK_V, vim_change_mode, VIM_VISUAL_LINE}, + //{ControlMask, XK_v, vim_change_mode, VIM_VISUAL}, + + // deleting + {XK_ANY_MOD, XK_J, vim_remove_newline_at_end}, + {0, XK_x, vim_remove_one_char}, + {XK_ANY_MOD, XK_X, vim_remove_one_char, -1}, + {0, XK_Delete, vim_remove_one_char, +1}, + {ControlMask, XK_BackSpace, vim_delete, VIM_PREV_STRING_START}, + {ControlMask, XK_Delete, vim_delete, VIM_TO_END_OF_STRING}, + {XK_ANY_MOD, XK_D, vim_delete, VIM_TO_END_OF_LINE}, + {0, XK_d, vim_enter, 0, "delete", (struct chained_keybind[]) { + {0, XK_d, vim_delete, VIM_CURRENT_LINE}, + {0, XK_l, vim_remove_one_char}, + {0, XK_h, vim_remove_one_char, -1}, + {XK_ANY_MOD, XK_I, vim_delete, VIM_TO_END_OF_INDENT}, + {XK_ANY_MOD, XK_A, vim_delete, VIM_TO_END_OF_LINE}, + {XK_ANY_MOD, XK_G, vim_delete, VIM_TO_END_OF_FILE}, + {XK_ANY_MOD, XK_w, vim_delete, VIM_TO_START_OF_WORD}, + {XK_ANY_MOD, XK_W, vim_delete, VIM_TO_START_OF_STRING}, + {XK_ANY_MOD, XK_e, vim_delete, VIM_TO_END_OF_WORD}, + {XK_ANY_MOD, XK_E, vim_delete, VIM_TO_END_OF_STRING}, + {XK_ANY_MOD, XK_b, vim_delete, VIM_PREV_WORD_START}, + {XK_ANY_MOD, XK_B, vim_delete, VIM_PREV_STRING_START}, + {XK_ANY_MOD, XK_dollar, vim_delete, VIM_TO_END_OF_LINE}, + {0, XK_g, vim_enter, 0, NULL, (struct chained_keybind[]) { + {0, XK_g, vim_delete, VIM_TO_START_OF_FILE}, + }, CHAIN_COUNT(1), + }, + {0, XK_i, vim_enter, 0, "inside", (struct chained_keybind[]) { + VIM_DELIMITER_REGIONS(vim_delete, INSIDE) + }, CHAIN_COUNT(16), + }, + {0, XK_a, vim_enter, 0, "around", (struct chained_keybind[]) { + VIM_DELIMITER_REGIONS(vim_delete, AROUND) + }, CHAIN_COUNT(16), + }, + numbers(), + {XK_ANY_MOD, XK_0, vim_delete, VIM_TO_START_OF_LINE}, + }, CHAIN_COUNT(27), + }, + // change + {XK_ANY_MOD, XK_C, vim_change, VIM_TO_END_OF_LINE}, + {0, XK_c, vim_enter, 0, "change", (struct chained_keybind[]) { + {0, XK_c, vim_change, VIM_CURRENT_LINE}, + //{0, XK_l, vim_remove_one_char}, + //{0, XK_h, vim_remove_one_char, -1}, + {XK_ANY_MOD, XK_I, vim_change, VIM_TO_END_OF_INDENT}, + {XK_ANY_MOD, XK_A, vim_change, VIM_TO_END_OF_LINE}, + {XK_ANY_MOD, XK_G, vim_change, VIM_TO_END_OF_FILE}, + {XK_ANY_MOD, XK_w, vim_change, VIM_TO_START_OF_WORD}, + {XK_ANY_MOD, XK_W, vim_change, VIM_TO_START_OF_STRING}, + {XK_ANY_MOD, XK_e, vim_change, VIM_TO_END_OF_WORD}, + {XK_ANY_MOD, XK_E, vim_change, VIM_TO_END_OF_STRING}, + {XK_ANY_MOD, XK_b, vim_change, VIM_PREV_WORD_START}, + {XK_ANY_MOD, XK_B, vim_change, VIM_PREV_STRING_START}, + {XK_ANY_MOD, XK_dollar, vim_change, VIM_TO_END_OF_LINE}, + {0, XK_g, vim_enter, 0, NULL, (struct chained_keybind[]) { + {0, XK_g, vim_change, VIM_TO_START_OF_FILE}, + }, CHAIN_COUNT(1), + }, + {0, XK_i, vim_enter, 0, "inside", (struct chained_keybind[]) { + VIM_DELIMITER_REGIONS(vim_change, INSIDE) + }, CHAIN_COUNT(16), + }, + {0, XK_a, vim_enter, 0, "around", (struct chained_keybind[]) { + VIM_DELIMITER_REGIONS(vim_change, AROUND) + }, CHAIN_COUNT(16), + }, + numbers(), + {XK_ANY_MOD, XK_0, vim_change, VIM_TO_START_OF_LINE}, + }, CHAIN_COUNT(27-2), + }, +}; + +struct chained_keybind visual_mode_keybinds[] = { + numbers(), + {XK_ANY_MOD, XK_Escape, vim_change_mode, VIM_NORMAL}, + {0, XK_v, vim_change_mode, VIM_NORMAL}, + + {0, XK_h, vim_move_on_line, -1}, + {0, XK_j, vim_move_lines, +1}, + {0, XK_k, vim_move_lines, -1}, + {0, XK_l, vim_move_on_line, +1}, + {0, XK_Left, vim_move_on_line, -1}, + {0, XK_Down, vim_move_lines, +1}, + {0, XK_Up, vim_move_lines, -1}, + {0, XK_Right, vim_move_on_line, +1}, + {0, XK_BackSpace, vim_move_on_line, -1}, + {0, XK_Home, vim_home}, + {0, XK_End, vim_end}, + {ControlMask, XK_Right, vim_move, VIM_TO_END_OF_STRING}, + {ControlMask, XK_Left, vim_move, VIM_PREV_STRING_START}, + {0, XK_w, vim_move, VIM_TO_START_OF_WORD}, + {ShiftMask, XK_W, vim_move, VIM_TO_START_OF_STRING}, + {0, XK_e, vim_move, VIM_TO_END_OF_WORD}, + {ShiftMask, XK_E, vim_move, VIM_TO_END_OF_STRING}, + {0, XK_b, vim_move, VIM_PREV_WORD_START}, + {ShiftMask, XK_B, vim_move, VIM_PREV_STRING_START}, + + {XK_ANY_MOD, XK_0, vim_move, VIM_TO_START_OF_LINE}, + {XK_ANY_MOD, XK_A, vim_move, VIM_TO_END_OF_LINE}, + {XK_ANY_MOD, XK_G, vim_move, VIM_TO_END_OF_FILE}, + {XK_ANY_MOD, XK_w, vim_move, VIM_TO_START_OF_WORD}, + {XK_ANY_MOD, XK_W, vim_move, VIM_TO_START_OF_STRING}, + {XK_ANY_MOD, XK_e, vim_move, VIM_TO_END_OF_WORD}, + {XK_ANY_MOD, XK_E, vim_move, VIM_TO_END_OF_STRING}, + {XK_ANY_MOD, XK_b, vim_move, VIM_PREV_WORD_START}, + {XK_ANY_MOD, XK_B, vim_move, VIM_PREV_STRING_START}, + {XK_ANY_MOD, XK_dollar, vim_move, VIM_TO_END_OF_LINE}, + {0, XK_i, vim_enter, 0, "inside", (struct chained_keybind[]) { + VIM_DELIMITER_REGIONS(vim_visual_delimit, INSIDE) + }, CHAIN_COUNT(16), + }, + {0, XK_a, vim_enter, 0, "around", (struct chained_keybind[]) { + VIM_DELIMITER_REGIONS(vim_visual_delimit, AROUND) + }, CHAIN_COUNT(16), + }, + + {0, XK_d, vim_delete, VIM_CURRENT_SELECTION}, + {0, XK_x, vim_delete, VIM_CURRENT_SELECTION}, + {XK_ANY_MOD, XK_X, vim_delete, VIM_CURRENT_SELECTION}, + {0, XK_y, vim_yank, VIM_CURRENT_SELECTION}, +}; + +struct chained_keybind insert_mode_keybinds[] = { + {XK_ANY_MOD, XK_Escape, vim_change_mode, VIM_NORMAL}, + {0, XK_Page_Down, vim_move_down_one_screen_devided_by, 1}, + {0, XK_Page_Up, vim_move_down_one_screen_devided_by, -1}, + {0, XK_Home, vim_home}, + {0, XK_End, vim_end}, + {0, XK_Left, vim_move_on_line, -1}, + {0, XK_Down, vim_move_lines, +1}, + {0, XK_Up, vim_move_lines, -1}, + {0, XK_Right, vim_move_on_line, +1}, + {ControlMask, XK_Right, vim_move, VIM_TO_END_OF_STRING}, + {ControlMask, XK_Left, vim_move, VIM_PREV_STRING_START}, + + {0, XK_BackSpace, vim_backspace, -1}, + {0, XK_Delete, vim_remove_one_char}, + {ControlMask, XK_BackSpace, vim_delete, VIM_PREV_STRING_START}, + {ControlMask, XK_Delete, vim_delete, VIM_TO_END_OF_STRING}, + {0, XK_Return, vim_insert_return}, + {0, XK_Tab, vim_insert_tab}, +}; + +static void +push_keypress_to_log(KeySym ksym, unsigned int modkey, const char* buf, int len) +{ + if (chain_len+1 <= CHAINED_KEYBIND_LEN) { + current_chain[chain_len].ksym = ksym; + current_chain[chain_len].modkey = modkey; + if (len != 0) + current_chain[chain_len].buf = xrealloc(current_chain[chain_len].buf, len); + memcpy(current_chain[chain_len].buf, buf, len); + current_chain[chain_len].len = len; + chain_len++; + } +} + +static int +do_chained_keybinds(struct chained_keybind** keybinds, int* keybind_count, KeySym ksym, unsigned int modkey, const char* buf, int len) +{ + // remove modifiers + switch(ksym) { + case XK_Shift_L: case XK_Shift_R: + case XK_Alt_L: case XK_Alt_R: + case XK_Control_L: case XK_Control_R: + case XK_Super_L: case XK_Super_R: + case XK_Meta_L: case XK_Meta_R: + case XK_ISO_Group_Latch: case XK_ISO_Group_Shift: case XK_ISO_Group_Lock: + case XK_ISO_Level3_Latch: case XK_ISO_Level3_Lock: case XK_ISO_Level3_Shift: + case XK_ISO_Level5_Latch: case XK_ISO_Level5_Lock: case XK_ISO_Level5_Shift: + case XK_ISO_Level2_Latch: + return 3; + } + push_keypress_to_log(ksym, modkey, buf, len); + for (int i = 0; i < *keybind_count; i++) { + struct chained_keybind keybind = (*keybinds)[i]; + if (ksym == keybind.keysym && match(keybind.mod, modkey)) { + if (!keybind.func) + return 1; + + int res = keybind.func(keybind.custom_mode); + + // TODO: add enums for random values + if (!res) + return 1; + if (res == 2) + continue; + + if (res < 0) { + *keybinds = NULL; + *keybind_count = 0; + if (res == -2) + return 4; + return 2; + } + if (res == 1) { + soft_assert(keybind.next_keybinds && keybind.keybind_count, + *keybinds = NULL; + return 0; + ); + *keybinds = keybind.next_keybinds; + *keybind_count = keybind.keybind_count; + return 1; + } + } + } + *keybinds = NULL; + return 0; +} + +static const char* +get_ksym_string_no_xk(KeySym ksym, unsigned int modkey) +{ + static char str[1024]; + *str = 0; + char* ksname; + if (!(ksname = XKeysymToString(ksym))) + ksname = "(no name)"; + else if (strcmp(ksname, "XK_") == 0) + ksname += 3; + + if (modkey != XK_ANY_MOD) { + if (modkey & ControlMask) + strcat(str, "C-"); + if (modkey & MODKEY) + strcat(str, "M-"); + } + strncat(str, ksname, sizeof(str) - strlen(str) - 1); + + return str; +} + +static const char* +current_key_chain_string(int dont_add_minus_end) +{ + if (!chain_len) + return NULL; + + static char buf[STATUS_BAR_MAX_LEN]; + *buf = 0; + int len = 0; + + for(int i = 0; i < chain_len; i++) { + const char* str = get_ksym_string_no_xk(current_chain[i].ksym, current_chain[i].modkey); + int new_len = strlen(str); + if (new_len + len < STATUS_BAR_MAX_LEN) { + strcat(buf, str); + len += new_len; + } + + if (len + 1 < STATUS_BAR_MAX_LEN) { + if (i + 1 >= chain_len) { + if (!dont_add_minus_end) + strcat(buf, "-"); + } else { + strcat(buf, " "); + } + len++; + } + } + + return buf; +} + +static const char* +current_keybind_options(struct chained_keybind* keybinds, int keybind_len) +{ + if (!keybind_len || !keybinds) + return "[no options]"; + static char buf[STATUS_BAR_MAX_LEN]; + strcpy(buf, "["); + int len = 0; + + for(int i = 0; i < keybind_len; i++) { + switch(keybinds[i].keysym) { + case XK_0: case XK_1: case XK_2: + case XK_3: case XK_4: case XK_5: + case XK_6: case XK_7: case XK_8: + case XK_9: + if (!keybinds[i].description) + continue; + } + + const char* str = get_ksym_string_no_xk(keybinds[i].keysym, keybinds[i].mod); + int new_len = strlen(str); + if (new_len + len < STATUS_BAR_MAX_LEN) { + strcat(buf, str); + len += new_len; + } + if (keybinds[i].description) { + new_len = strlen(keybinds[i].description) + sizeof(" → ") - 1; + if (new_len + len < STATUS_BAR_MAX_LEN) { + strcat(buf, " → "); + strcat(buf, keybinds[i].description); + len += new_len; + } + } + + if (i + 1 < keybind_len && len + 2 < STATUS_BAR_MAX_LEN) { + strcat(buf, ", "); + len += 2; + } + } + if (len + 1 < STATUS_BAR_MAX_LEN) + strcat(buf, "]"); + return buf; +} + +///////////////////////////////////////////////// +// callbacks +// + +#include "extensions/default_status_bar.h" +#include "extensions/window_modes/choose_one_of_selection.h" +#include "extensions/undo.h" +#include "extensions/keep_cursor_col.h" +#include "extensions/move_selection_with_cursor.h" +#include "extensions/startup_message.h" + +static int keypress_actions(KeySym keycode, int modkey, const char* buf, int len); +static int vim_paste_callback(struct file_buffer* fb, char* data, int len); +static const struct extension vim = { + .keypress = keypress_actions, + .fb_paste = vim_paste_callback, +}; + +struct extension_meta config_extensions[] = { + {.e = startup_message, .enabled = 1}, + {.e = file_browser, .enabled = 1}, + {.e = search_open_fb, .enabled = 1}, + {.e = search_keywords_open_fb, .enabled = 1}, + + {.e = syntax_e, .enabled = 1}, + + {.e = undo, .enabled = 1}, + {.e = keep_cursor_col, .enabled = 1}, + {.e = move_selection_with_cursor, .enabled = 1}, + {.e = vim, .enabled = 1}, + + {.e = default_status_bar, .enabled = 1}, + + {.end = 1} +}; + +struct extension_meta* extensions = config_extensions; + +static int +vim_paste_callback(struct file_buffer* fb, char* data, int len) +{ + int offset = focused_window->fb_index; + if (data[len-1] == '\n') { + if (paste_behind) { + offset = fb_seek_char_backwards(fb, offset, '\n'); + offset = MAX(offset, 0); + } else { + offset = fb_seek_char(fb, offset, '\n'); + if (offset < 0) + offset = fb->len-1; + else + offset++; + } + } else if (paste_behind) { + offset = MAX(offset - 1, 0); + } + fb_insert(fb, data, len, offset, 1); + window_node_move_all_cursors_on_same_fb(&root_node, (paste_behind) ? NULL : focused_node, focused_window->fb_index, offset, + wb_move_offset_relative, len, CURSOR_COMMAND_MOVEMENT); + paste_behind = 0; + return 0; +} + +static int +vim_repeat_last_command(int custom_mode) +{ + static int recursioin_prevention = 0; + if (recursioin_prevention || !previous_chain_len) + return -2; + recursioin_prevention = 1; + vim_change_mode(VIM_NORMAL); + + int count = vim_chain_parse_count(0); + while(count--) { + chain_len = 0; + last_used_command_index = 0; + for(int i = 0; i < previous_chain_len; i++) { + struct keypress_logg pc = previous_chain[i]; + keypress_actions(pc.ksym, pc.modkey, pc.buf, pc.len); + } + } + + vim_change_mode(VIM_NORMAL); + recursioin_prevention = 0; + writef_to_status_bar("repeated %d inputs", previous_chain_len); + return -2; +} + +void insert_string(const char* buf, int len) +{ + struct file_buffer* fb = get_fb(focused_window); + + if (buf[0] >= 32 || len > 1) { + fb_delete_selection(fb); + fb_insert(fb, buf, len, focused_window->cursor_offset, 0); + window_node_move_all_cursors_on_same_fb(&root_node, NULL, focused_window->fb_index, focused_window->cursor_offset, + wb_move_offset_relative, len, CURSOR_COMMAND_MOVEMENT); + } else { + writef_to_status_bar("unhandled control character 0x%x\n", buf[0]); + } +} + +int +keypress_actions(KeySym keycode, int modkey, const char* buf, int len) +{ + // current keybind that is set + static struct chained_keybind* keybinds; + static int keybind_len; + if (vim_mode == VIM_NORMAL) { + if (!keybinds) { + keybinds = normal_mode_keybinds; + keybind_len = LEN(normal_mode_keybinds); + } + } else if (vim_mode == VIM_INSERT) { + if (!keybinds) { + keybinds = insert_mode_keybinds; + keybind_len = LEN(insert_mode_keybinds); + } + } else if (vim_mode == VIM_VISUAL) { + if (!keybinds) { + keybinds = visual_mode_keybinds; + keybind_len = LEN(visual_mode_keybinds); + } + } + + char tmp_status_bar[STATUS_BAR_MAX_LEN]; + memcpy(tmp_status_bar, status_bar_contents, STATUS_BAR_MAX_LEN); + + int last_mode = vim_mode; + int res = do_chained_keybinds(&keybinds, &keybind_len, keycode, modkey, buf, len); + + int status_bar_changed = strcmp(tmp_status_bar, status_bar_contents) != 0; + + if (res == 3) + return 0; + if (res == 2) + last_used_command_index = MAX(0, chain_len - 1); + if (!res) { + if (vim_mode == VIM_INSERT) { + insert_string(buf, len); + } else { + if (vim_mode == VIM_NORMAL) { + const char* chain_str = current_key_chain_string(1); + writef_to_status_bar("keybind \"%s\" does not exist", chain_str); + + // reset chain + chain_len = 0; + last_used_command_index = 0; + } else { + writef_to_status_bar("keybind does not exist"); + } + } + } else if ((res == 2 || res == 4) && vim_mode == VIM_NORMAL) { + // reset chain + if (chain_len > 1 && !status_bar_changed && last_mode == vim_mode) { + const char* chain_str = current_key_chain_string(1); + if (chain_str) + writef_to_status_bar("%s", chain_str); + } + if (res != 4) { + vim_copy_log(&previous_chain, previous_chain_len, current_chain, chain_len); + previous_chain_len = chain_len; + } + chain_len = 0; + last_used_command_index = 0; + } else if (vim_mode == VIM_NORMAL) { + const char* chain_str = current_key_chain_string(0); + const char* options = current_keybind_options(keybinds, keybind_len); + if (chain_str && !status_bar_changed) + writef_to_status_bar("%s %s", chain_str, options); + } else { + const char* options = current_keybind_options(keybinds, keybind_len); + + if (chain_len > 0 && strcmp(options, "[no options]") != 0) { + const char* latest_keypress = get_ksym_string_no_xk(current_chain[chain_len - 1].ksym, current_chain[chain_len - 1].modkey); + writef_to_status_bar("%s- %s", latest_keypress, options); + } + } + return 0; +} + +// TODO: gg goes to line but not G diff --git a/extensions/startup_message.h b/extensions/startup_message.h @@ -3,7 +3,6 @@ static const char* const welcome[] = {"Welcome to se!", "Good day, Human", "Happy Coding", "se: the Simple Editor", "Time to get some progress done!", "Ready for combat", "Initialising...Done", "loaded in %%d seconds", - "Fun fact: vscode has over two times as many lines describing dependencies than se has in total", "You look based", "Another day, another bug to fix", "Who needs a mouse ¯\\_(ツ)_/¯", "grrgrrggghhaaaaaa (╯°□ °)╯︵ ┻━┻", "┬┴┬┤(・_├┬┴┬┴┬┴┬┤ʖ ͡°) ├┬┴┬┴┬┴┬┴┬┴┬┴┬┴┬┴┬┴┬┴", "ʰᵉˡˡᵒ"}; diff --git a/x.c b/x.c @@ -1610,15 +1610,12 @@ run(void) cresize(w, h); for (;;) { - struct timespec ts_start; - int xev = 0; while (XPending(xw.dpy)) { XNextEvent(xw.dpy, &ev); if (XFilterEvent(&ev, None)) continue; if (handler[ev.type]) { - clock_gettime(CLOCK_MONOTONIC, &ts_start); (handler[ev.type])(&ev); xev = 1; } @@ -1636,13 +1633,6 @@ run(void) xfinishdraw(); XFlush(xw.dpy); - - struct timespec ts_end; - clock_gettime(CLOCK_MONOTONIC, &ts_end); - double dif = 0; - dif = (ts_end.tv_sec - ts_start.tv_sec) * 1e6; - dif += (ts_end.tv_nsec - ts_start.tv_nsec) * 1e-3; - printf("%lf\n", dif); } }