se

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

buffer.c (27624B)


      1 #include "buffer.h"
      2 
      3 #include "config.h"
      4 #include "se.h"
      5 #include "extension.h"
      6 
      7 #include <unistd.h>
      8 #include <sys/stat.h>
      9 #include <errno.h>
     10 #include <time.h>
     11 
     12 // TODO: mark buffers as dirty and only redraw windows that have been changed
     13 
     14 ////////////////////////////////////////////////
     15 // Globals
     16 //
     17 
     18 static char root_node_search[SEARCH_TERM_MAX_LEN];
     19 struct window_split_node root_node = {.mode = WINDOW_SINGULAR, .search = root_node_search};
     20 struct window_split_node* focused_node = &root_node;
     21 struct window_buffer* focused_window = &root_node.wb;
     22 
     23 static struct file_buffer* file_buffers;
     24 static int available_buffer_slots = 0;
     25 
     26 /////////////////////////////////////////////////
     27 // Function implementations
     28 //
     29 
     30 ////////////////////////////////////////////////
     31 // File buffer
     32 //
     33 
     34 static void
     35 recursive_mkdir(char *path) {
     36 		if (!path || !strlen(path))
     37 				return;
     38 		char *sep = strrchr(path, '/');
     39 		if(sep) {
     40 				*sep = '\0';
     41 				recursive_mkdir(path);
     42 				*sep = '/';
     43 		}
     44 		if(mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO) && errno != EEXIST)
     45 				fprintf(stderr, "error while trying to create '%s'\n%s\n", path, strerror(errno));
     46 }
     47 
     48 
     49 // TODO: file open callback, implement as plugin
     50 static int open_seproj(struct file_buffer fb);
     51 int
     52 open_seproj(struct file_buffer fb)
     53 {
     54 		int first = -1;
     55 
     56 		char* path = file_path_get_path(fb.file_path);
     57 		chdir(path);
     58 		int offset = -1;
     59 
     60 		while((offset = fb_seek_char(&fb, offset+1, ' ')) >= 0)
     61 				fb_change(&fb, "\n", 1, offset, 0);
     62 
     63 		offset = -1;
     64 		while((offset = fb_seek_char(&fb, offset+1, '\n')) >= 0) {
     65 				char* line = fb_get_line_at_offset(&fb, offset);
     66 				if (strlen(line) && !is_file_type(line, ".seproj")) {
     67 						if (first < 0)
     68 								first = fb_new_entry(line);
     69 						else
     70 								fb_new_entry(line);
     71 				}
     72 				free(line);
     73 		}
     74 
     75 		if (first < 0)
     76 				first = fb_new_entry(NULL);
     77 		writef_to_status_bar("opened project %s", path);
     78 		free(path);
     79 
     80 		fb_destroy(&fb);
     81 		return first;
     82 }
     83 
     84 void
     85 fb_write_to_filepath(struct file_buffer* fb)
     86 {
     87 		if (!fb->file_path)
     88 				return;
     89 		soft_assert(fb->contents, return;);
     90 		FILE* file = fopen(fb->file_path, "w");
     91 		soft_assert(file, return;);
     92 
     93 		if (fb->mode & FB_UTF8_SIGNED)
     94 				fwrite("\xEF\xBB\xBF", 1, 3, file);
     95 		fwrite(fb->contents, sizeof(char), fb->len, file);
     96 		writef_to_status_bar("saved buffer to %s", fb->file_path);
     97 
     98 		fclose(file);
     99 		call_extension(fb_written_to_file, fb);
    100 }
    101 
    102 
    103 int
    104 destroy_fb_entry(struct window_split_node* node, struct window_split_node* root)
    105 {
    106 		// do not allow deletion of the lst file buffer
    107 		int n = 0;
    108 		for(; n < available_buffer_slots; n++)
    109 				if (file_buffers[n].contents && n != node->wb.fb_index)
    110 						break;
    111 		if (n >= available_buffer_slots) {
    112 				writef_to_status_bar("can't delete last buffer");
    113 				return 0;
    114 		}
    115 
    116 		if (window_other_nodes_contain_fb(node, root)) {
    117 				node->wb.fb_index++;
    118 				node->wb = wb_new(node->wb.fb_index);
    119 				writef_to_status_bar("swapped buffer");
    120 				return 0;
    121 		}
    122 		fb_destroy(get_fb(&node->wb));
    123 
    124 		node->wb = wb_new(node->wb.fb_index);
    125 
    126 		return 1;
    127 }
    128 
    129 
    130 
    131 struct file_buffer*
    132 get_fb(struct window_buffer* wb)
    133 {
    134 		soft_assert(wb, wb = focused_window;);
    135 		soft_assert(file_buffers, fb_new_entry(NULL););
    136 
    137 		if (wb->fb_index < 0)
    138 				wb->fb_index = available_buffer_slots-1;
    139 		else if (wb->fb_index >= available_buffer_slots)
    140 				wb->fb_index = 0;
    141 
    142 		if (!file_buffers[wb->fb_index].contents) {
    143 				for(int n = wb->fb_index; n < available_buffer_slots; n++) {
    144 						if (file_buffers[n].contents) {
    145 								wb->fb_index = n;
    146 								return &file_buffers[n];
    147 						}
    148 				}
    149 				for(int n = 0; n < available_buffer_slots; n++) {
    150 						if (file_buffers[n].contents) {
    151 								wb->fb_index = n;
    152 								return &file_buffers[n];
    153 						}
    154 				}
    155 		} else {
    156 				soft_assert(file_buffers[wb->fb_index].contents, );
    157 				return &file_buffers[wb->fb_index];
    158 		}
    159 
    160 		wb->fb_index = fb_new_entry(NULL);
    161 		writef_to_status_bar("all buffers were somehow deleted, creating new one");
    162 		status_bar_bg = warning_color;
    163 		return get_fb(wb);
    164 }
    165 
    166 int
    167 fb_delete_selection(struct file_buffer* fb)
    168 {
    169 		if (fb->mode & FB_SELECTION_ON) {
    170 				fb_remove_selection(fb);
    171 				wb_move_cursor_to_selection_start(focused_window);
    172 				fb->mode &= ~(FB_SELECTION_ON);
    173 				return 1;
    174 		}
    175 		return 0;
    176 }
    177 
    178 struct file_buffer
    179 fb_new(const char* file_path)
    180 {
    181 		struct file_buffer fb = {0};
    182 		fb.file_path = xmalloc(PATH_MAX);
    183 
    184 		char* res = realpath(file_path, fb.file_path);
    185 		if (!res) {
    186 				char* path = file_path_get_path(file_path);
    187 				recursive_mkdir(path);
    188 				free(path);
    189 
    190 				FILE *new_file = fopen(file_path, "wb");
    191 				fclose(new_file);
    192 				realpath(file_path, fb.file_path);
    193 				remove(file_path);
    194 
    195 				writef_to_status_bar("opened new file %s", fb.file_path);
    196 		} else if (path_is_folder(fb.file_path)) {
    197 				int len = strlen(fb.file_path);
    198 				if (fb.file_path[len-1] != '/' && len < PATH_MAX-1) {
    199 						fb.file_path[len] = '/';
    200 						fb.file_path[len+1] = '\0';
    201 				}
    202 		} else {
    203 				FILE *file = fopen(fb.file_path, "rb");
    204 				if (file) {
    205 						fseek(file, 0L, SEEK_END);
    206 						long readsize = ftell(file);
    207 						rewind(file);
    208 
    209 						if (readsize > (long)1.048576e+7) {
    210 								fclose(file);
    211 								die("you are opening a huge file(>10MiB), not allowed");
    212 								return fb;
    213 								// TODO: don't crash
    214 						}
    215 
    216 						fb.len = readsize;
    217 						fb.capacity = readsize + 100;
    218 
    219 						fb.contents = xmalloc(fb.capacity);
    220 						fb.contents[0] = 0;
    221 
    222 						char bom[4] = {0};
    223 						fread(bom, 1, 3, file);
    224 						if (strcmp(bom, "\xEF\xBB\xBF"))
    225 								rewind(file);
    226 						else
    227 								fb.mode |= FB_UTF8_SIGNED;
    228 						fread(fb.contents, 1, readsize, file);
    229 						fclose(file);
    230 
    231 						fb.syntax_index = -1;
    232 				}
    233 		}
    234 
    235 		if (!fb.capacity)
    236 				fb.capacity = 100;
    237 		if (!fb.contents) {
    238 				fb.contents = xmalloc(fb.capacity);
    239 				memset(fb.contents, 0, fb.capacity);
    240 		}
    241 		fb.ub = xmalloc(sizeof(struct undo_buffer) * UNDO_BUFFERS_COUNT);
    242 		fb.search_term = xmalloc(SEARCH_TERM_MAX_LEN);
    243 		fb.non_blocking_search_term = xmalloc(SEARCH_TERM_MAX_LEN);
    244 		memset(fb.ub, 0, sizeof(struct undo_buffer) * UNDO_BUFFERS_COUNT);
    245 		memset(fb.search_term, 0, SEARCH_TERM_MAX_LEN);
    246 		memset(fb.non_blocking_search_term, 0, SEARCH_TERM_MAX_LEN);
    247 		fb.indent_len = default_indent_len;
    248 
    249 		// change line endings
    250 		int offset = 0;
    251 		while((offset = fb_seek_string(&fb, offset, "\r\n")) >= 0)
    252 				fb_remove(&fb, offset, 1, 1, 1);
    253 		offset = 0;
    254 		while((offset = fb_seek_char(&fb, offset, '\r')) >= 0)
    255 				fb_change(&fb, "\n", 1, offset, 1);
    256 
    257 		call_extension(fb_new_file_opened, &fb);
    258 
    259 		call_extension(fb_contents_updated, &fb, 0, FB_CONTENT_INIT);
    260 
    261 		if (res)
    262 				writef_to_status_bar("new fb %s", fb.file_path);
    263 		return fb;
    264 }
    265 
    266 int
    267 fb_new_entry(const char* file_path)
    268 {
    269 		static char full_path[PATH_MAX];
    270 		if (!file_path)
    271 				file_path = "./";
    272 		soft_assert(strlen(file_path) < PATH_MAX, file_path = "./";);
    273 
    274 		char* res = realpath(file_path, full_path);
    275 
    276 		if (available_buffer_slots) {
    277 				if (res) {
    278 						for(int n = 0; n < available_buffer_slots; n++) {
    279 								if (file_buffers[n].contents) {
    280 										if (strcmp(file_buffers[n].file_path, full_path) == 0) {
    281 												writef_to_status_bar("buffer exits");
    282 												return n;
    283 										}
    284 								}
    285 						}
    286 				} else {
    287 						strcpy(full_path, file_path);
    288 				}
    289 
    290 				for(int n = 0; n < available_buffer_slots; n++) {
    291 						if (!file_buffers[n].contents) {
    292 								if (is_file_type(full_path, ".seproj"))
    293 										return open_seproj(fb_new(full_path));
    294 								file_buffers[n] = fb_new(full_path);
    295 								return n;
    296 						}
    297 				}
    298 		}
    299 
    300 		if (is_file_type(full_path, ".seproj"))
    301 				return open_seproj(fb_new(full_path));
    302 
    303 		available_buffer_slots++;
    304 		file_buffers = xrealloc(file_buffers, sizeof(struct file_buffer) * available_buffer_slots);
    305 		file_buffers[available_buffer_slots-1] = fb_new(full_path);
    306 
    307 		return available_buffer_slots-1;
    308 }
    309 
    310 void
    311 fb_destroy(struct file_buffer* fb)
    312 {
    313 		free(fb->ub);
    314 		free(fb->contents);
    315 		free(fb->file_path);
    316 		free(fb->search_term);
    317 		free(fb->non_blocking_search_term);
    318 		*fb = (struct file_buffer){0};
    319 }
    320 
    321 void
    322 fb_insert(struct file_buffer* fb, const char* new_content, const int len, const int offset, int do_not_callback)
    323 {
    324 		soft_assert(fb, return;);
    325 		soft_assert(fb->contents, fb->capacity = 0;);
    326 		soft_assert(offset <= fb->len && offset >= 0,
    327 					fprintf(stderr, "writing past fb '%s'\n", fb->file_path);
    328 					return;
    329 				);
    330 
    331 		if (fb->len + len >= fb->capacity) {
    332 				fb->capacity = fb->len + len + 256;
    333 				fb->contents = xrealloc(fb->contents, fb->capacity);
    334 		}
    335 		if (offset < fb->len)
    336 				memmove(fb->contents+offset+len, fb->contents+offset, fb->len-offset);
    337 		fb->len += len;
    338 
    339 		memcpy(fb->contents+offset, new_content, len);
    340 		if (!do_not_callback)
    341 				call_extension(fb_contents_updated, fb, offset, FB_CONTENT_NORMAL_EDIT);
    342 }
    343 
    344 void
    345 fb_change(struct file_buffer* fb, const char* new_content, const int len, const int offset, int do_not_callback)
    346 {
    347 		soft_assert(offset <= fb->len && offset >= 0, return;);
    348 
    349 		if (offset + len > fb->len) {
    350 				fb->len = offset + len;
    351 				if (fb->len >= fb->capacity) {
    352 						fb->capacity = fb->len + len + 256;
    353 						fb->contents = xrealloc(fb->contents, fb->capacity);
    354 				}
    355 		}
    356 
    357 		memcpy(fb->contents+offset, new_content, len);
    358 		if (!do_not_callback)
    359 				call_extension(fb_contents_updated, fb, offset, FB_CONTENT_NORMAL_EDIT);
    360 }
    361 
    362 int
    363 fb_remove(struct file_buffer* fb, int offset, int len, int do_not_calculate_charsize, int do_not_callback)
    364 {
    365 		LIMIT(offset, 0, fb->len-1);
    366 		if (len == 0) return 0;
    367 		soft_assert(fb->contents, return 0;);
    368 		soft_assert(offset + len <= fb->len, return 0;);
    369 
    370 		int removed_len = 0;
    371 		if (do_not_calculate_charsize) {
    372 				removed_len = len;
    373 		} else {
    374 				while (len--) {
    375 						int charsize = utf8_decode_buffer(fb->contents + offset, fb->len - offset, NULL);
    376 						if (fb->len - charsize < 0)
    377 								return 0;
    378 						removed_len += charsize;
    379 				}
    380 		}
    381 		fb->len -= removed_len;
    382 		memmove(fb->contents+offset, fb->contents+offset+removed_len, fb->len-offset);
    383 		if (!do_not_callback)
    384 				call_extension(fb_contents_updated, fb, offset, FB_CONTENT_NORMAL_EDIT);
    385 		return removed_len;
    386 }
    387 
    388 void
    389 wb_copy_ub_to_current(struct window_buffer* wb)
    390 {
    391 		struct file_buffer* fb = get_fb(wb);
    392 		struct undo_buffer* cub = &fb->ub[fb->current_undo_buffer];
    393 
    394 		fb->contents = xrealloc(fb->contents, cub->capacity);
    395 		memcpy(fb->contents, cub->contents, cub->capacity);
    396 		fb->len = cub->len;
    397 		fb->capacity = cub->capacity;
    398 
    399 		wb_move_to_offset(wb, cub->cursor_offset, CURSOR_SNAPPED);
    400 		//TODO: remove y_scroll from undo buffer
    401 		wb->y_scroll = cub->y_scroll;
    402 }
    403 
    404 
    405 void
    406 fb_undo(struct file_buffer* fb)
    407 {
    408 		if (fb->current_undo_buffer == 0) {
    409 				writef_to_status_bar("end of undo buffer");
    410 				return;
    411 		}
    412 		fb->current_undo_buffer--;
    413 		fb->available_redo_buffers++;
    414 
    415 		wb_copy_ub_to_current(focused_window);
    416 		writef_to_status_bar("undo");
    417 }
    418 
    419 void
    420 fb_redo(struct file_buffer* fb)
    421 {
    422 		if (fb->available_redo_buffers == 0) {
    423 				writef_to_status_bar("end of redo buffer");
    424 				return;
    425 		}
    426 		fb->available_redo_buffers--;
    427 		fb->current_undo_buffer++;
    428 
    429 		wb_copy_ub_to_current(focused_window);
    430 		writef_to_status_bar("redo");
    431 }
    432 
    433 void
    434 fb_add_to_undo(struct file_buffer* fb, int offset, enum buffer_content_reason reason)
    435 {
    436 		static time_t last_normal_edit;
    437 		static int edits;
    438 
    439 		if (reason == FB_CONTENT_CURSOR_MOVE) {
    440 				struct undo_buffer* cub = &fb->ub[fb->current_undo_buffer];
    441 				cub->cursor_offset = offset;
    442 				if (focused_window)
    443 						cub->y_scroll = focused_window->y_scroll;
    444 				else
    445 						cub->y_scroll = 0;
    446 				return;
    447 		}
    448 
    449 		if (reason == FB_CONTENT_NORMAL_EDIT) {
    450 				time_t previous_time = last_normal_edit;
    451 				last_normal_edit = time(NULL);
    452 
    453 				if (last_normal_edit - previous_time < 2 && edits < 30) {
    454 						edits++;
    455 						goto copy_undo_buffer;
    456 				} else {
    457 						edits = 0;
    458 				}
    459 		} else if (reason == FB_CONTENT_INIT) {
    460 				goto copy_undo_buffer;
    461 		}
    462 
    463 		fb->available_redo_buffers = 0;
    464 		if (fb->current_undo_buffer == UNDO_BUFFERS_COUNT-1) {
    465 				char* begin_buffer = fb->ub[0].contents;
    466 				memmove(fb->ub, &(fb->ub[1]), (UNDO_BUFFERS_COUNT-1) * sizeof(struct undo_buffer));
    467 				fb->ub[fb->current_undo_buffer].contents = begin_buffer;
    468 		} else {
    469 				fb->current_undo_buffer++;
    470 		}
    471 
    472 copy_undo_buffer: ;
    473 		struct undo_buffer* cub = fb->ub + fb->current_undo_buffer;
    474 
    475 		cub->contents = xrealloc(cub->contents, fb->capacity);
    476 		memcpy(cub->contents, fb->contents, fb->capacity);
    477 		cub->len = fb->len;
    478 		cub->capacity = fb->capacity;
    479 		cub->cursor_offset = offset;
    480 		if (focused_window)
    481 				cub->y_scroll = focused_window->y_scroll;
    482 		else
    483 				cub->y_scroll = 0;
    484 }
    485 
    486 
    487 char*
    488 fb_get_string_between_offsets(struct file_buffer* fb, int start, int end)
    489 {
    490 		int len = end - start;
    491 
    492 		char* string = xmalloc(len + 1);
    493 		memcpy(string, fb->contents+start, len);
    494 		string[len] = 0;
    495 		return string;
    496 }
    497 
    498 char*
    499 fb_get_selection(struct file_buffer* fb, int* selection_len)
    500 {
    501 		if (!(fb->mode & FB_SELECTION_ON))
    502 				return NULL;
    503 
    504 		int start, end;
    505 		if (fb_is_selection_start_top_left(fb)) {
    506 				start = fb->s1o;
    507 				end = fb->s2o+1;
    508 		} else {
    509 				start = fb->s2o;
    510 				end = fb->s1o+1;
    511 		}
    512 		if (selection_len)
    513 				*selection_len = end - start;
    514 		return fb_get_string_between_offsets(fb, start, end);
    515 }
    516 
    517 
    518 int
    519 fb_is_selection_start_top_left(const struct file_buffer* fb)
    520 {
    521 		return (fb->s1o <= fb->s2o) ? 1 : 0;
    522 }
    523 
    524 void
    525 fb_remove_selection(struct file_buffer* buffer)
    526 {
    527 		if (!(buffer->mode & FB_SELECTION_ON))
    528 				return;
    529 
    530 		int start, end, len;
    531 		if (fb_is_selection_start_top_left(buffer)) {
    532 				start = buffer->s1o;
    533 				end = buffer->s2o+1;
    534 		} else {
    535 				start = buffer->s2o;
    536 				end = buffer->s1o+1;
    537 		}
    538 		len = end - start;
    539 		fb_remove(buffer, start, len, 1, 1);
    540 		call_extension(fb_contents_updated, buffer, start, FB_CONTENT_BIG_CHANGE);
    541 }
    542 
    543 char*
    544 fb_get_line_at_offset(const struct file_buffer* fb, int offset)
    545 {
    546 		int start = fb_seek_char_backwards(fb, offset, '\n');
    547 		if (start < 0) start = 0;
    548 		int end = fb_seek_char(fb, offset, '\n');
    549 		if (end < 0) end = fb->len-1;
    550 
    551 		int len = end - start;
    552 
    553 		char* res = xmalloc(len + 1);
    554 		if (len > 0)
    555 				memcpy(res, fb->contents+start, len);
    556 		res[len] = 0;
    557 		return res;
    558 }
    559 
    560 void
    561 fb_offset_to_xy(struct file_buffer* fb, int offset, int maxx, int y_scroll, int* cx, int* cy, int* xscroll)
    562 {
    563 		*cx = *cy = *xscroll = 0;
    564 		soft_assert(fb, return;);
    565 
    566 		if (fb->len <= 0)
    567 				return;
    568 		LIMIT(offset, 0, fb->len);
    569 
    570 		char* repl = fb->contents;
    571 		char* last = repl + offset;
    572 
    573 		char* new_repl;
    574 		if (wrap_buffer && maxx > 0) {
    575 				int yscroll = 0;
    576 				while ((new_repl = memchr(repl, '\n', last - repl))) {
    577 						if (++yscroll >= y_scroll)
    578 								break;
    579 						repl = new_repl+1;
    580 				}
    581 				*cy = yscroll - y_scroll;
    582 		} else {
    583 				while ((new_repl = memchr(repl, '\n', last - repl))) {
    584 						repl = new_repl+1;
    585 						*cy += 1;
    586 				}
    587 				*cy -= y_scroll;
    588 		}
    589 
    590 		while (repl < last) {
    591 				if (wrap_buffer && maxx > 0 && (*repl == '\n' || *cx >= maxx)) {
    592 						*cy += 1;
    593 						*cx = 0;
    594 						repl++;
    595 						continue;
    596 				}
    597 				if (*repl == '\t') {
    598 						repl++;
    599 						if (*cx <= 0) *cx += 1;
    600 						while (*cx % tabspaces != 0) *cx += 1;
    601 						*cx += 1;
    602 						continue;
    603 				}
    604 				rune_t u;
    605 				repl += utf8_decode_buffer(repl, last - repl, &u);
    606 				*cx += wcwidth(u);
    607 		}
    608 
    609 		// TODO: make customizable
    610 		// -1 = wrap, >= 0 is padding or something like that
    611 		const int padding = 3;
    612 
    613 		if ((*cx - maxx) + padding > 0)
    614 				*xscroll = (*cx - maxx) + padding;
    615 }
    616 
    617 ////////////////////////////////////////////////
    618 // Window buffer
    619 //
    620 
    621 struct window_buffer
    622 wb_new(int fb_index)
    623 {
    624 		struct window_buffer wb = {0};
    625 		wb.fb_index = fb_index;
    626 		if (path_is_folder(get_fb(&wb)->file_path)) {
    627 				wb.mode = WB_FILE_BROWSER;
    628 				writef_to_status_bar("opened file browser %s", get_fb(&wb)->file_path);
    629 		}
    630 
    631 		return wb;
    632 }
    633 
    634 void
    635 wb_move_on_line(struct window_buffer* wb, int amount, enum cursor_reason callback_reason)
    636 {
    637 		const struct file_buffer* fb = get_fb(wb);
    638 		if (fb->len <= 0)
    639 				return;
    640 
    641 		if (amount < 0) {
    642 				while (wb->cursor_offset > 0 && fb->contents[wb->cursor_offset - 1] != '\n' && amount < 0) {
    643 						wb->cursor_offset--;
    644 						if ((fb->contents[wb->cursor_offset] & 0xC0) == 0x80) // if byte starts with 0b10
    645 								continue; // byte is UTF-8 extender
    646 						amount++;
    647 				}
    648 				LIMIT(wb->cursor_offset, 0, fb->len);
    649 		} else if (amount > 0) {
    650 				for (int charsize = 0;
    651 					 wb->cursor_offset < fb->len && amount > 0 && fb->contents[wb->cursor_offset + charsize] != '\n';
    652 					 wb->cursor_offset += charsize, amount--) {
    653 						rune_t u;
    654 						charsize = utf8_decode_buffer(fb->contents + wb->cursor_offset, fb->len - wb->cursor_offset, &u);
    655 						if (u != '\n' && u != '\t')
    656 								if (wcwidth(u) <= 0)
    657 										amount++;
    658 						if (wb->cursor_offset + charsize > fb->len)
    659 								break;
    660 				}
    661 		}
    662 
    663 		if (callback_reason)
    664 				call_extension(wb_cursor_movement, wb, callback_reason);
    665 }
    666 
    667 void
    668 wb_move_offset_relative(struct window_buffer* wb, int amount, enum cursor_reason callback_reason)
    669 {
    670 		//NOTE: this does not check if the character on this offset is the start of a valid utf8 char
    671 		const struct file_buffer* fb = get_fb((wb));
    672 		if (fb->len <= 0)
    673 				return;
    674 		wb->cursor_offset += amount;
    675 		LIMIT(wb->cursor_offset, 0, fb->len);
    676 
    677 		if (callback_reason)
    678 				call_extension(wb_cursor_movement, wb, callback_reason);
    679 }
    680 
    681 void
    682 wb_move_lines(struct window_buffer* wb, int amount, enum cursor_reason callback_reason)
    683 {
    684 		const struct file_buffer* fb = get_fb((wb));
    685 		if (fb->len <= 0)
    686 				return;
    687 		int offset = wb->cursor_offset;
    688 		if (amount > 0) {
    689 				while (amount-- && offset >= 0) {
    690 						int new_offset = fb_seek_char(fb, offset, '\n');
    691 						if (new_offset < 0) {
    692 								offset = fb->len;
    693 								break;
    694 						}
    695 						offset = new_offset+1;
    696 				}
    697 		} else if (amount < 0) {
    698 				while (amount++ && offset >= 0)
    699 						offset = fb_seek_char_backwards(fb, offset, '\n')-1;
    700 		}
    701 		wb_move_to_offset(wb, offset, callback_reason);
    702 }
    703 
    704 void
    705 wb_move_to_offset(struct window_buffer* wb, int offset, enum cursor_reason callback_reason)
    706 {
    707 		//NOTE: this does not check if the character on this offset is the start of a valid utf8 char
    708 		const struct file_buffer* fb = get_fb((wb));
    709 		if (fb->len <= 0)
    710 				return;
    711 		LIMIT(offset, 0, fb->len);
    712 		wb->cursor_offset = offset;
    713 
    714 		if (callback_reason)
    715 				call_extension(wb_cursor_movement, wb, callback_reason);
    716 }
    717 
    718 void
    719 wb_move_to_x(struct window_buffer* wb, int x, enum cursor_reason callback_reason)
    720 {
    721 		soft_assert(wb, return;);
    722 		struct file_buffer* fb = get_fb(wb);
    723 
    724 		int offset = fb_seek_char_backwards(fb, wb->cursor_offset, '\n');
    725 		if (offset < 0)
    726 				offset = 0;
    727 		wb_move_to_offset(wb, offset, 0);
    728 
    729 		int x_counter = 0;
    730 
    731 		while (offset < fb->len) {
    732 				if (fb->contents[offset] == '\t') {
    733 						offset++;
    734 						if (x_counter <= 0) x_counter += 1;
    735 						while (x_counter % tabspaces != 0) x_counter += 1;
    736 						x_counter += 1;
    737 						continue;
    738 				} else if (fb->contents[offset] == '\n') {
    739 						break;
    740 				}
    741 				rune_t u = 0;
    742 				int charsize = utf8_decode_buffer(fb->contents + offset, fb->len - offset, &u);
    743 				x_counter += wcwidth(u);
    744 				if (x_counter <= x) {
    745 						offset += charsize;
    746 						if (x_counter == x)
    747 								break;
    748 				} else {
    749 						break;
    750 				}
    751 		}
    752 		wb_move_to_offset(wb, offset, callback_reason);
    753 }
    754 
    755 ////////////////////////////////////////////////
    756 // Window split node
    757 //
    758 
    759 static int
    760 is_correct_mode(enum window_split_mode mode, enum move_directons move)
    761 {
    762 		if (move == MOVE_RIGHT || move == MOVE_LEFT)
    763 				return (mode == WINDOW_HORISONTAL);
    764 		if (move == MOVE_UP || move == MOVE_DOWN)
    765 				return (mode == WINDOW_VERTICAL);
    766 		return 0;
    767 }
    768 
    769 void
    770 window_node_split(struct window_split_node* parent, float ratio, enum window_split_mode mode)
    771 {
    772 		soft_assert(parent, return;);
    773 		soft_assert(parent->mode == WINDOW_SINGULAR, return;);
    774 		soft_assert(mode != WINDOW_SINGULAR, return;);
    775 
    776 		if ((parent->maxx - parent->minx < MIN_WINDOW_SPLIT_SIZE_HORISONTAL && mode == WINDOW_HORISONTAL)
    777 			|| (parent->maxy - parent->miny < MIN_WINDOW_SPLIT_SIZE_VERTICAL && mode == WINDOW_VERTICAL))
    778 				return;
    779 
    780 		parent->node1 = xmalloc(sizeof(struct window_split_node));
    781 		*parent->node1 = *parent;
    782 		parent->node1->search = xmalloc(SEARCH_TERM_MAX_LEN);
    783 		parent->node1->parent = parent;
    784 		parent->node1->node1 = NULL;
    785 		parent->node1->node2 = NULL;
    786 
    787 
    788 		parent->node2 = xmalloc(sizeof(struct window_split_node));
    789 		*parent->node2 = *parent;
    790 		parent->node2->search = xmalloc(SEARCH_TERM_MAX_LEN);
    791 		parent->node2->parent = parent;
    792 		parent->node2->node1 = NULL;
    793 		parent->node2->node2 = NULL;
    794 
    795 		if (parent->mode == WINDOW_HORISONTAL) {
    796 				// NOTE: if the window resizing is changed, change in draw tree function as well
    797 				int middlex = ((float)(parent->maxx - parent->minx) * parent->ratio) + parent->minx;
    798 				parent->node1->minx = parent->minx;
    799 				parent->node1->miny = parent->miny;
    800 				parent->node1->maxx = middlex;
    801 				parent->node1->maxy = parent->maxy;
    802 				parent->node2->minx = middlex+2;
    803 				parent->node2->miny = parent->miny;
    804 				parent->node2->maxx = parent->maxx;
    805 				parent->node2->maxy = parent->maxy;
    806 		} else if (parent->mode == WINDOW_VERTICAL) {
    807 				// NOTE: if the window resizing is changed, change in draw tree function as well
    808 				int middley = ((float)(parent->maxy - parent->miny) * parent->ratio) + parent->miny;
    809 				parent->node1->minx = parent->minx;
    810 				parent->node1->miny = parent->miny;
    811 				parent->node1->maxx = parent->maxx;
    812 				parent->node1->maxy = middley;
    813 				parent->node2->minx = parent->miny;
    814 				parent->node2->miny = middley;
    815 				parent->node2->maxx = parent->maxx;
    816 				parent->node2->maxy = parent->maxy;
    817 		}
    818 
    819 		parent->mode = mode;
    820 		parent->ratio = ratio;
    821 		parent->wb = (struct window_buffer){0};
    822 }
    823 
    824 struct window_split_node*
    825 window_node_delete(struct window_split_node* node)
    826 {
    827 		if (!node->parent) {
    828 				writef_to_status_bar("can't close root winodw");
    829 				return node;
    830 		}
    831 		struct window_split_node* old = node;
    832 		node = node->parent;
    833 		struct window_split_node* other = (node->node1 == old) ? node->node2 : node->node1;
    834 		free(old->search);
    835 		free(old);
    836 
    837 		struct window_split_node* parent = node->parent;
    838 		*node = *other;
    839 		if (other->mode != WINDOW_SINGULAR) {
    840 				other->node1->parent = node;
    841 				other->node2->parent = node;
    842 		}
    843 		free(other);
    844 		node->parent = parent;
    845 
    846 		return node;
    847 }
    848 
    849 void
    850 window_node_draw_tree_to_screen(struct window_split_node* root, int minx, int miny, int maxx, int maxy)
    851 {
    852 		soft_assert(root, return;);
    853 
    854 		if (root->mode == WINDOW_SINGULAR) {
    855 				LIMIT(maxx, 0, screen.col-1);
    856 				LIMIT(maxy, 0, screen.row-1);
    857 				LIMIT(minx, 0, maxx);
    858 				LIMIT(miny, 0, maxy);
    859 				root->minx = minx;
    860 				root->miny = miny;
    861 				root->maxx = maxx;
    862 				root->maxy = maxy;
    863 				if (root->wb.mode != 0) {
    864 						int wn_custom_window_draw_callback_exists = 0;
    865 						extension_callback_exists(wn_custom_window_draw, wn_custom_window_draw_callback_exists = 1;);
    866 						soft_assert(wn_custom_window_draw_callback_exists, return;);
    867 
    868 						call_extension(wn_custom_window_draw, root);
    869 
    870 						return;
    871 				} else {
    872 						window_node_draw_to_screen(root);
    873 				}
    874 		} else if (root->mode == WINDOW_HORISONTAL) {
    875 				// NOTE: if the window resizing is changed, change in split function as well
    876 				int middlex = ((float)(maxx - minx) * root->ratio) + minx;
    877 
    878 				// print seperator
    879 				screen_set_region(middlex+1, miny, middlex+1, maxy, L'│');
    880 
    881 				window_node_draw_tree_to_screen(root->node1, minx, miny, middlex, maxy);
    882 				window_node_draw_tree_to_screen(root->node2, middlex+2, miny, maxx, maxy);
    883 
    884 				for (int y = miny; y < maxy+1; y++)
    885 						xdrawline(middlex+1, y, middlex+2);
    886 		} else if (root->mode == WINDOW_VERTICAL) {
    887 				// NOTE: if the window resizing is changed, change in split function as well
    888 				int middley = ((float)(maxy - miny) * root->ratio) + miny;
    889 
    890 				window_node_draw_tree_to_screen(root->node1, minx, miny, maxx, middley);
    891 				window_node_draw_tree_to_screen(root->node2, minx, middley, maxx, maxy);
    892 		}
    893 }
    894 
    895 void
    896 window_node_move_all_cursors_on_same_fb(struct window_split_node* root, struct window_split_node* excluded, int fb_index, int offset,
    897 										void(movement)(struct window_buffer*, int, enum cursor_reason),
    898 										int move, enum cursor_reason reason)
    899 {
    900 		if (root->mode == WINDOW_SINGULAR) {
    901 				if (root->wb.fb_index == fb_index && root->wb.cursor_offset >= offset && root != excluded)
    902 						movement(&root->wb, move, reason);
    903 		} else {
    904 				window_node_move_all_cursors_on_same_fb(root->node1, excluded, fb_index, offset, movement, move, reason);
    905 				window_node_move_all_cursors_on_same_fb(root->node2, excluded, fb_index, offset, movement, move, reason);
    906 		}
    907 }
    908 
    909 void
    910 window_node_move_all_yscrolls(struct window_split_node* root, struct window_split_node* excluded, int fb_index, int offset, int move)
    911 {
    912 		if (root->mode == WINDOW_SINGULAR) {
    913 				if (root->wb.fb_index == fb_index && root->wb.cursor_offset >= offset && root != excluded)
    914 						root->wb.y_scroll += move;
    915 		} else {
    916 				window_node_move_all_yscrolls(root->node1, excluded, fb_index, offset, move);
    917 				window_node_move_all_yscrolls(root->node2, excluded, fb_index, offset, move);
    918 		}
    919 }
    920 
    921 int
    922 window_other_nodes_contain_fb(struct window_split_node* node, struct window_split_node* root)
    923 {
    924 		if (root->mode == WINDOW_SINGULAR)
    925 				return (root->wb.fb_index == node->wb.fb_index && root != node);
    926 
    927 		return (window_other_nodes_contain_fb(node, root->node1) ||
    928 				window_other_nodes_contain_fb(node, root->node2));
    929 }
    930 
    931 // TODO: create a distance type function and use that instead (from the current cursor position)?
    932 // struct window_split_node* wincdow_closest(root, node ignore(get maxx/minx etc from this and make sure the it's outside the ignored node), enum move_directions move, int cx, int cy)
    933 struct window_split_node*
    934 window_switch_to_window(struct window_split_node* node, enum move_directons move)
    935 {
    936 		soft_assert(node, return &root_node;);
    937 		if (!node->parent) return node;
    938 		soft_assert(node->mode == WINDOW_SINGULAR,
    939 					while(node->mode != WINDOW_SINGULAR)
    940 							node = node->node1;
    941 					return node;
    942 				);
    943 		struct window_split_node* old_node = node;
    944 
    945 		if (move == MOVE_RIGHT || move == MOVE_DOWN) {
    946 				// traverse up the tree to the right
    947 				for (; node->parent; node = node->parent) {
    948 						if (is_correct_mode(node->parent->mode, move) && node->parent->node1 == node) {
    949 								// traverse down until a screen is found
    950 								node = node->parent->node2;
    951 								while(node->mode != WINDOW_SINGULAR)
    952 										node = node->node1;
    953 
    954 								return node;
    955 						}
    956 				}
    957 		} else if (move == MOVE_LEFT || move == MOVE_UP) {
    958 				// traverse up the tree to the left
    959 				for (; node->parent; node = node->parent) {
    960 						if (is_correct_mode(node->parent->mode, move) && node->parent->node2 == node) {
    961 								// traverse down until a screen is found
    962 								node = node->parent->node1;
    963 								while(node->mode != WINDOW_SINGULAR)
    964 										node = node->node2;
    965 
    966 								return node;
    967 						}
    968 				}
    969 		}
    970 
    971 		return old_node;
    972 }
    973 
    974 void
    975 window_node_resize(struct window_split_node* node, enum move_directons move, float amount)
    976 {
    977 		for (; node; node = node->parent) {
    978 				if (is_correct_mode(node->mode, move)) {
    979 						float amount = (move == MOVE_RIGHT || move == MOVE_LEFT) ? 0.1f : 0.05f;
    980 						if (move == MOVE_RIGHT || move == MOVE_DOWN) amount = -amount;
    981 						node->ratio -= amount;
    982 						LIMIT(node->ratio, 0.001f, 0.95f);
    983 						return;
    984 				}
    985 		}
    986 }
    987 
    988 void
    989 window_node_resize_absolute(struct window_split_node* node, enum move_directons move, float amount)
    990 {
    991 		for (; node; node = node->parent) {
    992 				if (is_correct_mode(node->mode, move)) {
    993 						node->ratio = amount;
    994 						LIMIT(node->ratio, 0.001f, 0.95f);
    995 						return;
    996 				}
    997 		}
    998 }