se

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

config.c (55261B)


      1 #include "config.h"
      2 #include "extension.h"
      3 #include <ctype.h>
      4 
      5 #define MODKEY Mod1Mask
      6 
      7 // TODO: search hilight no longer works because syntax is ran after drawing search highlight
      8 
      9 ////////////////////////////////////////
     10 // apperance
     11 //
     12 
     13 // font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html
     14 char *fontconfig = "Liberation Mono:pixelsize=21:antialias=true:autohint=true";
     15 //char *fontconfig = "Iosevka:pixelsize=20:antialias=true:autohint=true";
     16 
     17 // pixels of border around the window
     18 int border_px = 2;
     19 
     20 // default size of the editor
     21 unsigned int default_cols = 80;
     22 unsigned int default_rows = 24;
     23 
     24 // Kerning / character bounding-box multipliers
     25 float cw_scale = 1.0;
     26 float ch_scale = 1.0;
     27 
     28 int wrap_buffer = 0;
     29 
     30 // spaces per tab (tabs will self align)
     31 unsigned int tabspaces = 8;
     32 unsigned int default_indent_len = 0; // 0 means tab
     33 
     34 // Default shape of cursor
     35 // 2: block ("█")
     36 // 4: underline ("_")
     37 // 6: bar ("|")
     38 // TODO: half_block
     39 // TODO: enums
     40 unsigned int cursor_shape = 2;
     41 
     42 // thickness of underline and bar cursors
     43 unsigned int cursor_thickness = 2;
     44 
     45 ///////////////////////////////////////////
     46 // custom window modes
     47 //
     48 
     49 enum window_modes {
     50 		WB_MODES_START = WB_MODES_DEFAULT_END,
     51 		#include "extensions/window_modes/choose_one_of_selection.enums"
     52 		WB_MODES_END
     53 };
     54 
     55 ///////////////////////////////////////////
     56 // color scheme
     57 // the syntax highlighting is applied in one pass,
     58 // so you can't have nested syntax highlighting
     59 //
     60 
     61 #include "extensions/syntax/syntax.h"
     62 
     63 #include "extensions/syntax/schemes/gruvbox.h"
     64 
     65 // disable coloring functions for the syntax schemes below
     66 #undef function_color
     67 //#define function_color normal_color
     68 
     69 #include "extensions/syntax/gd.h"
     70 #include "extensions/syntax/c.h"
     71 
     72 const struct syntax_scheme syntax[] = {
     73 		{".c", c_word_seperators, c_syntax, LEN(c_syntax), c_indent, LEN(c_indent)},
     74 		{".h", c_word_seperators, c_syntax, LEN(c_syntax), c_indent, LEN(c_indent)},
     75 		{".gd", gd_word_seperators, gd_syntax, LEN(gd_syntax), gd_indent, LEN(gd_indent)},
     76 
     77 		{0},
     78 };
     79 
     80 const struct syntax_scheme* syntax_schemes = syntax;
     81 
     82 /////////////////////////////////////////
     83 // Shortcuts
     84 //
     85 
     86 enum vim_modes {
     87 		VIM_NORMAL,
     88 		VIM_INSERT,
     89 		VIM_REPLACE,
     90 		VIM_SINGULAR_REPLACE,
     91 		VIM_VISUAL,
     92 		VIM_VISUAL_LINE,
     93 		VIM_VISUAL_BLOCK,
     94 };
     95 
     96 static int vim_mode = VIM_NORMAL;
     97 
     98 enum inside_around_delimiters {
     99 		VIM_INSIDE_ROUND_BRACKETS,
    100 		VIM_INSIDE_SQUARE_BRACKETS,
    101 		VIM_INSIDE_CURLY_BRACKETS,
    102 		VIM_INSIDE_ANGLE_BRACKET,
    103 		VIM_INSIDE_DOUBLE_QUOTES,
    104 		VIM_INSIDE_SINGLE_QUOTES,
    105 		VIM_INSIDE_BACK_QUOTES,
    106 		VIM_INSIDE_PARAGRAPH,
    107 
    108 		VIM_INSIDE_COUNT,
    109 		VIM_AROUND_START = VIM_INSIDE_COUNT,
    110 
    111 		VIM_AROUND_ROUND_BRACKETS = VIM_AROUND_START,
    112 		VIM_AROUND_SQUARE_BRACKETS,
    113 		VIM_AROUND_CURLY_BRACKETS,
    114 		VIM_AROUND_ANGLE_BRACKET,
    115 		VIM_AROUND_DOUBLE_QUOTES,
    116 		VIM_AROUND_SINGLE_QUOTES,
    117 		VIM_AROUND_BACK_QUOTES,
    118 		VIM_AROUND_PARAGRAPH,
    119 
    120 		VIM_AROUND_END = VIM_AROUND_PARAGRAPH,
    121 };
    122 
    123 _Static_assert(VIM_AROUND_END - VIM_AROUND_START + 1 == VIM_INSIDE_COUNT, "Bracket count does not match");
    124 
    125 
    126 const struct delimiter vim_delimiters[VIM_INSIDE_COUNT] = {
    127 		{"(", ")"},
    128 		{"[", "]"},
    129 		{"{", "}"},
    130 		{"<", ">"},
    131 		{"\"", "\""},
    132 		{"'", "'"},
    133 		{"`", "`"},
    134 		{"\n\n", "\n\n"}, // TODO: this works poorly
    135 };
    136 
    137 struct delimiter ignore_delimiters[] = {
    138 
    139 		// C comments
    140 		{"//", "\n"},
    141 		{"/*", "*/"},
    142 
    143 		// Python multiline strings
    144 		{"\"\"\"", "\"\"\""},
    145 
    146 		// Strings
    147 		{"\"", "\""},
    148 		{"'", "''"},
    149 		{"'", "''"},
    150 		{"`", "`"},
    151 
    152 		{0},
    153 };
    154 
    155 // TODO: create a "delete in comments" function that
    156 // selects around the current ignore delimiter
    157 
    158 enum misc_delimiters {
    159 		VIM_MISC_START = VIM_AROUND_END + 1,
    160 
    161 		VIM_CURRENT_WORD = VIM_MISC_START,
    162 		VIM_CURRENT_LINE,
    163 		//VIM_CURRENT_LINE_INSIDE,
    164 		VIM_CURRENT_WORD_AND_SURROUNDING_WHITESPACE,
    165 		VIM_NEXT_QUOUTE,
    166 
    167 		VIM_PREV_WORD_START,
    168 		VIM_TO_START_OF_WORD,
    169 		VIM_TO_END_OF_WORD,
    170 
    171 		VIM_PREV_STRING_START,
    172 		VIM_TO_START_OF_STRING,
    173 		VIM_TO_END_OF_STRING,
    174 
    175 		VIM_TO_START_OF_LINE,
    176 		VIM_TO_END_OF_INDENT,
    177 		VIM_TO_END_OF_LINE,
    178 		VIM_TO_START_OF_FILE,
    179 		VIM_TO_END_OF_FILE,
    180 
    181 		VIM_SAME_INDENT_PLUS,
    182 
    183 		VIM_CURRENT_SELECTION,
    184 
    185 		VIM_MISC_COUNT,
    186 };
    187 
    188 
    189 const char* vim_default_word_seperators  = "., '\n\t*+-/%!~<>=(){}[]\"^&|\\`´?:;";
    190 const char* vim_override_word_seperators = "., '\n\t*+-/%!~<>=(){}[]\"^&|\\`´?:;_";
    191 
    192 struct chained_keybind {
    193 		unsigned int mod;
    194 		KeySym keysym;
    195 		//  0 return value means it will keep this command level
    196 		// +1 return value means it will move into this commands next_keybinds
    197 		// +2 return value means it will continue looking another command
    198 		// -1 return value means it will reset the chained keybind
    199 		// -2 return value means it will reset the chained keybind (special case, e.g not reset state)
    200 		int(*func)(int custom_mode);
    201 		int custom_mode;
    202 		const char* description;
    203 		struct chained_keybind* next_keybinds;
    204 		int keybind_count;
    205 };
    206 
    207 #define CHAINED_KEYBIND_LEN 1024
    208 struct keypress_logg {
    209 		KeySym ksym;
    210 		unsigned int modkey;
    211 		int len;
    212 		char* buf;
    213 };
    214 struct keypress_logg* previous_chain;
    215 struct keypress_logg current_chain[CHAINED_KEYBIND_LEN] = {0};
    216 int previous_chain_len;
    217 int chain_len;
    218 
    219 int last_used_command_index;
    220 
    221 static void
    222 vim_copy_log(struct keypress_logg** dest, int dest_len, const struct keypress_logg* src, int src_len)
    223 {
    224 		*dest = xrealloc(*dest, src_len * sizeof(struct keypress_logg));
    225 		if (dest_len == 0)
    226 				(*dest)->buf = NULL;
    227 
    228 		int total_buf_len = 0;
    229 		for (int i = 0; i < src_len; i++)
    230 				total_buf_len += src[i].len;
    231 		char* chars = xrealloc((*dest)->buf, total_buf_len);
    232 
    233 		for (int i = 0; i < src_len; i++) {
    234 				struct keypress_logg* t = (*dest) + i;
    235 				struct keypress_logg s = src[i];
    236 				t->buf = chars;
    237 				memcpy(t->buf, s.buf, s.len);
    238 				t->ksym = s.ksym;
    239 				t->modkey = s.modkey;
    240 				t->len = s.len;
    241 				chars += s.len;
    242 		}
    243 }
    244 
    245 static int
    246 vim_chain_parse_count_raw()
    247 {
    248 		int last_ksym_was_num = 0;
    249 		int sum = 0;
    250 		int current_num = 0;
    251 		for (int i = last_used_command_index; i < chain_len; i++) {
    252 				int current_val;
    253 				switch(current_chain[i].ksym) {
    254 				case XK_0: current_val = 0; break;
    255 				case XK_1: current_val = 1; break;
    256 				case XK_2: current_val = 2; break;
    257 				case XK_3: current_val = 3; break;
    258 				case XK_4: current_val = 4; break;
    259 				case XK_5: current_val = 5; break;
    260 				case XK_6: current_val = 6; break;
    261 				case XK_7: current_val = 7; break;
    262 				case XK_8: current_val = 8; break;
    263 				case XK_9: current_val = 9; break;
    264 				default:   current_val =-1; break;
    265 				}
    266 				if (current_val < 0) {
    267 						if (last_ksym_was_num) {
    268 								sum += current_num;
    269 								current_num = 0;
    270 								last_ksym_was_num = 0;
    271 						}
    272 						continue;
    273 				}
    274 				if (last_ksym_was_num)
    275 						current_num *= 10;
    276 				else
    277 						last_ksym_was_num = 1;
    278 				current_num += current_val;
    279 		}
    280 		if (last_ksym_was_num)
    281 				sum += current_num;
    282 
    283 		return sum;
    284 }
    285 
    286 static int
    287 vim_chain_parse_count(int custom_mode)
    288 {
    289 		if (custom_mode == VIM_TO_END_OF_FILE || custom_mode == VIM_TO_START_OF_FILE)
    290 				return 1;
    291 		int sum = vim_chain_parse_count_raw();
    292 		if (sum == 0)
    293 				sum = 1;
    294 		return sum;
    295 }
    296 
    297 static int
    298 vim_get_delimiter(int delimiter_type, int offset, int* start, int* end)
    299 {
    300 		if (delimiter_type < 0)
    301 				return 0;
    302 		soft_assert(start, static int tmp; start = &tmp;);
    303 		soft_assert(end, static int tmp; end = &tmp;);
    304 
    305 		struct file_buffer* fb = get_fb(focused_window);
    306 		LIMIT(offset, 0, fb->len-1);
    307 
    308 		int count;
    309 
    310 		if (delimiter_type < VIM_MISC_START) {
    311 				int around = delimiter_type >= VIM_AROUND_START ? 1 : 0;
    312 				if (around)
    313 						delimiter_type -= VIM_AROUND_START;
    314 
    315 				soft_assert(delimiter_type < VIM_INSIDE_COUNT, return 0;);
    316 				if (!fb_get_delimiter(fb, offset, vim_delimiters[delimiter_type], ignore_delimiters, start, end)) {
    317 						int next_offset = fb_seek_string(fb, offset, vim_delimiters[delimiter_type].start);
    318 						if (next_offset < 0 || next_offset - offset >= 115)
    319 								return 0;
    320 						if (!fb_get_delimiter(fb, next_offset, vim_delimiters[delimiter_type], ignore_delimiters, start, end))
    321 								return 0;
    322 				}
    323 
    324 				if (!around)
    325 						*start += strlen(vim_delimiters[delimiter_type].start);
    326 				else
    327 						*end += strlen(vim_delimiters[delimiter_type].end);
    328 
    329 				return 1;
    330 		}
    331 
    332 
    333 #ifdef SYNTAX_H_
    334 		const struct syntax_scheme* cs = fb_get_syntax_scheme(fb);
    335 		const char* word_seperators = cs ? cs->word_seperators : vim_default_word_seperators;
    336 #else
    337 		const char* word_seperators = vim_default_word_seperators;
    338 #endif
    339 		if (vim_override_word_seperators)
    340 				word_seperators = vim_override_word_seperators;
    341 
    342 		switch(delimiter_type) {
    343 		case VIM_CURRENT_WORD:
    344 				if (isspace(fb->contents[offset])) {
    345 						int not_whitespace = fb_seek_not_whitespace_backwards(fb, offset);
    346 						if (not_whitespace >= 0)
    347 								not_whitespace += 1;
    348 						int line_start = fb_seek_char_backwards(fb, offset, '\n');
    349 						*start = MAX(not_whitespace, line_start);
    350 						not_whitespace = fb_seek_not_whitespace(fb, offset);
    351 						line_start = fb_seek_char(fb, offset, '\n');
    352 						*end = MIN(not_whitespace, line_start);
    353 						if (*end < 0)
    354 								*end = MAX(not_whitespace, line_start);
    355 				} else if (fb_is_on_a_word(fb, offset, word_seperators)) {
    356 						*start = fb_seek_start_of_word_backwards(fb, offset, word_seperators);
    357 						*end = fb_seek_word_end(fb, offset, word_seperators);
    358 				} else {
    359 						const char* word_seperators_no_newline = "., '\t*+-/%!~<>=(){}[]\"^&|\\`´?:;";
    360 						*start = fb_seek_word_backwards(fb, offset, word_seperators_no_newline);
    361 						if (*start >= 0)
    362 								*start += 1;
    363 						*end = fb_seek_word(fb, offset, word_seperators_no_newline);
    364 				}
    365 				if (*start < 0 || *end < 0)
    366 						return 0;
    367 				return 1;
    368 		case VIM_CURRENT_LINE:
    369 				*start = fb_seek_char_backwards(fb, offset, '\n');
    370 				*end = fb_seek_char(fb, offset, '\n');
    371 				if (*start < 0)
    372 						*start = 0;
    373 				if (*end < 0)
    374 						*end = fb->len-1;
    375 				if (*end > 0)
    376 						*end += 1;
    377 				return 1;
    378 		case VIM_TO_END_OF_LINE:
    379 				*end = fb_seek_char(fb, offset, '\n');
    380 				if (*end < 0)
    381 						return 0;
    382 				*start = offset;
    383 				return 1;
    384 		case VIM_TO_START_OF_LINE:
    385 				*start = fb_seek_char_backwards(fb, offset-1, '\n');
    386 				if (*start < 0)
    387 						*start = 0;
    388 				*end = offset;
    389 				return 1;
    390 		case VIM_TO_END_OF_INDENT:
    391 				*start = fb_seek_char_backwards(fb, offset, '\n');
    392 				if (*start < 0)
    393 						*start = 0;
    394 				if (*start + 1 < fb->len-1 && isspace(fb->contents[*start + 1])) {
    395 						int not_whitespace = fb_seek_not_whitespace(fb, *start + 1);
    396 						int line_end = fb_seek_char(fb, *start + 1, '\n');
    397 						if (line_end < not_whitespace)
    398 								return 0;
    399 						if (not_whitespace >= 0)
    400 								*start = not_whitespace;
    401 				}
    402 				*end = offset;
    403 				return 1;
    404 		case VIM_TO_START_OF_FILE:
    405 				count = vim_chain_parse_count_raw();
    406 				if (count) {
    407 						count--;
    408 						int old_offset = focused_window->cursor_offset;
    409 
    410 						focused_window->cursor_offset = 0;
    411 						wb_move_lines(focused_window, count, 0);
    412 						int new_offset = fb_seek_char_backwards(fb, focused_window->cursor_offset, '\n');
    413 						if (new_offset < 0)
    414 								new_offset = focused_window->cursor_offset;
    415 
    416 						focused_window->cursor_offset = old_offset;
    417 
    418 						if (new_offset < old_offset) {
    419 								*start = new_offset;
    420 								*end = old_offset;
    421 						} else {
    422 								*end = new_offset;
    423 								*start = old_offset;
    424 						}
    425 						printf("%d %d\n", *start, *end);
    426 				} else {
    427 						*start = 0;
    428 						*end = offset;
    429 				}
    430 				return 1;
    431 		case VIM_TO_END_OF_FILE:
    432 				count = vim_chain_parse_count_raw();
    433 				if (count) {
    434 						count--;
    435 						int old_offset = focused_window->cursor_offset;
    436 						focused_window->cursor_offset = 0;
    437 						wb_move_lines(focused_window, count, 0);
    438 						focused_window->cursor_offset = old_offset;
    439 						int new_offset = fb_seek_char_backwards(fb, focused_window->cursor_offset, '\n');
    440 						if (new_offset < old_offset) {
    441 								*start = new_offset;
    442 								*end = old_offset;
    443 						} else {
    444 								*end = new_offset;
    445 								*start = old_offset;
    446 						}
    447 				} else {
    448 						*start = offset;
    449 						*end = fb->len-1;
    450 				}
    451 				return 1;
    452 		case VIM_TO_START_OF_WORD:
    453 				*end = fb_seek_word(fb, offset, word_seperators);
    454 				*start = offset;
    455 				if (*end < 0)
    456 						return 0;
    457 				return 1;
    458 		case VIM_PREV_WORD_START:
    459 				*start = fb_seek_start_of_word_backwards(fb, offset-1, word_seperators);
    460 				*end = offset;
    461 				if (*start < 0)
    462 						return 0;
    463 				return 1;
    464 		case VIM_PREV_STRING_START:
    465 				*end = offset;
    466 				offset--;
    467 				if (isspace(fb->contents[offset]))
    468 						offset = fb_seek_not_whitespace_backwards(fb, offset);
    469 				*start = fb_seek_whitespace_backwards(fb, offset-1);
    470 				if (*start < 0)
    471 						return 0;
    472 				*start += 1;
    473 				return 1;
    474 		case VIM_TO_END_OF_WORD:
    475 				*end = fb_seek_word_end(fb, offset, word_seperators);
    476 				*start = offset;
    477 				if (*end < 0)
    478 						return 0;
    479 				return 1;
    480 		case VIM_TO_START_OF_STRING:
    481 				*start = offset;
    482 				if (!isspace(fb->contents[offset]))
    483 						offset = fb_seek_whitespace(fb, offset);
    484 				*end = fb_seek_not_whitespace(fb, offset);
    485 				if (*end < 0)
    486 						return 0;
    487 				return 1;
    488 		case VIM_TO_END_OF_STRING:
    489 				*start = offset;
    490 				if (isspace(fb->contents[offset]))
    491 						offset = fb_seek_not_whitespace(fb, offset);
    492 				*end = fb_seek_whitespace(fb, offset);
    493 				if (*end < 0)
    494 						return 0;
    495 				return 1;
    496 		case VIM_CURRENT_SELECTION:
    497 				if (fb_is_selection_start_top_left(fb)) {
    498 						*start = fb->s1o;
    499 						*end = fb->s2o + 1;
    500 				} else {
    501 						*start = fb->s2o;
    502 						*end = fb->s1o + 1;
    503 				}
    504 				return 1;
    505 		default:
    506 				return 0;
    507 		}
    508 }
    509 
    510 static int
    511 vim_change_mode(int custom_mode)
    512 {
    513 		int previous_mode = vim_mode;
    514 
    515 		if (custom_mode == VIM_NORMAL) {
    516 				if (vim_mode == VIM_INSERT)
    517 						wb_move_on_line(focused_window, -1, CURSOR_COMMAND_MOVEMENT);
    518 				struct file_buffer* fb = get_fb(focused_window);
    519 				cursor_shape = 2;
    520 				fb->mode &= ~FB_SELECT_MASK;
    521 				fb->mode &= ~FB_SEARCH_BLOCKING_MASK;
    522 				fb->mode &= ~FB_SEARCH_NON_BLOCKING;
    523 				fb->mode &= ~FB_SEARCH_NON_BLOCKING_BACKWARDS;
    524 				fb->mode &= ~FB_SEARCH_BLOCKING_BACKWARDS;
    525 				writef_to_status_bar("Escape");
    526 		} else if (custom_mode == VIM_INSERT) {
    527 				cursor_shape = 6;
    528 				writef_to_status_bar("-- INSERT --");
    529 		} else if (custom_mode == VIM_REPLACE) {
    530 				cursor_shape = 4;
    531 		} else if (custom_mode == VIM_VISUAL || VIM_VISUAL_LINE) {
    532 				struct file_buffer* fb = get_fb(focused_window);
    533 				fb->mode |= FB_SELECTION_ON;
    534 				if (custom_mode == VIM_VISUAL) {
    535 						fb->s1o = fb->s2o = focused_window->cursor_offset;
    536 						writef_to_status_bar("-- VISUAL --");
    537 				} else if (custom_mode == VIM_VISUAL_LINE) {
    538 						fb->s1o = fb_seek_char(fb, focused_window->cursor_offset, '\n');
    539 						fb->s2o = fb_seek_char_backwards(fb, focused_window->cursor_offset, '\n');
    540 						fb->mode |= FB_LINE_SELECT;
    541 						writef_to_status_bar("-- VISUAL LINE --");
    542 
    543 						custom_mode = VIM_VISUAL;
    544 				}
    545 		}
    546 		vim_mode = custom_mode;
    547 		if (custom_mode == VIM_VISUAL || (vim_mode == previous_mode))
    548 				return -2;
    549 		return -1;
    550 }
    551 
    552 static int
    553 vim_exit(int custom_mode) {
    554 		exit(0);
    555 }
    556 
    557 static int
    558 vim_move_on_line(int custom_mode)
    559 {
    560 		if (focused_window->mode != WB_FILE_BROWSER)
    561 				wb_move_on_line(focused_window, custom_mode * vim_chain_parse_count(0), CURSOR_RIGHT_LEFT_MOVEMENT);
    562 		return -2;
    563 }
    564 
    565 static int
    566 vim_move_lines(int custom_mode)
    567 {
    568 		wb_move_lines(focused_window, custom_mode * vim_chain_parse_count(0), 0);
    569 		wb_move_to_x(focused_window, focused_window->cursor_col, CURSOR_UP_DOWN_MOVEMENT);
    570 		return -2;
    571 }
    572 
    573 static int
    574 vim_change_window(int custom_mode)
    575 {
    576 		int count = vim_chain_parse_count(0);
    577 		while(count--) {
    578 				focused_node = window_switch_to_window(focused_node, custom_mode);
    579 				focused_window = &focused_node->wb;
    580 		}
    581 		return -2;
    582 }
    583 
    584 static int
    585 vim_resize_window(int custom_mode)
    586 {
    587 		float amount = (custom_mode == MOVE_RIGHT || custom_mode == MOVE_LEFT) ? 0.1f : 0.05f;
    588 		window_node_resize(focused_node, custom_mode, amount * vim_chain_parse_count(0));
    589 		return -2;
    590 }
    591 
    592 static int
    593 vim_swap_to_next_file_buffer(int custom_mode)
    594 {
    595 		focused_window->fb_index += vim_chain_parse_count(0);
    596 		return -2;
    597 }
    598 
    599 static int
    600 vim_split_window(int custom_mode)
    601 {
    602 		int count = vim_chain_parse_count(0);
    603 		while(count--) {
    604 				window_node_split(focused_node, 0.5, custom_mode);
    605 #if 1
    606 				if (focused_node->node2) {
    607 						focused_node = focused_node->node2;
    608 						focused_window = &focused_node->wb;
    609 				}
    610 #else
    611 				if (focused_node->node1) {
    612 						focused_node = focused_node->node1;
    613 						focused_window = &focused_node->wb;
    614 				}
    615 #endif
    616 		}
    617 		return -2;
    618 }
    619 
    620 static int
    621 vim_delete_window(int custom_mode)
    622 {
    623 		int count = vim_chain_parse_count(0);
    624 		while(count--) {
    625 				struct window_split_node* new_node = window_node_delete(focused_node);
    626 				while (new_node->mode != WINDOW_SINGULAR)
    627 						new_node = new_node->node1;
    628 				focused_node = new_node;
    629 				focused_window = &focused_node->wb;
    630 		}
    631 		return -2;
    632 }
    633 
    634 static int
    635 vim_kill_buffer(int custom_mode)
    636 {
    637 		int count = vim_chain_parse_count(0);
    638 		while(count--)
    639 				destroy_fb_entry(focused_node, &root_node);
    640 		return -2;
    641 }
    642 
    643 static int
    644 vim_save_buffer(int custom_mode)
    645 {
    646 		fb_write_to_filepath(get_fb(focused_window));
    647 		return -2;
    648 }
    649 
    650 static int
    651 vim_zoomabs(int custom_mode)
    652 {
    653 		xunloadfonts();
    654 		xloadfonts(fontconfig, custom_mode);
    655 		cresize(0, 0);
    656 		xhints();
    657 		return -2;
    658 }
    659 
    660 static int
    661 vim_zoom(int custom_mode)
    662 {
    663 		vim_zoomabs(usedfontsize + custom_mode);
    664 		return -2;
    665 }
    666 
    667 static int
    668 vim_zoomreset(int custom_mode)
    669 {
    670 		if (defaultfontsize > 0)
    671 				vim_zoomabs(defaultfontsize);
    672 		return -2;
    673 }
    674 
    675 static int
    676 vim_zero(int custom_mode)
    677 {
    678 		if (vim_chain_parse_count_raw())
    679 				return 0;
    680 		return 2;
    681 }
    682 
    683 static int
    684 vim_yank(int custom_mode)
    685 {
    686 		struct file_buffer* fb = get_fb(focused_window);
    687 		char* buf;
    688 		int len;
    689 		if (custom_mode == VIM_CURRENT_SELECTION || fb->mode & FB_SELECTION_ON) {
    690 				buf = fb_get_selection(fb, &len);
    691 				wb_move_cursor_to_selection_start(focused_window);
    692 				fb->mode &= ~FB_SELECT_MASK;
    693 				vim_change_mode(VIM_NORMAL);
    694 		} else {
    695 				int start, end;
    696 				if (!vim_get_delimiter(custom_mode, focused_window->cursor_offset, &start, &end)) {
    697 						writef_to_status_bar("unable to yank area");
    698 						return -1;
    699 				}
    700 				buf = fb_get_string_between_offsets(fb, start, end);
    701 				len = end - start;
    702 		}
    703 		set_clipboard_copy(buf, len);
    704 		writef_to_status_bar("area yanked");
    705 		return -2;
    706 }
    707 
    708 static int paste_behind = 0;
    709 
    710 static int
    711 vim_paste(int custom_mode)
    712 {
    713 		custom_mode = paste_behind;
    714 		execute_clipbaord_event();
    715 		return -1;
    716 }
    717 
    718 static int
    719 vim_auto_indent_current_line(int custom_mode)
    720 {
    721 		struct file_buffer* fb = get_fb(focused_window);
    722 		focused_window->cursor_offset = fb_seek_char_backwards(fb, focused_window->cursor_offset, '\n');
    723 		int indent_size = fb_auto_indent(fb, focused_window->cursor_offset);
    724 
    725 		focused_window->cursor_offset = MIN(fb_seek_not_whitespace(fb, focused_window->cursor_offset),
    726 											fb_seek_char(fb, focused_window->cursor_offset, '\n'));
    727 
    728 		window_node_move_all_cursors_on_same_fb(&root_node, focused_node, focused_window->fb_index, focused_window->cursor_offset,
    729 												wb_move_offset_relative, indent_size, CURSOR_COMMAND_MOVEMENT);
    730 		return -2;
    731 }
    732 
    733 static int
    734 vim_open_file_browser(int custom_mode)
    735 {
    736 		int last_fb = focused_window->fb_index;
    737 		struct file_buffer* fb = get_fb(focused_window);
    738 
    739 		char* path = file_path_get_path(fb->file_path);
    740 		*focused_window = wb_new(fb_new_entry(path));
    741 		focused_window->cursor_col = last_fb;
    742 		free(path);
    743 		return -1;
    744 }
    745 
    746 int
    747 vim_search_for_buffer(int custom_mode)
    748 {
    749 		if (focused_window->mode != WB_NORMAL)
    750 				return -2;
    751 		*focused_node->search = 0;
    752 		focused_node->selected = 0;
    753 		focused_window->mode = WB_SEARCH_FOR_BUFFERS;
    754 		return -2;
    755 }
    756 
    757 int
    758 vim_search_keyword_in_buffers(int custom_mode)
    759 {
    760 		if (focused_window->mode != WB_NORMAL)
    761 				return -2;
    762 		*focused_node->search = 0;
    763 		focused_node->selected = 0;
    764 		focused_window->mode = WB_SEARCH_KEYWORD_ALL_BUFFERS;
    765 		return -2;
    766 }
    767 
    768 static int
    769 vim_delete(int custom_mode)
    770 {
    771 		int count = vim_chain_parse_count(custom_mode);
    772 		while (count--) {
    773 				int start, end;
    774 				if (!vim_get_delimiter(custom_mode, focused_window->cursor_offset, &start, &end)) {
    775 						writef_to_status_bar("unable to find section to delete");
    776 						return -1;
    777 				}
    778 				if (start - end == 0)
    779 						return -2;
    780 				struct file_buffer* fb = get_fb(focused_window);
    781 				fb_remove(fb, start, end - start, 1, 1);
    782 				call_extension(fb_contents_updated, fb, focused_window->cursor_offset, FB_CONTENT_BIG_CHANGE);
    783 				wb_move_to_offset(focused_window, start, CURSOR_COMMAND_MOVEMENT);
    784 				window_node_move_all_cursors_on_same_fb(&root_node, focused_node, focused_window->fb_index, end,
    785 														wb_move_offset_relative, end - start, CURSOR_COMMAND_MOVEMENT);
    786 				if (custom_mode == VIM_CURRENT_SELECTION) {
    787 						vim_change_mode(VIM_NORMAL);
    788 						break;
    789 				}
    790 		}
    791 		return -1;
    792 }
    793 
    794 static int
    795 vim_change(int custom_mode)
    796 {
    797 		int count = vim_chain_parse_count(custom_mode);
    798 		while (count--) {
    799 				int start, end;
    800 				if (!vim_get_delimiter(custom_mode, focused_window->cursor_offset, &start, &end)) {
    801 						writef_to_status_bar("unable to find section to change");
    802 						return -1;
    803 				}
    804 				if (start - end == 0)
    805 						return -2;
    806 				struct file_buffer* fb = get_fb(focused_window);
    807 				fb_remove(fb, start, end - start, 1, 1);
    808 				call_extension(fb_contents_updated, fb, focused_window->cursor_offset, FB_CONTENT_BIG_CHANGE);
    809 				wb_move_to_offset(focused_window, start, CURSOR_COMMAND_MOVEMENT);
    810 				window_node_move_all_cursors_on_same_fb(&root_node, focused_node, focused_window->fb_index, end,
    811 														wb_move_offset_relative, end - start, CURSOR_COMMAND_MOVEMENT);
    812 				if (custom_mode == VIM_CURRENT_SELECTION)
    813 						break;
    814 
    815 		}
    816 		vim_change_mode(VIM_INSERT);
    817 		return -1;
    818 }
    819 
    820 static int
    821 vim_visual_delimit(int custom_mode)
    822 {
    823 		int start, end;
    824 		if (!vim_get_delimiter(custom_mode, focused_window->cursor_offset, &start, &end)) {
    825 				writef_to_status_bar("unable to find section to highlight");
    826 				return -1;
    827 		}
    828 
    829 		end--;
    830 		struct file_buffer* fb = get_fb(focused_window);
    831 		fb->mode |= FB_SELECTION_ON;
    832 		fb->mode &= ~FB_SELECT_MODE_MASK;
    833 		writef_to_status_bar("-- VISUAL --");
    834 
    835 		if (end == focused_window->cursor_offset) {
    836 				fb->s1o = end;
    837 				fb->s2o = start;
    838 
    839 				focused_window->cursor_offset = start;
    840 				return -1;
    841 		}
    842 
    843 		fb->s1o = start;
    844 		fb->s2o = end;
    845 
    846 		focused_window->cursor_offset = end;
    847 		return -2;
    848 }
    849 
    850 static int vim_enter(int custom_mode) {return 1;}
    851 
    852 static int
    853 vim_append(int custom_mode)
    854 {
    855 		vim_change_mode(VIM_INSERT);
    856 		wb_move_on_line(focused_window, 1, CURSOR_COMMAND_MOVEMENT);
    857 		return -1;
    858 }
    859 
    860 static int
    861 vim_insert_start(int custom_mode)
    862 {
    863 		vim_change_mode(VIM_INSERT);
    864 		int start, tmp;
    865 		if (vim_get_delimiter(VIM_TO_END_OF_INDENT, focused_window->cursor_offset, &start, &tmp))
    866 				wb_move_to_offset(focused_window, start, CURSOR_COMMAND_MOVEMENT);
    867 		return -1;
    868 }
    869 
    870 static int
    871 vim_insert_end(int custom_mode)
    872 {
    873 		vim_change_mode(VIM_INSERT);
    874 		int end, tmp;
    875 		if (vim_get_delimiter(VIM_TO_END_OF_LINE, focused_window->cursor_offset, &tmp, &end))
    876 				wb_move_to_offset(focused_window, end, CURSOR_COMMAND_MOVEMENT);
    877 		return -1;
    878 }
    879 
    880 static int
    881 vim_insert_new_line(int custom_mode)
    882 {
    883 		vim_change_mode(VIM_INSERT);
    884 		struct file_buffer* fb = get_fb(focused_window);
    885 
    886 		int offset = focused_window->cursor_offset;
    887 		offset = fb_seek_char(fb, offset, '\n');
    888 		if (offset < 0)
    889 				offset = fb->len-1;
    890 		focused_window->cursor_offset = offset;
    891 		wb_move_offset_relative(focused_window, 1, CURSOR_COMMAND_MOVEMENT);
    892 
    893 		fb_insert(fb, "\n", 1, offset, 0);
    894 		window_node_move_all_cursors_on_same_fb(&root_node, focused_node, focused_window->fb_index, focused_window->cursor_offset,
    895 												wb_move_offset_relative, 1, CURSOR_COMMAND_MOVEMENT);
    896 		vim_auto_indent_current_line(0);
    897 
    898 		return -1;
    899 }
    900 
    901 static int
    902 vim_remove_newline_at_end(int custom_mode)
    903 {
    904 		int count = vim_chain_parse_count(0);
    905 		struct file_buffer* fb = get_fb(focused_window);
    906 		while (count--) {
    907 				int offset = fb_seek_char(fb, focused_window->cursor_offset, '\n');
    908 				fb_remove(fb, offset, 1, 1, 0);
    909 				wb_move_to_offset(focused_window, offset, CURSOR_COMMAND_MOVEMENT);
    910 				window_node_move_all_cursors_on_same_fb(&root_node, focused_node, focused_window->fb_index, offset,
    911 														wb_move_offset_relative, -1, CURSOR_COMMAND_MOVEMENT);
    912 
    913 				LIMIT(offset, 0, fb->len);
    914 				if (isspace(fb->contents[offset])) {
    915 						int start, end;
    916 						int not_whitespace = fb_seek_not_whitespace_backwards(fb, offset);
    917 						if (not_whitespace >= 0)
    918 								not_whitespace += 1;
    919 						int line_start = fb_seek_char_backwards(fb, offset, '\n');
    920 						start = MAX(not_whitespace, line_start);
    921 
    922 						not_whitespace = fb_seek_not_whitespace(fb, offset);
    923 						line_start = fb_seek_char(fb, offset, '\n');
    924 						end = MIN(not_whitespace, line_start);
    925 						if (end < 0)
    926 								end = MAX(not_whitespace, line_start);
    927 
    928 						if (end < 0 || start < 0)
    929 								return -1;
    930 
    931 						if (fb->contents[offset] != '\n') {
    932 								fb_remove(fb, start, end - start, 1, 0);
    933 								fb_insert(fb, " ", 1, start, 1);
    934 								start++;
    935 								wb_move_to_offset(focused_window, start, CURSOR_COMMAND_MOVEMENT);
    936 								window_node_move_all_cursors_on_same_fb(&root_node, focused_node, focused_window->fb_index, end,
    937 																		wb_move_offset_relative, end - start, CURSOR_COMMAND_MOVEMENT);
    938 						}
    939 				} else {
    940 						fb_insert(fb, " ", 1, offset, 1);
    941 				}
    942 		}
    943 		return -1;
    944 }
    945 
    946 static int
    947 vim_undo(int custom_mode)
    948 {
    949 		int count = vim_chain_parse_count(0);
    950 		while (count--)
    951 				fb_undo(get_fb(focused_window));
    952 		return -2;
    953 }
    954 
    955 static int
    956 vim_redo(int custom_mode)
    957 {
    958 		int count = vim_chain_parse_count(0);
    959 		while (count--)
    960 				fb_redo(get_fb(focused_window));
    961 		return -2;
    962 }
    963 
    964 static int search_reversed = 0;
    965 static int vim_next(int custom_mode);
    966 static int vim_prev(int custom_mode);
    967 
    968 int
    969 vim_next(int custom_mode)
    970 {
    971 		if (!custom_mode && search_reversed)
    972 				return vim_prev(1);
    973 
    974 		get_fb(focused_window)->mode |= FB_SEARCH_BLOCKING_IDLE;
    975 		int new_offset = wb_seek_string_wrap(focused_window, focused_window->cursor_offset+1,
    976 											 get_fb(focused_window)->search_term);
    977 		if (new_offset < 0) {
    978 				writef_to_status_bar("no results for \"%s\"", get_fb(focused_window)->search_term);
    979 				return -1;
    980 		} else if (focused_window->cursor_offset > new_offset) {
    981 				writef_to_status_bar("search wrapped");
    982 		}
    983 		focused_window->cursor_offset = new_offset;
    984 		return -2;
    985 }
    986 
    987 int
    988 vim_prev(int custom_mode)
    989 {
    990 		if (!custom_mode && search_reversed)
    991 				return vim_next(1);
    992 
    993 		get_fb(focused_window)->mode |= FB_SEARCH_BLOCKING_IDLE;
    994 		int new_offset = wb_seek_string_wrap_backwards(focused_window, focused_window->cursor_offset-1,
    995 													   get_fb(focused_window)->search_term);
    996 		if (new_offset < 0) {
    997 				writef_to_status_bar("no results for \"%s\"", get_fb(focused_window)->search_term);
    998 				return -1;
    999 		} else if (focused_window->cursor_offset < new_offset) {
   1000 				writef_to_status_bar("search wrapped");
   1001 		}
   1002 		focused_window->cursor_offset = new_offset;
   1003 		return -2;
   1004 }
   1005 
   1006 static int
   1007 vim_search(int custom_mode)
   1008 {
   1009 		search_reversed = custom_mode;
   1010 		get_fb(focused_window)->mode &= ~FB_SEARCH_BLOCKING_IDLE;
   1011 		get_fb(focused_window)->mode |= FB_SEARCH_BLOCKING;
   1012 		writef_to_status_bar("search: %s", get_fb(focused_window)->search_term);
   1013 		return -2;
   1014 }
   1015 
   1016 static int
   1017 vim_home(int custom_mode)
   1018 {
   1019 		struct file_buffer* fb = get_fb(focused_window);
   1020 		int new_offset = fb_seek_char_backwards(fb, focused_window->cursor_offset, '\n');
   1021 		if (new_offset < 0)
   1022 				new_offset = 0;
   1023 		wb_move_to_offset(focused_window, new_offset, CURSOR_COMMAND_MOVEMENT);
   1024 		return -2;
   1025 }
   1026 
   1027 static int
   1028 vim_end(int custom_mode)
   1029 {
   1030 		struct file_buffer* fb = get_fb(focused_window);
   1031 		int new_offset = fb_seek_char(fb, focused_window->cursor_offset, '\n');
   1032 		if (new_offset < 0)
   1033 				new_offset = fb->len-1;
   1034 		wb_move_to_offset(focused_window, new_offset, CURSOR_COMMAND_MOVEMENT);
   1035 		return -2;
   1036 }
   1037 
   1038 static int
   1039 vim_move_down_one_screen_devided_by(int custom_mode)
   1040 {
   1041 		soft_assert(custom_mode, return -2;);
   1042 		int count = vim_chain_parse_count(0);
   1043 
   1044 		wb_move_lines(focused_window, ((focused_node->maxy - focused_node->miny) / custom_mode) * count, 0);
   1045 		wb_move_to_x(focused_window, focused_window->cursor_col, CURSOR_UP_DOWN_MOVEMENT);
   1046 		focused_window->y_scroll += ((focused_node->maxy - focused_node->miny) / custom_mode) * count;
   1047 		return -2;
   1048 }
   1049 
   1050 static int
   1051 vim_move_scroll(int custom_mode)
   1052 {
   1053 		int count = vim_chain_parse_count(0);
   1054 		focused_window->y_scroll += custom_mode * count;
   1055 		return -2;
   1056 }
   1057 
   1058 static int
   1059 vim_center_scroll(int custom_mode)
   1060 {
   1061 		struct file_buffer* fb = get_fb(focused_window);
   1062 		int tmp, y;
   1063 		fb_offset_to_xy(fb, focused_window->cursor_offset, focused_node->maxx, focused_window->y_scroll, &tmp, &y, &tmp);
   1064 		focused_window->y_scroll += y - ((focused_node->maxy - focused_node->miny) / 2);
   1065 		return -2;
   1066 }
   1067 
   1068 // TODO: make (x/delete) remove something to the on it when it's next to new line
   1069 // instead of doing nothing
   1070 static int
   1071 vim_remove_one_char(int custom_mode)
   1072 {
   1073 		struct window_split_node* excluded = (custom_mode == 0) ? focused_node : NULL;
   1074 
   1075 		int old_offset = focused_window->cursor_offset;
   1076 		if (custom_mode)
   1077 				wb_move_on_line(focused_window, custom_mode, CURSOR_DO_NOT_CALLBACK);
   1078 		int offset = focused_window->cursor_offset;
   1079 		focused_window->cursor_offset = old_offset;
   1080 
   1081 		int times = vim_chain_parse_count(0);
   1082 		struct file_buffer* fb = get_fb(focused_window);
   1083 		while (times-- && fb->contents[offset] != '\n') {
   1084 				int len = fb_remove(fb, offset, 1, 0, 0);
   1085 				window_node_move_all_cursors_on_same_fb(&root_node, excluded, focused_window->fb_index, offset,
   1086 														wb_move_offset_relative, -len, CURSOR_COMMAND_MOVEMENT);
   1087 		}
   1088 		return -1;
   1089 }
   1090 
   1091 static int
   1092 vim_backspace(int custom_mode)
   1093 {
   1094 		struct file_buffer* fb = get_fb(focused_window);
   1095 		int offset = focused_window->cursor_offset-1;
   1096 		if (offset <= 0 || offset >= fb->len)
   1097 				return -1;
   1098 		if (fb->contents[offset] == '\n') {
   1099 				fb_remove(fb, offset, 1, 1, 0);
   1100 				window_node_move_all_cursors_on_same_fb(&root_node, NULL, focused_window->fb_index, offset,
   1101 														wb_move_offset_relative, -1, CURSOR_COMMAND_MOVEMENT);
   1102 		} else {
   1103 				vim_remove_one_char(-1);
   1104 		}
   1105 		return -1;
   1106 }
   1107 
   1108 static int
   1109 vim_insert_return(int custom_mode)
   1110 {
   1111 		struct file_buffer* fb = get_fb(focused_window);
   1112 		int offset = focused_window->cursor_offset;
   1113 
   1114 		fb_insert(fb, "\n", 1, offset, 0);
   1115 		int indent_size = fb_auto_indent(fb, offset);
   1116 		window_node_move_all_cursors_on_same_fb(&root_node, NULL, focused_window->fb_index, offset,
   1117 												wb_move_offset_relative, indent_size + 1, 0);
   1118 		indent_size = fb_auto_indent(fb, offset + 1 + indent_size);
   1119 		window_node_move_all_cursors_on_same_fb(&root_node, NULL, focused_window->fb_index, offset,
   1120 												wb_move_offset_relative, indent_size, CURSOR_COMMAND_MOVEMENT);
   1121 		window_node_move_all_yscrolls(&root_node, focused_node, focused_window->fb_index, focused_window->cursor_offset, 1);
   1122 		return -1;
   1123 }
   1124 
   1125 static int
   1126 vim_insert_tab(int custom_mode)
   1127 {
   1128 		int offset = focused_window->cursor_offset;
   1129 		struct file_buffer* fb = get_fb(focused_window);
   1130 
   1131 		fb_insert(fb, "\t", 1, offset, 0);
   1132 		window_node_move_all_cursors_on_same_fb(&root_node, NULL, focused_window->fb_index, offset,
   1133 												wb_move_on_line, 1, CURSOR_COMMAND_MOVEMENT);
   1134 		return -1;
   1135 }
   1136 
   1137 static int
   1138 vim_move(int custom_mode)
   1139 {
   1140 		int count = vim_chain_parse_count(custom_mode);
   1141 		struct file_buffer* fb = get_fb(focused_window);
   1142 		while(count--) {
   1143 				int offset = focused_window->cursor_offset;
   1144 				if (custom_mode != VIM_PREV_STRING_START && custom_mode != VIM_PREV_WORD_START && offset + 1 < fb->len)
   1145 						offset++;
   1146 				int start, end;
   1147 				if(!vim_get_delimiter(custom_mode, offset, &start, &end))
   1148 						return -1;
   1149 				int other;
   1150 				if (abs(offset - start) > abs(offset - end))
   1151 						other = start;
   1152 				else
   1153 						other = end;
   1154 				wb_move_to_offset(focused_window, other, CURSOR_COMMAND_MOVEMENT);
   1155 		}
   1156 		return -2;
   1157 }
   1158 
   1159 static int vim_repeat_last_command(int custom_mode);
   1160 
   1161 #define numbers()								\
   1162 		{0, XK_0, vim_zero},					\
   1163 	{0, XK_1},									\
   1164 	{0, XK_2},									\
   1165 	{0, XK_3},									\
   1166 	{0, XK_4},									\
   1167 	{0, XK_5},									\
   1168 	{0, XK_6},									\
   1169 	{0, XK_7},									\
   1170 	{0, XK_8},									\
   1171 	{0, XK_9}
   1172 // count = 10
   1173 
   1174 #define glue(_start, _end) _start##_end
   1175 
   1176 #define VIM_DELIMITER_REGIONS(_func, _inside_around)					\
   1177 		{XK_ANY_MOD, XK_w,            _func, VIM_CURRENT_WORD},			\
   1178 	{XK_ANY_MOD, XK_parenleft,    _func, glue(VIM_, _inside_around##_ROUND_BRACKETS)}, \
   1179 	{XK_ANY_MOD, XK_parenright,   _func, glue(VIM_, _inside_around##_ROUND_BRACKETS)}, \
   1180 	{XK_ANY_MOD, XK_b,            _func, glue(VIM_, _inside_around##_ROUND_BRACKETS)}, \
   1181 	{XK_ANY_MOD, XK_bracketleft,  _func, glue(VIM_, _inside_around##_SQUARE_BRACKETS)}, \
   1182 	{XK_ANY_MOD, XK_bracketright, _func, glue(VIM_, _inside_around##_SQUARE_BRACKETS)}, \
   1183 	{XK_ANY_MOD, XK_braceleft,    _func, glue(VIM_, _inside_around##_CURLY_BRACKETS)}, \
   1184 	{XK_ANY_MOD, XK_braceright,   _func, glue(VIM_, _inside_around##_CURLY_BRACKETS)}, \
   1185 	{XK_ANY_MOD, XK_B,            _func, glue(VIM_, _inside_around##_CURLY_BRACKETS)}, \
   1186 	{XK_ANY_MOD, XK_less,         _func, glue(VIM_, _inside_around##_ANGLE_BRACKET)}, \
   1187 	{XK_ANY_MOD, XK_greater,      _func, glue(VIM_, _inside_around##_ANGLE_BRACKET)}, \
   1188 	{XK_ANY_MOD, XK_t,            _func, glue(VIM_, _inside_around##_ANGLE_BRACKET)}, \
   1189 	{XK_ANY_MOD, XK_quotedbl,     _func, glue(VIM_, _inside_around##_DOUBLE_QUOTES)}, \
   1190 	{XK_ANY_MOD, XK_apostrophe,   _func, glue(VIM_, _inside_around##_SINGLE_QUOTES)}, \
   1191 	{XK_ANY_MOD, XK_p,            _func, glue(VIM_, _inside_around##_PARAGRAPH)}, \
   1192 	{XK_ANY_MOD, XK_grave,        _func, glue(VIM_, _inside_around##_BACK_QUOTES)}
   1193 // count = 16
   1194 
   1195 #define CHAIN_COUNT(_x) (_x)
   1196 
   1197 struct chained_keybind normal_mode_keybinds[]  = {
   1198 		numbers(),
   1199 		// se specific keybinds, all followed by SPC, inspired by Emacs evil-mode
   1200 		{XK_ANY_MOD, XK_space, vim_enter, 0, "se commands [...]", (struct chained_keybind[]) {
   1201 						{0, XK_h, vim_change_window, MOVE_LEFT},
   1202 						{0, XK_j, vim_change_window, MOVE_DOWN},
   1203 						{0, XK_k, vim_change_window, MOVE_UP},
   1204 						{0, XK_l, vim_change_window, MOVE_RIGHT},
   1205 						{XK_ANY_MOD, XK_h, vim_resize_window, MOVE_LEFT},
   1206 						{XK_ANY_MOD, XK_j, vim_resize_window, MOVE_DOWN},
   1207 						{XK_ANY_MOD, XK_k, vim_resize_window, MOVE_UP},
   1208 						{XK_ANY_MOD, XK_l, vim_resize_window, MOVE_RIGHT},
   1209 						{XK_ANY_MOD, XK_H, vim_resize_window, MOVE_LEFT},
   1210 						{XK_ANY_MOD, XK_J, vim_resize_window, MOVE_DOWN},
   1211 						{XK_ANY_MOD, XK_K, vim_resize_window, MOVE_UP},
   1212 						{XK_ANY_MOD, XK_L, vim_resize_window, MOVE_RIGHT},
   1213 						{ShiftMask, XK_W, vim_save_buffer},
   1214 						{ControlMask, XK_s, vim_save_buffer},
   1215 						{0, XK_s, vim_split_window, WINDOW_VERTICAL},
   1216 						{0, XK_v, vim_split_window, WINDOW_HORISONTAL},
   1217 						{0, XK_d, vim_delete_window},
   1218 						{0, XK_Tab, vim_swap_to_next_file_buffer},
   1219 						{XK_ANY_MOD, XK_D, vim_kill_buffer},
   1220 						{0, XK_b, vim_enter, 0, "buffer [...]", (struct chained_keybind[]) {
   1221 										{0, XK_k, vim_kill_buffer},
   1222 										{0, XK_s, vim_search_for_buffer},
   1223 										{0, XK_w, vim_save_buffer},
   1224 										{0, XK_space, vim_search_for_buffer},
   1225 										{XK_ANY_MOD, XK_slash, vim_search_keyword_in_buffers},
   1226 								}, CHAIN_COUNT(5),
   1227 						},
   1228 						{0, XK_space, vim_open_file_browser},
   1229 						{ControlMask, XK_space, vim_search_for_buffer},
   1230 						{0, XK_p, vim_search_for_buffer},
   1231 						{XK_ANY_MOD, XK_slash, vim_search_keyword_in_buffers},
   1232 						{XK_ANY_MOD, XK_plus, vim_zoom,  +1},
   1233 						{XK_ANY_MOD, XK_minus, vim_zoom, -1},
   1234 						{XK_ANY_MOD, XK_Home, vim_zoomreset},
   1235 						numbers(),
   1236 				}, CHAIN_COUNT(36),
   1237 		},
   1238 
   1239 		// movement
   1240 		{0, XK_h, vim_move_on_line, -1},
   1241 		{0, XK_j, vim_move_lines,   +1},
   1242 		{0, XK_k, vim_move_lines,   -1},
   1243 		{0, XK_l, vim_move_on_line, +1},
   1244 		{0, XK_Left, vim_move_on_line, -1},
   1245 		{0, XK_Down, vim_move_lines,   +1},
   1246 		{0, XK_Up, vim_move_lines,   -1},
   1247 		{0, XK_Right, vim_move_on_line, +1},
   1248 		{0, XK_BackSpace, vim_move_on_line, -1},
   1249 		{0, XK_Home, vim_home},
   1250 		{0, XK_End,  vim_end},
   1251 		{0, XK_0, vim_move, VIM_TO_START_OF_LINE},
   1252 		{ControlMask, XK_Right, vim_move, VIM_TO_END_OF_STRING},
   1253 		{ControlMask, XK_Left, vim_move, VIM_PREV_STRING_START},
   1254 		{0, XK_w, vim_move, VIM_TO_START_OF_WORD},
   1255 		{ShiftMask, XK_W, vim_move, VIM_TO_START_OF_STRING},
   1256 		{0, XK_e, vim_move, VIM_TO_END_OF_WORD},
   1257 		{ShiftMask, XK_E, vim_move, VIM_TO_END_OF_STRING},
   1258 		{0, XK_b, vim_move, VIM_PREV_WORD_START},
   1259 		{ShiftMask, XK_B, vim_move, VIM_PREV_STRING_START},
   1260 		{XK_ANY_MOD, XK_dollar, vim_move, VIM_TO_END_OF_LINE},
   1261 		{XK_ANY_MOD, XK_G, vim_move, VIM_TO_END_OF_FILE},
   1262 		{0, XK_g, vim_enter, 0, "goto", (struct chained_keybind[]) {
   1263 						{0, XK_g, vim_move, VIM_TO_START_OF_FILE},
   1264 				}, CHAIN_COUNT(1),
   1265 		},
   1266 
   1267 		// scroll
   1268 		{0, XK_Page_Down, vim_move_down_one_screen_devided_by, 1},
   1269 		{0, XK_Page_Up,   vim_move_down_one_screen_devided_by,  -1},
   1270 		{ControlMask, XK_d, vim_move_down_one_screen_devided_by, 2},
   1271 		{ControlMask, XK_u, vim_move_down_one_screen_devided_by,  -2},
   1272 		{ControlMask, XK_f, vim_move_down_one_screen_devided_by, 1},
   1273 		{ControlMask, XK_b, vim_move_down_one_screen_devided_by,  -1},
   1274 		{ControlMask, XK_e, vim_move_scroll, 1},
   1275 		{ControlMask, XK_y, vim_move_scroll,  -1},
   1276 		{0, XK_z, vim_enter, 0, NULL, (struct chained_keybind[]) {
   1277 						{0, XK_z, vim_center_scroll},
   1278 				}, CHAIN_COUNT(1),
   1279 		},
   1280 
   1281 		// misc
   1282 		{XK_ANY_MOD, XK_Escape, vim_change_mode, VIM_NORMAL},
   1283 		{0, XK_Tab, vim_auto_indent_current_line},
   1284 		{0, XK_q, vim_exit},
   1285 		{0, XK_u, vim_undo},
   1286 		{0, XK_period, vim_repeat_last_command},
   1287 		{ControlMask, XK_r, vim_redo},
   1288 		{XK_ANY_MOD, XK_slash, vim_search},
   1289 		{XK_ANY_MOD, XK_question, vim_search, 1},
   1290 		{0, XK_n, vim_next},
   1291 		{ShiftMask, XK_N, vim_prev},
   1292 		// copy / yank
   1293 		{0, XK_p, vim_paste},
   1294 		{0, XK_y, vim_enter, 0, "yank", (struct chained_keybind[]) {
   1295 						{0, XK_y, vim_yank, VIM_CURRENT_LINE},
   1296 						{XK_ANY_MOD, XK_I, vim_yank, VIM_TO_END_OF_INDENT},
   1297 						{XK_ANY_MOD, XK_0, vim_yank, VIM_TO_START_OF_LINE},
   1298 						{XK_ANY_MOD, XK_A, vim_yank, VIM_TO_END_OF_LINE},
   1299 						{XK_ANY_MOD, XK_G, vim_yank, VIM_TO_END_OF_FILE},
   1300 						{XK_ANY_MOD, XK_w, vim_yank, VIM_TO_START_OF_WORD},
   1301 						{XK_ANY_MOD, XK_W, vim_yank, VIM_TO_START_OF_STRING},
   1302 						{XK_ANY_MOD, XK_e, vim_yank, VIM_TO_END_OF_WORD},
   1303 						{XK_ANY_MOD, XK_E, vim_yank, VIM_TO_END_OF_STRING},
   1304 						{XK_ANY_MOD, XK_b, vim_yank, VIM_PREV_WORD_START},
   1305 						{XK_ANY_MOD, XK_B, vim_yank, VIM_PREV_STRING_START},
   1306 						{XK_ANY_MOD, XK_dollar, vim_yank, VIM_TO_END_OF_LINE},
   1307 						{0, XK_g, vim_enter, 0, NULL, (struct chained_keybind[]) {
   1308 										{0, XK_g, vim_yank, VIM_TO_START_OF_FILE},
   1309 								}, CHAIN_COUNT(1),
   1310 						},
   1311 						{0, XK_i, vim_enter, 0, "inside", (struct chained_keybind[]) {
   1312 										VIM_DELIMITER_REGIONS(vim_yank, INSIDE)
   1313 								}, CHAIN_COUNT(16),
   1314 						},
   1315 						{0, XK_a, vim_enter, 0, "around", (struct chained_keybind[]) {
   1316 										VIM_DELIMITER_REGIONS(vim_yank, AROUND)
   1317 								}, CHAIN_COUNT(16),
   1318 						},
   1319 						numbers(),
   1320 				}, CHAIN_COUNT(25)
   1321 		},
   1322 
   1323 		// insert
   1324 		{0, XK_i, vim_change_mode, VIM_INSERT},
   1325 		{XK_ANY_MOD, XK_I, vim_insert_start},
   1326 		{0, XK_a, vim_append},
   1327 		{XK_ANY_MOD, XK_A, vim_insert_end},
   1328 		{0, XK_o, vim_insert_new_line},
   1329 
   1330 		// visual
   1331 		{0, XK_v, vim_change_mode, VIM_VISUAL},
   1332 		{ShiftMask, XK_V, vim_change_mode, VIM_VISUAL_LINE},
   1333 		//{ControlMask, XK_v, vim_change_mode, VIM_VISUAL},
   1334 
   1335 		// deleting
   1336 		{XK_ANY_MOD, XK_J, vim_remove_newline_at_end},
   1337 		{0, XK_x, vim_remove_one_char},
   1338 		{XK_ANY_MOD, XK_X, vim_remove_one_char, -1},
   1339 		{0, XK_Delete, vim_remove_one_char, +1},
   1340 		{ControlMask, XK_BackSpace, vim_delete, VIM_PREV_STRING_START},
   1341 		{ControlMask, XK_Delete, vim_delete, VIM_TO_END_OF_STRING},
   1342 		{XK_ANY_MOD, XK_D, vim_delete, VIM_TO_END_OF_LINE},
   1343 		{0, XK_d, vim_enter, 0, "delete", (struct chained_keybind[]) {
   1344 						{0, XK_d, vim_delete, VIM_CURRENT_LINE},
   1345 						{0, XK_l, vim_remove_one_char},
   1346 						{0, XK_h, vim_remove_one_char, -1},
   1347 						{XK_ANY_MOD, XK_I, vim_delete, VIM_TO_END_OF_INDENT},
   1348 						{XK_ANY_MOD, XK_A, vim_delete, VIM_TO_END_OF_LINE},
   1349 						{XK_ANY_MOD, XK_G, vim_delete, VIM_TO_END_OF_FILE},
   1350 						{XK_ANY_MOD, XK_w, vim_delete, VIM_TO_START_OF_WORD},
   1351 						{XK_ANY_MOD, XK_W, vim_delete, VIM_TO_START_OF_STRING},
   1352 						{XK_ANY_MOD, XK_e, vim_delete, VIM_TO_END_OF_WORD},
   1353 						{XK_ANY_MOD, XK_E, vim_delete, VIM_TO_END_OF_STRING},
   1354 						{XK_ANY_MOD, XK_b, vim_delete, VIM_PREV_WORD_START},
   1355 						{XK_ANY_MOD, XK_B, vim_delete, VIM_PREV_STRING_START},
   1356 						{XK_ANY_MOD, XK_dollar, vim_delete, VIM_TO_END_OF_LINE},
   1357 						{0, XK_g, vim_enter, 0, NULL, (struct chained_keybind[]) {
   1358 										{0, XK_g, vim_delete, VIM_TO_START_OF_FILE},
   1359 								}, CHAIN_COUNT(1),
   1360 						},
   1361 						{0, XK_i, vim_enter, 0, "inside", (struct chained_keybind[]) {
   1362 										VIM_DELIMITER_REGIONS(vim_delete, INSIDE)
   1363 								}, CHAIN_COUNT(16),
   1364 						},
   1365 						{0, XK_a, vim_enter, 0, "around", (struct chained_keybind[]) {
   1366 										VIM_DELIMITER_REGIONS(vim_delete, AROUND)
   1367 								}, CHAIN_COUNT(16),
   1368 						},
   1369 						numbers(),
   1370 						{XK_ANY_MOD, XK_0, vim_delete, VIM_TO_START_OF_LINE},
   1371 				}, CHAIN_COUNT(27),
   1372 		},
   1373 		// change
   1374 		{XK_ANY_MOD, XK_C, vim_change, VIM_TO_END_OF_LINE},
   1375 		{0, XK_c, vim_enter, 0, "change", (struct chained_keybind[]) {
   1376 						{0, XK_c, vim_change, VIM_CURRENT_LINE},
   1377 						//{0, XK_l, vim_remove_one_char},
   1378 						//{0, XK_h, vim_remove_one_char, -1},
   1379 						{XK_ANY_MOD, XK_I, vim_change, VIM_TO_END_OF_INDENT},
   1380 						{XK_ANY_MOD, XK_A, vim_change, VIM_TO_END_OF_LINE},
   1381 						{XK_ANY_MOD, XK_G, vim_change, VIM_TO_END_OF_FILE},
   1382 						{XK_ANY_MOD, XK_w, vim_change, VIM_TO_START_OF_WORD},
   1383 						{XK_ANY_MOD, XK_W, vim_change, VIM_TO_START_OF_STRING},
   1384 						{XK_ANY_MOD, XK_e, vim_change, VIM_TO_END_OF_WORD},
   1385 						{XK_ANY_MOD, XK_E, vim_change, VIM_TO_END_OF_STRING},
   1386 						{XK_ANY_MOD, XK_b, vim_change, VIM_PREV_WORD_START},
   1387 						{XK_ANY_MOD, XK_B, vim_change, VIM_PREV_STRING_START},
   1388 						{XK_ANY_MOD, XK_dollar, vim_change, VIM_TO_END_OF_LINE},
   1389 						{0, XK_g, vim_enter, 0, NULL, (struct chained_keybind[]) {
   1390 										{0, XK_g, vim_change, VIM_TO_START_OF_FILE},
   1391 								}, CHAIN_COUNT(1),
   1392 						},
   1393 						{0, XK_i, vim_enter, 0, "inside", (struct chained_keybind[]) {
   1394 										VIM_DELIMITER_REGIONS(vim_change, INSIDE)
   1395 								}, CHAIN_COUNT(16),
   1396 						},
   1397 						{0, XK_a, vim_enter, 0, "around", (struct chained_keybind[]) {
   1398 										VIM_DELIMITER_REGIONS(vim_change, AROUND)
   1399 								}, CHAIN_COUNT(16),
   1400 						},
   1401 						numbers(),
   1402 						{XK_ANY_MOD, XK_0, vim_change, VIM_TO_START_OF_LINE},
   1403 				}, CHAIN_COUNT(27-2),
   1404 		},
   1405 };
   1406 
   1407 struct chained_keybind visual_mode_keybinds[]  = {
   1408 		numbers(),
   1409 		{XK_ANY_MOD, XK_Escape, vim_change_mode, VIM_NORMAL},
   1410 		{0, XK_v, vim_change_mode, VIM_NORMAL},
   1411 
   1412 		{0, XK_h, vim_move_on_line, -1},
   1413 		{0, XK_j, vim_move_lines,   +1},
   1414 		{0, XK_k, vim_move_lines,   -1},
   1415 		{0, XK_l, vim_move_on_line, +1},
   1416 		{0, XK_Left, vim_move_on_line, -1},
   1417 		{0, XK_Down, vim_move_lines,   +1},
   1418 		{0, XK_Up, vim_move_lines,   -1},
   1419 		{0, XK_Right, vim_move_on_line, +1},
   1420 		{0, XK_BackSpace, vim_move_on_line, -1},
   1421 		{0, XK_Home, vim_home},
   1422 		{0, XK_End,  vim_end},
   1423 		{ControlMask, XK_Right, vim_move, VIM_TO_END_OF_STRING},
   1424 		{ControlMask, XK_Left, vim_move, VIM_PREV_STRING_START},
   1425 		{0, XK_w, vim_move, VIM_TO_START_OF_WORD},
   1426 		{ShiftMask, XK_W, vim_move, VIM_TO_START_OF_STRING},
   1427 		{0, XK_e, vim_move, VIM_TO_END_OF_WORD},
   1428 		{ShiftMask, XK_E, vim_move, VIM_TO_END_OF_STRING},
   1429 		{0, XK_b, vim_move, VIM_PREV_WORD_START},
   1430 		{ShiftMask, XK_B, vim_move, VIM_PREV_STRING_START},
   1431 
   1432 		{XK_ANY_MOD, XK_0, vim_move, VIM_TO_START_OF_LINE},
   1433 		{XK_ANY_MOD, XK_A, vim_move, VIM_TO_END_OF_LINE},
   1434 		{XK_ANY_MOD, XK_G, vim_move, VIM_TO_END_OF_FILE},
   1435 		{XK_ANY_MOD, XK_w, vim_move, VIM_TO_START_OF_WORD},
   1436 		{XK_ANY_MOD, XK_W, vim_move, VIM_TO_START_OF_STRING},
   1437 		{XK_ANY_MOD, XK_e, vim_move, VIM_TO_END_OF_WORD},
   1438 		{XK_ANY_MOD, XK_E, vim_move, VIM_TO_END_OF_STRING},
   1439 		{XK_ANY_MOD, XK_b, vim_move, VIM_PREV_WORD_START},
   1440 		{XK_ANY_MOD, XK_B, vim_move, VIM_PREV_STRING_START},
   1441 		{XK_ANY_MOD, XK_dollar, vim_move, VIM_TO_END_OF_LINE},
   1442 		{0, XK_i, vim_enter, 0, "inside", (struct chained_keybind[]) {
   1443 						VIM_DELIMITER_REGIONS(vim_visual_delimit, INSIDE)
   1444 				}, CHAIN_COUNT(16),
   1445 		},
   1446 		{0, XK_a, vim_enter, 0, "around", (struct chained_keybind[]) {
   1447 						VIM_DELIMITER_REGIONS(vim_visual_delimit, AROUND)
   1448 				}, CHAIN_COUNT(16),
   1449 		},
   1450 
   1451 		{0, XK_d, vim_delete, VIM_CURRENT_SELECTION},
   1452 		{0, XK_x, vim_delete, VIM_CURRENT_SELECTION},
   1453 		{XK_ANY_MOD, XK_X, vim_delete, VIM_CURRENT_SELECTION},
   1454 		{0, XK_y, vim_yank, VIM_CURRENT_SELECTION},
   1455 };
   1456 
   1457 struct chained_keybind insert_mode_keybinds[]  = {
   1458 		{XK_ANY_MOD, XK_Escape, vim_change_mode, VIM_NORMAL},
   1459 		{0, XK_Page_Down, vim_move_down_one_screen_devided_by, 1},
   1460 		{0, XK_Page_Up,   vim_move_down_one_screen_devided_by,  -1},
   1461 		{0, XK_Home, vim_home},
   1462 		{0, XK_End,  vim_end},
   1463 		{0, XK_Left, vim_move_on_line, -1},
   1464 		{0, XK_Down, vim_move_lines,   +1},
   1465 		{0, XK_Up, vim_move_lines,   -1},
   1466 		{0, XK_Right, vim_move_on_line, +1},
   1467 		{ControlMask, XK_Right, vim_move, VIM_TO_END_OF_STRING},
   1468 		{ControlMask, XK_Left, vim_move, VIM_PREV_STRING_START},
   1469 
   1470 		{0, XK_BackSpace, vim_backspace, -1},
   1471 		{0, XK_Delete,    vim_remove_one_char},
   1472 		{ControlMask, XK_BackSpace, vim_delete, VIM_PREV_STRING_START},
   1473 		{ControlMask, XK_Delete, vim_delete, VIM_TO_END_OF_STRING},
   1474 		{0, XK_Return,    vim_insert_return},
   1475 		{0, XK_Tab,       vim_insert_tab},
   1476 };
   1477 
   1478 static void
   1479 push_keypress_to_log(KeySym ksym, unsigned int modkey, const char* buf, int len)
   1480 {
   1481 		if (chain_len+1 <= CHAINED_KEYBIND_LEN) {
   1482 				current_chain[chain_len].ksym = ksym;
   1483 				current_chain[chain_len].modkey = modkey;
   1484 				if (len != 0)
   1485 						current_chain[chain_len].buf = xrealloc(current_chain[chain_len].buf, len);
   1486 				memcpy(current_chain[chain_len].buf, buf, len);
   1487 				current_chain[chain_len].len = len;
   1488 				chain_len++;
   1489 		}
   1490 }
   1491 
   1492 static int
   1493 do_chained_keybinds(struct chained_keybind** keybinds, int* keybind_count, KeySym ksym, unsigned int modkey, const char* buf, int len)
   1494 {
   1495 		// remove modifiers
   1496 		switch(ksym) {
   1497 		case XK_Shift_L:   case XK_Shift_R:
   1498 		case XK_Alt_L:     case XK_Alt_R:
   1499 		case XK_Control_L: case XK_Control_R:
   1500 		case XK_Super_L:   case XK_Super_R:
   1501 		case XK_Meta_L:    case XK_Meta_R:
   1502 		case XK_ISO_Group_Latch:  case XK_ISO_Group_Shift: case XK_ISO_Group_Lock:
   1503 		case XK_ISO_Level3_Latch: case XK_ISO_Level3_Lock: case XK_ISO_Level3_Shift:
   1504 		case XK_ISO_Level5_Latch: case XK_ISO_Level5_Lock: case XK_ISO_Level5_Shift:
   1505 		case XK_ISO_Level2_Latch:
   1506 				return 3;
   1507 		}
   1508 		push_keypress_to_log(ksym, modkey, buf, len);
   1509 		for (int i = 0; i < *keybind_count; i++) {
   1510 				struct chained_keybind keybind = (*keybinds)[i];
   1511 				if (ksym == keybind.keysym && match(keybind.mod, modkey)) {
   1512 						if (!keybind.func)
   1513 								return 1;
   1514 
   1515 						int res = keybind.func(keybind.custom_mode);
   1516 
   1517 						// TODO: add enums for random values
   1518 						if (!res)
   1519 								return 1;
   1520 						if (res == 2)
   1521 								continue;
   1522 
   1523 						if (res < 0) {
   1524 								*keybinds = NULL;
   1525 								*keybind_count = 0;
   1526 								if (res == -2)
   1527 										return 4;
   1528 								return 2;
   1529 						}
   1530 						if (res == 1) {
   1531 								soft_assert(keybind.next_keybinds && keybind.keybind_count,
   1532 											*keybinds = NULL;
   1533 											return 0;
   1534 										);
   1535 								*keybinds = keybind.next_keybinds;
   1536 								*keybind_count = keybind.keybind_count;
   1537 								return 1;
   1538 						}
   1539 				}
   1540 		}
   1541 		*keybinds = NULL;
   1542 		return 0;
   1543 }
   1544 
   1545 static const char*
   1546 get_ksym_string_no_xk(KeySym ksym, unsigned int modkey)
   1547 {
   1548 		static char str[1024];
   1549 		*str = 0;
   1550 		char* ksname;
   1551 		if (!(ksname = XKeysymToString(ksym)))
   1552 				ksname = "(no name)";
   1553 		else if (strcmp(ksname, "XK_") == 0)
   1554 				ksname += 3;
   1555 
   1556 		if (modkey != XK_ANY_MOD) {
   1557 				if (modkey & ControlMask)
   1558 						strcat(str, "C-");
   1559 				if (modkey & MODKEY)
   1560 						strcat(str, "M-");
   1561 		}
   1562 		strncat(str, ksname, sizeof(str) - strlen(str) - 1);
   1563 
   1564 		return str;
   1565 }
   1566 
   1567 static const char*
   1568 current_key_chain_string(int dont_add_minus_end)
   1569 {
   1570 		if (!chain_len)
   1571 				return NULL;
   1572 
   1573 		static char buf[STATUS_BAR_MAX_LEN];
   1574 		*buf = 0;
   1575 		int len = 0;
   1576 
   1577 		for(int i = 0; i < chain_len; i++) {
   1578 				const char* str = get_ksym_string_no_xk(current_chain[i].ksym, current_chain[i].modkey);
   1579 				int new_len = strlen(str);
   1580 				if (new_len + len < STATUS_BAR_MAX_LEN) {
   1581 						strcat(buf, str);
   1582 						len += new_len;
   1583 				}
   1584 
   1585 				if (len + 1 < STATUS_BAR_MAX_LEN) {
   1586 						if (i + 1 >= chain_len) {
   1587 								if (!dont_add_minus_end)
   1588 										strcat(buf, "-");
   1589 						} else {
   1590 								strcat(buf, " ");
   1591 						}
   1592 						len++;
   1593 				}
   1594 		}
   1595 
   1596 		return buf;
   1597 }
   1598 
   1599 static const char*
   1600 current_keybind_options(struct chained_keybind* keybinds, int keybind_len)
   1601 {
   1602 		if (!keybind_len || !keybinds)
   1603 				return "[no options]";
   1604 		static char buf[STATUS_BAR_MAX_LEN];
   1605 		strcpy(buf, "[");
   1606 		int len = 0;
   1607 
   1608 		for(int i = 0; i < keybind_len; i++) {
   1609 				switch(keybinds[i].keysym) {
   1610 				case XK_0: case XK_1: case XK_2:
   1611 				case XK_3: case XK_4: case XK_5:
   1612 				case XK_6: case XK_7: case XK_8:
   1613 				case XK_9:
   1614 						if (!keybinds[i].description)
   1615 								continue;
   1616 				}
   1617 
   1618 				const char* str = get_ksym_string_no_xk(keybinds[i].keysym, keybinds[i].mod);
   1619 				int new_len = strlen(str);
   1620 				if (new_len + len < STATUS_BAR_MAX_LEN) {
   1621 						strcat(buf, str);
   1622 						len += new_len;
   1623 				}
   1624 				if (keybinds[i].description) {
   1625 						new_len = strlen(keybinds[i].description) + sizeof(" →  ") - 1;
   1626 						if (new_len + len < STATUS_BAR_MAX_LEN) {
   1627 								strcat(buf, " →  ");
   1628 								strcat(buf, keybinds[i].description);
   1629 								len += new_len;
   1630 						}
   1631 				}
   1632 
   1633 				if (i + 1 < keybind_len && len + 2 < STATUS_BAR_MAX_LEN) {
   1634 						strcat(buf, ", ");
   1635 						len += 2;
   1636 				}
   1637 		}
   1638 		if (len + 1 < STATUS_BAR_MAX_LEN)
   1639 				strcat(buf, "]");
   1640 		return buf;
   1641 }
   1642 
   1643 /////////////////////////////////////////////////
   1644 // callbacks
   1645 //
   1646 
   1647 #include "extensions/default_status_bar.h"
   1648 #include "extensions/window_modes/choose_one_of_selection.h"
   1649 #include "extensions/undo.h"
   1650 #include "extensions/keep_cursor_col.h"
   1651 #include "extensions/move_selection_with_cursor.h"
   1652 #include "extensions/startup_message.h"
   1653 
   1654 static int keypress_actions(KeySym keycode, int modkey, const char* buf, int len);
   1655 static int vim_paste_callback(struct file_buffer* fb, char* data, int len);
   1656 static const struct extension vim = {
   1657 		.keypress = keypress_actions,
   1658 		.fb_paste = vim_paste_callback,
   1659 };
   1660 
   1661 struct extension_meta config_extensions[] = {
   1662 		{.e = startup_message, .enabled = 1},
   1663 		{.e = file_browser, .enabled = 1},
   1664 		{.e = search_open_fb, .enabled = 1},
   1665 		{.e = search_keywords_open_fb, .enabled = 1},
   1666 
   1667 		{.e = syntax_e, .enabled = 1},
   1668 
   1669 		{.e = undo, .enabled = 1},
   1670 		{.e = keep_cursor_col, .enabled = 1},
   1671 		{.e = move_selection_with_cursor, .enabled = 1},
   1672 		{.e = vim, .enabled = 1},
   1673 
   1674 		{.e = default_status_bar, .enabled = 1},
   1675 
   1676 		{.end = 1}
   1677 };
   1678 
   1679 struct extension_meta* extensions = config_extensions;
   1680 
   1681 static int
   1682 vim_paste_callback(struct file_buffer* fb, char* data, int len)
   1683 {
   1684 		int offset = focused_window->fb_index;
   1685 		if (data[len-1] == '\n') {
   1686 				if (paste_behind) {
   1687 						offset = fb_seek_char_backwards(fb, offset, '\n');
   1688 						offset = MAX(offset, 0);
   1689 				} else {
   1690 						offset = fb_seek_char(fb, offset, '\n');
   1691 						if (offset < 0)
   1692 								offset = fb->len-1;
   1693 						else
   1694 								offset++;
   1695 				}
   1696 		} else if (paste_behind) {
   1697 				offset = MAX(offset - 1, 0);
   1698 		}
   1699 		fb_insert(fb, data, len, offset, 1);
   1700 		window_node_move_all_cursors_on_same_fb(&root_node, (paste_behind) ? NULL : focused_node, focused_window->fb_index, offset,
   1701 												wb_move_offset_relative, len, CURSOR_COMMAND_MOVEMENT);
   1702 		paste_behind = 0;
   1703 		return 0;
   1704 }
   1705 
   1706 static int
   1707 vim_repeat_last_command(int custom_mode)
   1708 {
   1709 		static int recursioin_prevention = 0;
   1710 		if (recursioin_prevention || !previous_chain_len)
   1711 				return -2;
   1712 		recursioin_prevention = 1;
   1713 		vim_change_mode(VIM_NORMAL);
   1714 
   1715 		int count = vim_chain_parse_count(0);
   1716 		while(count--) {
   1717 				chain_len = 0;
   1718 				last_used_command_index = 0;
   1719 				for(int i = 0; i < previous_chain_len; i++) {
   1720 						struct keypress_logg pc = previous_chain[i];
   1721 						keypress_actions(pc.ksym, pc.modkey, pc.buf, pc.len);
   1722 				}
   1723 		}
   1724 
   1725 		vim_change_mode(VIM_NORMAL);
   1726 		recursioin_prevention = 0;
   1727 		writef_to_status_bar("repeated %d inputs", previous_chain_len);
   1728 		return -2;
   1729 }
   1730 
   1731 void insert_string(const char* buf, int len)
   1732 {
   1733 		struct file_buffer* fb = get_fb(focused_window);
   1734 
   1735 		if (buf[0] >= 32 || len > 1) {
   1736 				fb_delete_selection(fb);
   1737 				fb_insert(fb, buf, len, focused_window->cursor_offset, 0);
   1738 				window_node_move_all_cursors_on_same_fb(&root_node, NULL, focused_window->fb_index, focused_window->cursor_offset,
   1739 														wb_move_offset_relative, len, CURSOR_COMMAND_MOVEMENT);
   1740 		} else {
   1741 				writef_to_status_bar("unhandled control character 0x%x\n", buf[0]);
   1742 		}
   1743 }
   1744 
   1745 int
   1746 keypress_actions(KeySym keycode, int modkey, const char* buf, int len)
   1747 {
   1748 		// current keybind that is set
   1749 		static struct chained_keybind* keybinds;
   1750 		static int keybind_len;
   1751 		if (vim_mode == VIM_NORMAL) {
   1752 				if (!keybinds) {
   1753 						keybinds = normal_mode_keybinds;
   1754 						keybind_len = LEN(normal_mode_keybinds);
   1755 				}
   1756 		} else if (vim_mode == VIM_INSERT) {
   1757 				if (!keybinds) {
   1758 						keybinds = insert_mode_keybinds;
   1759 						keybind_len = LEN(insert_mode_keybinds);
   1760 				}
   1761 		} else if (vim_mode == VIM_VISUAL) {
   1762 				if (!keybinds) {
   1763 						keybinds = visual_mode_keybinds;
   1764 						keybind_len = LEN(visual_mode_keybinds);
   1765 				}
   1766 		}
   1767 
   1768 		char tmp_status_bar[STATUS_BAR_MAX_LEN];
   1769 		memcpy(tmp_status_bar, status_bar_contents, STATUS_BAR_MAX_LEN);
   1770 
   1771 		int last_mode = vim_mode;
   1772 		int res = do_chained_keybinds(&keybinds, &keybind_len, keycode, modkey, buf, len);
   1773 
   1774 		int status_bar_changed = strcmp(tmp_status_bar, status_bar_contents) != 0;
   1775 
   1776 		if (res == 3)
   1777 				return 0;
   1778 		if (res == 2)
   1779 				last_used_command_index = MAX(0, chain_len - 1);
   1780 		if (!res) {
   1781 				if (vim_mode == VIM_INSERT) {
   1782 						insert_string(buf, len);
   1783 				} else  {
   1784 						if (vim_mode == VIM_NORMAL) {
   1785 								const char* chain_str = current_key_chain_string(1);
   1786 								writef_to_status_bar("keybind \"%s\" does not exist", chain_str);
   1787 
   1788 								// reset chain
   1789 								chain_len = 0;
   1790 								last_used_command_index = 0;
   1791 						} else {
   1792 								writef_to_status_bar("keybind does not exist");
   1793 						}
   1794 				}
   1795 		} else if ((res == 2 || res == 4) && vim_mode == VIM_NORMAL) {
   1796 				// reset chain
   1797 				if (chain_len > 1 && !status_bar_changed && last_mode == vim_mode) {
   1798 						const char* chain_str = current_key_chain_string(1);
   1799 						if (chain_str)
   1800 								writef_to_status_bar("%s", chain_str);
   1801 				}
   1802 				if (res != 4) {
   1803 						vim_copy_log(&previous_chain, previous_chain_len, current_chain, chain_len);
   1804 						previous_chain_len = chain_len;
   1805 				}
   1806 				chain_len = 0;
   1807 				last_used_command_index = 0;
   1808 		} else if (vim_mode == VIM_NORMAL) {
   1809 				const char* chain_str = current_key_chain_string(0);
   1810 				const char* options = current_keybind_options(keybinds, keybind_len);
   1811 				if (chain_str && !status_bar_changed)
   1812 						writef_to_status_bar("%s %s", chain_str, options);
   1813 		} else {
   1814 				const char* options = current_keybind_options(keybinds, keybind_len);
   1815 
   1816 				if (chain_len > 0 && strcmp(options, "[no options]") != 0) {
   1817 						const char* latest_keypress = get_ksym_string_no_xk(current_chain[chain_len - 1].ksym, current_chain[chain_len - 1].modkey);
   1818 						writef_to_status_bar("%s- %s", latest_keypress, options);
   1819 				}
   1820 		}
   1821 		return 0;
   1822 }
   1823 
   1824 // TODO: gg goes to line but not G