choose_one_of_selection.h (16745B)
1 #ifndef CHOOSE_ONE_OF_SELECTION_H_ 2 #define CHOOSE_ONE_OF_SELECTION_H_ 3 4 #include "../../config.h" 5 #include "../../extension.h" 6 #include <ctype.h> 7 #include <dirent.h> 8 9 static int draw_dir(struct window_split_node* win); 10 static int draw_search_buffers(struct window_split_node* wn); 11 static int draw_search_keyword_in_all_buffers(struct window_split_node* wn); 12 13 static int file_buffer_keypress_override_callback(int* skip_keypress_callback, struct window_split_node* wn, KeySym ksym, int modkey, const char* buf, int len); 14 static int choose_one_of_selection_keypress_override_callback(int* skip_keypress_callback, struct window_split_node* wn, KeySym ksym, int modkey, const char* buf, int len); 15 16 static const struct extension file_browser = { 17 .wn_custom_window_draw = draw_dir, 18 .wn_custom_window_keypress_override = file_buffer_keypress_override_callback, 19 }; 20 21 static const struct extension search_open_fb = { 22 .wn_custom_window_draw = draw_search_buffers, 23 .wn_custom_window_keypress_override = choose_one_of_selection_keypress_override_callback 24 }; 25 26 static const struct extension search_keywords_open_fb = { 27 .wn_custom_window_draw = draw_search_keyword_in_all_buffers, 28 .wn_custom_window_keypress_override = choose_one_of_selection_keypress_override_callback 29 }; 30 31 struct keyword_pos { 32 int offset, fb_index; 33 }; 34 35 static void choose_one_of_selection(const char* prefix, const char* search, const char* err, 36 const char*(*get_next_element)(const char*, const char*, int* offset, struct glyph* attr, void* data), 37 int* selected_line, int minx, int miny, int maxx, int maxy, int focused); 38 39 // TODO: system for custom buffers 40 // and allow for input overrides 41 // have theese in their own file 42 static const char* file_browser_next_item(const char* path, const char* search, int* offset, struct glyph* attr, void* data); 43 // data pointer will give the file buffer of the current item 44 static const char* buffer_search_next_item(const char* tmp, const char* search, int* offset, struct glyph* attr, void* data); 45 // data pointer will give the keyword_pos of the current item 46 static const char* buffers_search_keyword_next_item(const char* tmp, const char* search, int* offset, struct glyph* attr, void* data); 47 48 const char* 49 file_browser_next_item(const char* path, const char* search, int* offset, struct glyph* attr, void* data) 50 { 51 static char filename_search[PATH_MAX]; 52 static char filename[PATH_MAX]; 53 static char full_path[PATH_MAX]; 54 static DIR* dir; 55 if (!path || !search) { 56 if (dir) { 57 closedir(dir); 58 dir = NULL; 59 } 60 return NULL; 61 } 62 if (!dir) 63 dir = opendir(path); 64 int len = strlen(search); 65 66 struct dirent *folder; 67 while((folder = readdir(dir))) { 68 strcpy(filename, folder->d_name); 69 strcpy(full_path, path); 70 strcat(full_path, filename); 71 if (path_is_folder(full_path)) { 72 strcat(filename, "/"); 73 if (attr) 74 attr->fg = path_color; 75 } else { 76 if (attr) 77 attr->fg = default_attributes.fg; 78 } 79 80 strcpy(filename_search, filename); 81 const char* s_repl = search; 82 while(!isupper(*s_repl++)) { 83 if (*s_repl == 0) { 84 for (char* fs = filename_search; *fs; fs++) 85 *fs = tolower(*fs); 86 break; 87 } 88 } 89 90 int f_len = strlen(filename_search); 91 char* search_start = filename_search; 92 if (!*search) 93 goto search_match; 94 while((search_start = memchr(search_start, *search, f_len))) { 95 if (memcmp(search_start, search, len) == 0) { 96 search_match: 97 if (search[0] != '.' && folder->d_name[0] == '.') 98 break; 99 if (strcmp(filename, "./") == 0 || strcmp(filename, "../") == 0) 100 break; 101 if (offset) 102 *offset = search_start - filename_search; 103 return filename; 104 } 105 search_start++; 106 } 107 } 108 *filename = *full_path = 0; 109 closedir(dir); 110 dir = NULL; 111 return NULL; 112 } 113 114 const char* 115 buffer_search_next_item(const char* tmp, const char* search, int* offset, struct glyph* attr, void* data) 116 { 117 static struct window_buffer wb; 118 static char file_path[PATH_MAX]; 119 static int quit_next = 0; 120 if (!tmp || !search) { 121 wb.fb_index = 0; 122 quit_next = 0; 123 return NULL; 124 } 125 126 int len = strlen(search); 127 for(;;) { 128 if (quit_next) { 129 wb.fb_index = 0; 130 quit_next = 0; 131 return NULL; 132 } 133 struct file_buffer* fb = get_fb(&wb); 134 int last_fb_index = wb.fb_index; 135 wb.fb_index++; 136 get_fb(&wb); 137 if (wb.fb_index <= last_fb_index) 138 quit_next = 1; 139 140 strcpy(file_path, fb->file_path); 141 142 const char* s_repl = search; 143 while(!isupper(*s_repl++)) { 144 if (*s_repl == 0) { 145 for (char* fs = file_path; *fs; fs++) 146 *fs = tolower(*fs); 147 break; 148 } 149 } 150 char* search_start = file_path; 151 152 if (!len) 153 goto search_match; 154 while ((search_start = strchr(search_start+1, *search))) { 155 if (memcmp(search_start, search, len) == 0) { 156 search_match: 157 if (offset) 158 *offset = search_start - file_path; 159 if (data) 160 *(int*)data = last_fb_index; 161 return fb->file_path; 162 } 163 } 164 } 165 } 166 167 const char* 168 buffers_search_keyword_next_item(const char* tmp, const char* search, int* offset, struct glyph* attr, void* data) 169 { 170 static char item[LINE_MAX_LEN]; 171 static struct window_buffer wb; 172 static int pos = -1; 173 if (!tmp || !search) { 174 wb.fb_index = 0; 175 pos = -1; 176 return NULL; 177 } 178 int len = strlen(search); 179 if (!len) 180 return NULL; 181 182 for (;;) { 183 struct file_buffer* fb = get_fb(&wb); 184 185 if ((pos = fb_seek_string(fb, pos+1, search)) >= 0) { 186 const char* filename = strrchr(fb->file_path, '/')+1; 187 if (!filename) 188 filename = fb->file_path; 189 190 int tmp, y; 191 fb_offset_to_xy(fb, pos, 0, wb.y_scroll, &tmp, &y, &tmp); 192 193 snprintf(item, LINE_MAX_LEN, "%s:%d: ", filename, y); 194 int itemlen = strlen(item); 195 196 char* line = fb_get_line_at_offset(fb, pos); 197 char* line_no_whitespace = line; 198 if (!isspace(*search)) 199 while(isspace(*line_no_whitespace)) line_no_whitespace++; 200 201 snprintf(item + itemlen, LINE_MAX_LEN - itemlen, "%s", line_no_whitespace); 202 203 int line_start = fb_seek_char_backwards(fb, pos, '\n') + (line_no_whitespace - line); 204 free(line); 205 206 if (line_start < 0) 207 line_start = 0; 208 if (offset) 209 *offset = (pos - line_start) + itemlen; 210 if (data) 211 *(struct keyword_pos*)data = (struct keyword_pos){.offset = pos, .fb_index = wb.fb_index}; 212 pos = fb_seek_char(fb, pos+1, '\n'); 213 return item; 214 } else { 215 int last_fb_index = wb.fb_index; 216 wb.fb_index++; 217 get_fb(&wb); 218 if (wb.fb_index <= last_fb_index) 219 break; 220 pos = -1; 221 } 222 } 223 wb.fb_index = 0; 224 pos = -1; 225 return NULL; 226 } 227 228 static void 229 choose_one_of_selection(const char* prefix, const char* search, const char* err, 230 const char*(*get_next_element)(const char*, const char*, int* offset, struct glyph* attr, void* data), 231 int* selected_line, int minx, int miny, int maxx, int maxy, int focused) 232 { 233 soft_assert(prefix, prefix = "ERROR";); 234 soft_assert(search, search = "ERROR";); 235 soft_assert(err, search = "ERROR";); 236 soft_assert(selected_line, static int tmp; selected_line = &tmp;); 237 soft_assert(get_next_element, die("function choose_one_of_selection REQUIRES get_next_element to be a valid pointer");); 238 239 // change background color 240 global_attr.bg = alternate_bg_dark; 241 screen_set_region(minx, miny+1, maxx, maxy, ' '); 242 global_attr = default_attributes; 243 get_next_element(NULL, NULL, NULL, NULL, NULL); 244 245 int len = strlen(search); 246 247 // count folders to get scroll 248 int folder_lines = maxy - miny - 2; 249 int elements = 0; 250 int tmp_offset; 251 int limit = MAX(*selected_line, 999); 252 while(get_next_element(prefix, search, &tmp_offset, NULL, NULL) && elements <= limit) 253 elements++; 254 get_next_element(NULL, NULL, NULL, NULL, NULL); 255 *selected_line = MIN(*selected_line, elements-1); 256 int sel_local = *selected_line; 257 258 // print num of files 259 char count[256]; 260 if (elements >= 1000) 261 snprintf(count, sizeof(count), "[>999:%2d] ", *selected_line+1); 262 else if (*selected_line > folder_lines) 263 snprintf(count, sizeof(count), "^[%3d:%2d] ", elements, *selected_line+1); 264 else if (elements-1 > folder_lines) 265 snprintf(count, sizeof(count), "ˇ[%3d:%2d] ", elements, *selected_line+1); 266 else 267 snprintf(count, sizeof(count), " [%3d:%2d] ", elements, *selected_line+1); 268 269 // print search term with prefix in front of it 270 // prefix is in path_color 271 global_attr.fg = path_color; 272 int new_x = write_string(count, miny, minx, maxx+1); 273 new_x = write_string(prefix, miny, new_x, maxx+1); 274 global_attr = default_attributes; 275 new_x = write_string(search, miny, new_x, maxx+1); 276 277 // print elements 278 int start_miny = miny; 279 int offset; 280 elements--; 281 miny++; 282 global_attr = default_attributes; 283 global_attr.bg = alternate_bg_dark; 284 const char* element; 285 while(miny <= maxy && (element = get_next_element(prefix, search, &offset, &global_attr, NULL))) { 286 if (elements > folder_lines && sel_local > folder_lines) { 287 elements--; 288 sel_local--; 289 continue; 290 } 291 write_string(element, miny, minx, maxx+1); 292 293 // change the color to highlight search term 294 for (int i = minx + offset; i < minx + len + offset && i < maxx+1; i++) 295 screen_set_attr(i, miny)->fg = highlight_color; 296 // change the background of the selected line 297 if (miny - start_miny - 1 == sel_local) 298 for (int i = minx; i < maxx+1; i++) 299 screen_set_attr(i, miny)->bg = selection_bg; 300 miny++; 301 } 302 303 if (elements < 0) { 304 global_attr = default_attributes; 305 global_attr.fg = warning_color; 306 write_string(err, start_miny, new_x, maxx+1); 307 } 308 309 // draw 310 311 for (int y = start_miny; y < maxy+1; y++) 312 xdrawline(minx, y, maxx+1); 313 314 draw_horisontal_line(maxy-1, minx, maxx); 315 xdrawcursor(new_x, start_miny, focused); 316 317 global_attr = default_attributes; 318 } 319 320 static int 321 draw_dir(struct window_split_node* wn) 322 { 323 soft_assert(wn->wb.mode < WB_MODES_END, return 1;); 324 if (wn->wb.mode != WB_FILE_BROWSER) return 0; 325 326 int focused = &wn->wb == focused_window; 327 struct file_buffer* fb = get_fb(&wn->wb); 328 char* folder = file_path_get_path(fb->file_path); 329 330 fb_change(fb, "\0", 1, fb->len, 1); 331 if (fb->len > 0) fb->len--; 332 333 choose_one_of_selection(folder, fb->contents, " [Create New File]", file_browser_next_item, 334 &wn->selected, wn->minx, wn->miny, wn->maxx, wn->maxy, focused); 335 336 free(folder); 337 return 1; 338 } 339 340 static int 341 draw_search_buffers(struct window_split_node* wn) 342 { 343 soft_assert(wn->wb.mode < WB_MODES_END, return 1;); 344 if (wn->wb.mode != WB_SEARCH_FOR_BUFFERS) return 0; 345 346 int focused = &wn->wb == focused_window; 347 choose_one_of_selection("Find loaded buffer: ", wn->search, " [No resuts]", buffer_search_next_item, 348 &wn->selected, wn->minx, wn->miny, wn->maxx, wn->maxy, focused); 349 return 1; 350 } 351 352 static int 353 draw_search_keyword_in_all_buffers(struct window_split_node* wn) 354 { 355 soft_assert(wn->wb.mode < WB_MODES_END, return 1;); 356 if (wn->wb.mode != WB_SEARCH_KEYWORD_ALL_BUFFERS) return 0; 357 358 int focused = &wn->wb == focused_window; 359 choose_one_of_selection("Find in all buffers: ", wn->search, " [No resuts]", buffers_search_keyword_next_item, 360 &wn->selected, wn->minx, wn->miny, wn->maxx, wn->maxy, focused); 361 return 1; 362 } 363 364 static int 365 file_browser_actions(KeySym keysym, int modkey) 366 { 367 static char full_path[PATH_MAX]; 368 struct file_buffer* fb = get_fb(focused_window); 369 int offset = fb->len; 370 371 switch (keysym) { 372 int new_fb; 373 case XK_BackSpace: 374 if (offset <= 0) { 375 char* dest = strrchr(fb->file_path, '/'); 376 if (dest && dest != fb->file_path) *dest = 0; 377 return 1; 378 } 379 380 focused_window->cursor_offset = offset; 381 wb_move_on_line(focused_window, -1, 0); 382 offset = focused_window->cursor_offset; 383 384 fb_remove(fb, offset, 1, 0, 0); 385 focused_node->selected = 0; 386 return 1; 387 case XK_Tab: 388 case XK_Return: 389 { 390 char* path = file_path_get_path(fb->file_path); 391 fb_change(fb, "\0", 1, fb->len, 1); 392 if (fb->len > 0) fb->len--; 393 394 file_browser_next_item(NULL, NULL, NULL, NULL, NULL); 395 const char* filename; 396 for (int y = 0; (filename = file_browser_next_item(path, fb->contents, NULL, NULL, NULL)); y++) { 397 strcpy(full_path, path); 398 strcat(full_path, filename); 399 if (y == focused_node->selected) { 400 if (path_is_folder(full_path)) { 401 strcpy(fb->file_path, full_path); 402 403 fb->len = 0; 404 fb->contents[0] = '\0'; 405 focused_node->selected = 0; 406 407 free(path); 408 file_browser_next_item(NULL, NULL, NULL, NULL, NULL); 409 *full_path = 0; 410 return 1; 411 } 412 goto open_file; 413 } 414 } 415 416 if (fb->contents[fb->len-1] == '/') { 417 free(path); 418 *full_path = 0; 419 return 1; 420 } 421 422 strcpy(full_path, path); 423 strcat(full_path, fb->contents); 424 open_file: 425 new_fb = fb_new_entry(full_path); 426 destroy_fb_entry(focused_node, &root_node); 427 focused_node->wb = wb_new(new_fb); 428 free(path); 429 *full_path = 0; 430 return 1; 431 } 432 case XK_Down: 433 focused_node->selected++; 434 return 1; 435 case XK_Up: 436 focused_node->selected--; 437 if (focused_node->selected < 0) 438 focused_node->selected = 0; 439 return 1; 440 case XK_Escape: 441 if (destroy_fb_entry(focused_node, &root_node)) 442 writef_to_status_bar("Quit"); 443 return 1; 444 } 445 return 0; 446 } 447 448 static void 449 file_browser_string_insert(const char* buf, int buflen) 450 { 451 static char full_path[PATH_MAX]; 452 struct file_buffer* fb = get_fb(focused_window); 453 454 if (fb->len + buflen + strlen(fb->file_path) > PATH_MAX) 455 return; 456 457 if (buf[0] >= 32 || buflen > 1) { 458 fb_insert(fb, buf, buflen, fb->len, 0); 459 focused_node->selected = 0; 460 461 if (*buf == '/') { 462 fb_change(fb, "\0", 1, fb->len, 0); 463 if (fb->len > 0) fb->len--; 464 char* path = file_path_get_path(fb->file_path); 465 strcpy(full_path, path); 466 strcat(full_path, fb->contents); 467 468 free(path); 469 470 if (path_is_folder(full_path)) { 471 file_browser_actions(XK_Return, 0); 472 return; 473 } 474 } 475 } else { 476 writef_to_status_bar("unhandled control character 0x%x\n", buf[0]); 477 } 478 } 479 480 static int 481 search_for_buffer_actions(KeySym keysym, int modkey) 482 { 483 switch (keysym) { 484 case XK_Return: 485 case XK_Tab: 486 if (focused_window->mode == WB_SEARCH_KEYWORD_ALL_BUFFERS) { 487 struct keyword_pos kw; 488 int n = 0; 489 buffers_search_keyword_next_item(NULL, NULL, NULL, NULL, NULL); 490 while (buffers_search_keyword_next_item("", focused_node->search, NULL, NULL, &kw)) { 491 if (n == focused_node->selected) { 492 *focused_window = wb_new(kw.fb_index); 493 focused_window->cursor_offset = kw.offset; 494 return 1; 495 } 496 n++; 497 } 498 buffers_search_keyword_next_item(NULL, NULL, NULL, NULL, NULL); 499 } else if (focused_window->mode == WB_SEARCH_FOR_BUFFERS) { 500 int fb_index; 501 int n = 0; 502 buffer_search_next_item(NULL, NULL, NULL, NULL, NULL); 503 while (buffer_search_next_item("", focused_node->search, NULL, NULL, &fb_index)) { 504 if (n == focused_node->selected) { 505 *focused_window = wb_new(fb_index); 506 return 1; 507 } 508 n++; 509 } 510 buffer_search_next_item(NULL, NULL, NULL, NULL, NULL); 511 } 512 writef_to_status_bar("no results for \"%s\"", focused_node->search); 513 return 1; 514 case XK_BackSpace: 515 utf8_remove_string_end(focused_node->search); 516 focused_node->selected = 0; 517 return 1; 518 case XK_Down: 519 focused_node->selected++; 520 return 1; 521 case XK_Up: 522 focused_node->selected--; 523 if (focused_node->selected < 0) 524 focused_node->selected = 0; 525 return 1; 526 case XK_Page_Down: 527 focused_node->selected += 10; 528 return 1; 529 case XK_Page_Up: 530 focused_node->selected -= 10; 531 if (focused_node->selected < 0) 532 focused_node->selected = 0; 533 return 1; 534 case XK_Escape: 535 if (path_is_folder(get_fb(focused_window)->file_path)) 536 focused_window->mode = WB_FILE_BROWSER; 537 else 538 focused_window->mode = WB_NORMAL; 539 540 writef_to_status_bar("Quit"); 541 return 1; 542 } 543 return 0; 544 } 545 546 static void 547 search_for_buffer_string_insert(const char* buf, int buflen) 548 { 549 int len = strlen(focused_node->search); 550 if (buflen + len + 1 > SEARCH_TERM_MAX_LEN) 551 return; 552 553 if (buf[0] >= 32 || buflen > 1) { 554 memcpy(focused_node->search + len, buf, buflen); 555 focused_node->search[len + buflen] = 0; 556 focused_node->selected = 0; 557 } else { 558 writef_to_status_bar("unhandled control character 0x%x\n", buf[0]); 559 } 560 } 561 562 int 563 file_buffer_keypress_override_callback(int* skip_keypress_callback, struct window_split_node* wn, KeySym ksym, int modkey, const char* buf, int len) 564 { 565 soft_assert(wn->wb.mode < WB_MODES_END, return 1;); 566 if (wn->wb.mode != WB_FILE_BROWSER) return 0; 567 568 if (file_browser_actions(ksym, modkey)) { 569 *skip_keypress_callback = 1; 570 return 1; 571 } 572 file_browser_string_insert(buf, len); 573 *skip_keypress_callback = 1; 574 return 1; 575 } 576 577 int 578 choose_one_of_selection_keypress_override_callback(int* skip_keypress_callback, struct window_split_node* wn, KeySym ksym, int modkey, const char* buf, int len) 579 { 580 soft_assert(wn->wb.mode < WB_MODES_END, return 1;); 581 if (wn->wb.mode != WB_SEARCH_FOR_BUFFERS && 582 wn->wb.mode != WB_SEARCH_KEYWORD_ALL_BUFFERS) 583 return 0; 584 585 if (search_for_buffer_actions(ksym, modkey)) { 586 *skip_keypress_callback = 1; 587 return 1; 588 } 589 search_for_buffer_string_insert(buf, len); 590 *skip_keypress_callback = 1; 591 return 1; 592 } 593 594 #endif // CHOOSE_ONE_OF_SELECTION_H_