draw.c (26342B)
1 /* This program is licensed under the terms of GNU GPL v3 or (at your option) 2 * any later version. Copyright (C) 2022-2026 Страхиња Радић. 3 * See the file LICENSE for exact copyright and license details. */ 4 5 #include <assert.h> 6 #include <errno.h> 7 #include <stdio.h> 8 #include <stdlib.h> 9 #include <string.h> 10 11 #include "draw.h" 12 #include "config.h" 13 #include "limit.h" 14 #include "util.h" 15 #include "version.h" 16 17 enum { 18 UNKNOWN_MODE, 19 COMMAND_MODE, 20 HELP_MODE, 21 INSERT_MODE, 22 OVERWRITE_MODE, 23 PROMPT_MODE, 24 INPUT_MDOE, 25 INPUT_NUM_MODE, 26 SELECTION_MODE 27 }; 28 29 /* clang-format off */ 30 static const char* help_title = "sled help"; 31 static const char* help[] = { 32 "Esc - Close dialog, clear error, cancel", 33 " selection", 34 "F1 - Show this screen", 35 "C-\\ - Toggle command mode", 36 "Home or C-A - Go to start of line", 37 "End or C-E - Go to end of line", 38 "Insert - Toggle overwrite mode", 39 "C-L - Show Go to line dialog", 40 "C-O - Reread (reopen) file", 41 "C-Q - Quit", 42 "C-R - Go to previous word", 43 "C-S - Write (save) file", 44 "C-Space - Toggle selection mode", 45 "C-T - Go to next word", 46 "", 47 "Arrow keys work in insert, selection and command modes.", 48 "Some vi-like keys work in command and selection modes.", 49 "", 50 "See the manual (man sled) for full help."}; 51 static const char* mode_labels[] = { 52 [UNKNOWN_MODE] = "UNKNOWN", 53 [COMMAND_MODE] = "COMMAND", 54 [HELP_MODE] = "HELP", 55 [INSERT_MODE] = "INSERT", 56 [OVERWRITE_MODE] = "OVERWRITE", 57 [PROMPT_MODE] = "PROMPT", 58 [INPUT_MDOE] = "INPUT", 59 [INPUT_NUM_MODE] = "NUM INPUT", 60 [SELECTION_MODE] = "SELECTION" 61 }; 62 const char* errors[] = { 63 [ERR_NO_CL_ARG] = "No filename given", 64 [ERR_BINARY] = "Not opening a binary file", 65 [ERR_NOTREG_FILE] = "'%s' is not a regular file", 66 [ERR_TB_ERROR] = "termbox error", 67 [ERR_UNKNOWN_KEY] = "Unknown key (press F1 for help)", 68 [ERR_EXIT_KEY] = "Press C-Q to quit", 69 [ERR_DLG_OPEN] = "Press Esc to close dialog or C-Q to quit", 70 [ERR_DLG_OPEN_QUIT] = "Press Esc to close dialog", 71 [ERR_INPUT_NONNUM] = "Input must be numerical", 72 [ERR_INVALID_NUM] = "Invalid input", 73 [ERR_NOT_IMP] = "Function not implemented/configured", 74 [ERR_ERRNO_SET] = "System call/library function error"}; 75 const char* actions[] = { 76 [ACT_WRITING] = "Writing...", 77 [ACT_READING] = "Reading...", 78 [ACT_WRAPPING] = "Wrapping..." 79 }; 80 static const struct { 81 ssize_t boundary; 82 int width; 83 } number_widths[] = { 84 { .boundary = 9999999, .width = 8 }, 85 { .boundary = 999999, .width = 7 }, 86 { .boundary = 99999, .width = 6 }, 87 { .boundary = 9999, .width = 5 }, 88 { .boundary = 999, .width = 4 }, 89 { .boundary = 99, .width = 3 }, 90 { .boundary = 9, .width = 2 } 91 }; 92 const char* go_to_dialog_title = "Go to line"; 93 const char* prompt_dirty = "File changed, save [y/n/ESC]?"; 94 const char* prompt_overwrite = "Not empty, overwrite? [y/n/ESC]?"; 95 const char* prompt_reread_dirty = "File changed, lose changes [y/n/ESC]?"; 96 const char* replace_dialog_title = "Replace with"; 97 const char* search_dialog_title = "Search text"; 98 /* clang-format on */ 99 100 void get_selection(int* ssr, int* ssc, int* ser, int* sec, 101 const struct DrawState* state); 102 103 void 104 assign_coord(struct Coord* dest, const struct Coord src) 105 { 106 init_coord(dest, src.col, src.row); 107 } 108 109 /* Calculates buffer length from a display length. 110 * max_index = the display index up to which the length is calculated 111 */ 112 ssize_t 113 buffer_length(uint32_t* buffer, const ssize_t max_index) 114 { 115 uint32_t* pbuf = buffer; 116 ssize_t len = 0; 117 ssize_t i; 118 int w; 119 120 if (!pbuf) 121 return 0; 122 123 for (i = 0; *pbuf && len < max_index; i++) 124 { 125 pbuf = buffer + i; 126 if (*pbuf == '\t') 127 len += MIN(TAB_SIZE - (len % TAB_SIZE), max_index - len); 128 else 129 { 130 w = wcwidth((wchar_t)*pbuf); 131 if (w > 0) 132 len += MIN(w, max_index - len); 133 } 134 } 135 return i; 136 } 137 138 int 139 calculate_help_width(const struct DrawState* state) 140 { 141 assert(state != NULL); 142 return state->maxx > HELP_WIDTH + 2 ? HELP_WIDTH : state->maxx - 2; 143 } 144 145 int 146 calculate_input_dialog_width(const struct DrawState* state) 147 { 148 assert(state != NULL); 149 return state->maxx > INPUT_DLG_WIDTH + 2 ? INPUT_DLG_WIDTH 150 : state->maxx - 2; 151 } 152 153 int 154 calculate_line_numbers_width(const struct DrawState* state) 155 { 156 ssize_t last_row; 157 int i; 158 assert(state != NULL); 159 last_row = MIN(state->rows_count, 160 state->first_shown.row + state->maxy - 1); 161 i = 0; 162 while ((size_t)i < LEN(number_widths)) 163 { 164 if (last_row > number_widths[i].boundary) 165 return number_widths[i].width; 166 i++; 167 } 168 return 1; 169 } 170 171 /* Calculates display length of a buffer line. 172 * max_index = the buffer index up to which the length is calculated 173 */ 174 ssize_t 175 display_length(uint32_t* buffer, const ssize_t max_index) 176 { 177 uint32_t* pbuf = buffer; 178 ssize_t len = 0; 179 int w; 180 181 assert(buffer != NULL); 182 for (ssize_t i = 0; *pbuf && i < max_index; i++) 183 { 184 pbuf = &buffer[i]; 185 if (*pbuf == '\t') 186 len += TAB_SIZE - (len % TAB_SIZE); 187 else if (!*pbuf) 188 len++; 189 else 190 { 191 w = wcwidth((wchar_t)*pbuf); 192 if (w > 0) 193 len += w; 194 } 195 } 196 197 return len; 198 } 199 200 int 201 draw(const struct DrawState* state) 202 { 203 assert(state != NULL); 204 if (draw_screen(state)) 205 return 1; 206 if (IN(state->mode, M_HELP)) 207 if (draw_help(state)) 208 return 1; 209 if (IN(state->mode, M_INPUT)) 210 if (draw_input_dialog(state)) 211 return 1; 212 if (draw_status(state)) 213 return 1; 214 return 0; 215 } 216 217 void 218 draw_box(const int startx, const int endx, const int starty, const int endy, 219 const uint16_t fg, const uint16_t bg) 220 { 221 int x, y; 222 223 tb_set_cell(startx, starty, BORDER_NW, fg, bg); 224 for (x = startx + 1; x < endx - 1; x++) 225 tb_set_cell(x, starty, BORDER_HOR, fg, bg); 226 tb_set_cell(endx - 1, starty, BORDER_NE, fg, bg); 227 228 for (y = starty + 1; y < endy - 2; y++) 229 { 230 tb_set_cell(startx, y, BORDER_VER, fg, bg); 231 for (x = startx + 1; x < endx - 1; x++) 232 tb_set_cell(x, y, ' ', fg, bg); 233 tb_set_cell(endx - 1, y, BORDER_VER, fg, bg); 234 tb_set_cell(endx, y, ' ', colors[C_SHADOW].fg, 235 colors[C_SHADOW].bg); 236 } 237 238 tb_set_cell(startx, endy - 2, BORDER_SW, fg, bg); 239 for (x = startx + 1; x < endx - 1; x++) 240 tb_set_cell(x, endy - 2, BORDER_HOR, fg, bg); 241 tb_set_cell(endx - 1, endy - 2, BORDER_SE, fg, bg); 242 tb_set_cell(endx, endy - 2, ' ', colors[C_SHADOW].fg, 243 colors[C_SHADOW].bg); 244 245 for (x = startx + 1; x < endx + 1; x++) 246 tb_set_cell(x, endy - 1, ' ', colors[C_SHADOW].fg, 247 colors[C_SHADOW].bg); 248 } 249 250 int 251 draw_help(const struct DrawState* state) 252 { 253 int startx, endx, starty, endy; 254 int w, h; 255 int row = 0; 256 int maxx; 257 int maxy; 258 259 assert(state != NULL); 260 w = state->help_width; 261 h = HELP_HEIGHT; 262 maxx = state->maxx; 263 maxy = state->maxy; 264 265 startx = w > maxx ? 0 : (maxx - w) / 2; 266 endx = w > maxx ? maxx - 1 : startx + w; 267 268 starty = h > maxy - 1 ? 0 : (maxy - h) / 2; 269 endy = h > maxy - 1 ? maxy - 2 : starty + h; 270 271 draw_box(startx, endx, starty, endy, colors[C_DLG].fg, colors[C_DLG].bg); 272 if (draw_string(startx, starty, colors[C_TITLE].fg, colors[C_TITLE].bg, 273 help_title, strlen(help_title) + 2, w, 1, 1, CENTER, 0, 0, 274 NULL, 0, 0, 0, 0, 0, 0)) 275 return 1; 276 while ((size_t)row < LEN(help)) 277 { 278 if (draw_string(startx + 1, starty + 1 + row, colors[C_DLG].fg, 279 colors[C_DLG].bg, help[row], w - 2, w - 2, 1, 1, 280 LEFT, 0, 1, NULL, 0, 0, 0, 0, 0, 0)) 281 return 1; 282 row++; 283 } 284 tb_hide_cursor(); 285 return 0; 286 } 287 288 int 289 draw_input_dialog(const struct DrawState* state) 290 { 291 int startx, endx, starty, endy; 292 int w, h; 293 int maxx, maxy; 294 int len; 295 int dlen = 0; 296 297 assert(state != NULL); 298 w = state->input_dialog_width; 299 h = INPUT_DLG_HEIGHT; 300 maxx = state->maxx; 301 maxy = state->maxy; 302 len = u32_strlen(state->input); 303 304 startx = w > maxx ? 0 : (maxx - w) / 2; 305 endx = w > maxx ? maxx - 1 : startx + w; 306 307 starty = maxy - 1 - h; 308 endy = maxy - 1; 309 310 draw_box(startx, endx, starty, endy, colors[C_INPUT].fg, 311 colors[C_INPUT].bg); 312 draw_rect(startx + 1, endx - 1, starty + 1, endy - 2, 313 colors[C_INPUT_EDIT].fg, colors[C_INPUT_EDIT].bg); 314 if (state->input_title) 315 if (draw_string(startx, starty, colors[C_TITLE].fg, 316 colors[C_TITLE].bg, state->input_title, 317 strlen(state->input_title) + 2, w, 1, 1, CENTER, 0, 318 0, NULL, 0, 0, 0, 0, 0, 0)) 319 return 1; 320 dlen = display_length(state->input, len); 321 if (dlen > state->input_first_shown_column) 322 u32_draw_string(startx + 1, starty + 1, colors[C_INPUT_EDIT].fg, 323 colors[C_INPUT_EDIT].bg, state->input, w - 2, w - 2, 1, 324 1, LEFT, state->input_first_shown_column, 1, NULL, 0, 0, 325 0, 0, 0, 0); 326 tb_set_cursor(startx + 2 + state->input_display_column 327 - state->input_first_shown_column, 328 starty + 1); 329 return 0; 330 } 331 332 void 333 draw_line_number(const struct DrawState* state, const int i, const int cy) 334 { 335 char numbuf[LINE_DEFAULT]; 336 char* numbuf_end = NULL; 337 char* pnumbuf = NULL; 338 assert(state != NULL); 339 snprintf(numbuf, LINE_DEFAULT, "%d", i + 1); 340 numbuf_end = strchr(numbuf, 0); 341 if (numbuf_end) 342 { 343 pnumbuf = numbuf_end - numbuf > state->line_numbers_width 344 ? numbuf_end - state->line_numbers_width 345 : numbuf; 346 draw_string(0, cy, colors[C_LINENO].fg, colors[C_LINENO].bg, 347 pnumbuf, numbuf_end - numbuf + 1, 348 state->line_numbers_width, 0, 0, RIGHT, 0, 1, NULL, 0, 349 0, 0, 0, 0, 0); 350 } 351 } 352 353 void 354 draw_rect(const int startx, const int endx, const int starty, const int endy, 355 const uint16_t fg, const uint16_t bg) 356 { 357 assert((starty < endy) && (startx < endx)); 358 for (int y = starty; y < endy; y++) 359 for (int x = startx; x < endx; x++) 360 tb_set_cell(x, y, ' ', fg, bg); 361 } 362 363 int 364 draw_screen(const struct DrawState* state) 365 { 366 const struct BufferLine* current = NULL; 367 int max_screen_lines; 368 int cy = 0; 369 int i; 370 /* Selection start/end index - passed to u32_draw_string */ 371 int ssi = 0; 372 int sei = 0; 373 /* Selection start row/col */ 374 int ssr = 0; 375 int ssc = 0; 376 /* Selection end row/col */ 377 int ser = 0; 378 int sec = 0; 379 int maxx; 380 int fsc; /* First Shown Col */ 381 382 assert(state != NULL); 383 maxx = state->maxx; 384 fsc = state->first_shown.col; 385 if (state->line_numbers) 386 maxx -= state->line_numbers_width + 2; 387 388 if (state->rows_count == 0) 389 return 0; 390 391 max_screen_lines = state->maxy - 1; 392 i = state->first_shown.row; 393 394 while (((ssize_t)i < state->rows_count) && (cy < max_screen_lines)) 395 { 396 current = state->buffer + i; 397 assert(current != NULL); 398 399 ssi = 0; 400 sei = 0; 401 402 get_selection(&ssr, &ssc, &ser, &sec, state); 403 if ((i > ssr) && (i < ser)) 404 { 405 ssi = 0; 406 sei = current->length; 407 } 408 else 409 { 410 if (i == ssr) 411 { 412 ssi = ssc; 413 if (i != ser) 414 sei = current->length; 415 } 416 if (i == ser) 417 { 418 sei = sec; 419 } 420 } 421 if (state->line_numbers) 422 draw_line_number(state, i, cy); 423 if (current->text) 424 u32_draw_string(state->line_numbers 425 ? state->line_numbers_width + 2 426 : 0, 427 cy, colors[C_TEXT].fg, colors[C_TEXT].bg, 428 current->text, maxx, maxx, 0, 0, LEFT, fsc, 0, 429 state->search, colors[C_SEARCH].fg, 430 colors[C_SEARCH].bg, ssi, sei, colors[C_SEL].fg, 431 colors[C_SEL].bg); 432 i++; 433 cy++; 434 } 435 436 tb_set_cursor(state->line_numbers ? state->line_numbers_width + 2 437 + state->display_column - fsc 438 : state->display_column - fsc, 439 state->coord.row - state->first_shown.row); 440 441 return 0; 442 } 443 444 int 445 draw_status(const struct DrawState* state) 446 { 447 const struct StatusSegment* pseg; 448 char* buf = NULL; 449 ssize_t seg_size; 450 int current_start = 0; 451 int maxx; 452 453 assert(state != NULL); 454 maxx = state->maxx; 455 seg_size = maxx / LEN(status_segments); 456 pseg = status_segments; 457 458 if (IN(state->mode, M_PROMPT) && *state->prompt) 459 { 460 if (draw_string(0, state->maxy - 1, colors[C_PROMPT].fg, 461 colors[C_PROMPT].bg, state->prompt, 462 strlen(state->prompt) + 2, maxx, 1, 1, CENTER, 0, 1, 463 NULL, 0, 0, 0, 0, 0, 0)) 464 return 1; 465 } 466 else if (state->action) 467 { 468 if (draw_string(0, state->maxy - 1, colors[C_ACTION].fg, 469 colors[C_ACTION].bg, actions[state->action], 470 strlen(actions[state->action]) + 2, maxx, 1, 1, 471 CENTER, 0, 1, NULL, 0, 0, 0, 0, 0, 0)) 472 return 1; 473 } 474 else if (state->error) 475 { 476 if (draw_string(0, state->maxy - 1, colors[C_ERROR].fg, 477 colors[C_ERROR].bg, errors[state->error], 478 strlen(errors[state->error]) + 2, maxx, 1, 1, 479 CENTER, 0, 1, NULL, 0, 0, 0, 0, 0, 0)) 480 return 1; 481 } 482 else 483 while (pseg < status_segments + LEN(status_segments)) 484 { 485 int last = pseg - status_segments 486 == LEN(status_segments) - 1; 487 488 buf = calloc(limits[L_LINE_MAX], 1); 489 if (!buf) 490 return 1; 491 492 *buf = 0; 493 if (pseg->callback) 494 pseg->callback(buf, limits[L_LINE_MAX], 495 pseg->format, seg_size, state); 496 else 497 memccpy(buf, pseg->format, 0, 498 limits[L_LINE_MAX]); 499 if (pseg == status_segments + LEN(status_segments) - 1) 500 seg_size = maxx - 1 - current_start; 501 if (draw_string(current_start, state->maxy - 1, 502 colors[pseg->color_index].fg, 503 colors[pseg->color_index].bg, buf, 504 MIN(seg_size, (ssize_t)(strlen(buf) + 2)), 505 last ? seg_size + 1 : seg_size, 1, 1, 506 pseg->alignment, 0, 1, NULL, 0, 0, 0, 0, 0, 507 0)) 508 { 509 free(buf); 510 return 1; 511 } 512 free(buf); 513 pseg++; 514 current_start += seg_size; 515 } 516 517 return 0; 518 } 519 520 /* mc - (max cols) pack text and padding into this many columns 521 * fc - (fill cols) this many columns from the left/right/center will be 522 * filled 523 * p_? - (padding) this many cells will be skipped from ? side 524 * ds - (display start) string starts being displayed from this display column 525 * counted from the start of the string; an "entry point" joining string 526 * index with screen 527 * fill - boolean; 1 = fill fc, 0 = don't fill fc 528 * srch - search string; if empty or NULL no highlighting happens 529 * sfg - fg color of search highlght 530 * sbg - bg color of search highlight 531 * ssi - selection start index (>=) 532 * sei - selection end index (<) 533 * slfg - selection fg color 534 * slbg - selection bg color 535 * */ 536 int 537 draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, 538 const char* s, const int mc, const int fc, const int p_l, const int p_r, 539 const Alignment align, const int ds, const int fill, 540 const uint32_t* srch, const uint16_t sfg, const uint16_t sbg, 541 const int ssi, const int sei, const uint16_t slfg, const uint16_t slbg) 542 { 543 uint32_t* us = NULL; 544 assert(s != NULL); 545 us = calloc(strlen(s) + 1, sizeof(uint32_t)); 546 if (!us) 547 return 1; 548 u8_string_to_unicode(us, s, mc); 549 u32_draw_string(x, y, fg, bg, us, mc, fc, p_l, p_r, align, ds, fill, 550 srch, sfg, sbg, ssi, sei, slfg, slbg); 551 free(us); 552 return 0; 553 } 554 555 void 556 format_codep(char* result, ssize_t result_size, const char* format, 557 const ssize_t seg_size, const struct DrawState* state) 558 { 559 char codep[LINE_DEFAULT]; 560 assert((state != NULL) && (format != NULL)); 561 UNUSED(seg_size); 562 if (state->buffer 563 && state->buffer[state->coord.row].text[state->coord.col]) 564 snprintf(codep, LINE_DEFAULT, "%06X", 565 state->buffer[state->coord.row].text[state->coord.col]); 566 else 567 memccpy(codep, "------", 0, LINE_DEFAULT); 568 snprintf(result, result_size, format, codep); 569 } 570 571 void 572 format_filename(char* result, ssize_t result_size, const char* format, 573 const ssize_t seg_size, const struct DrawState* state) 574 { 575 char* basename = NULL; 576 const char* pbase = NULL; 577 578 assert((state != NULL) && (format != NULL)); 579 if (!result || seg_size < 4) 580 return; 581 582 pbase = u8_basename(state->filename); 583 basename = strndup(pbase, strlen(pbase) + 1); 584 585 if ((ssize_t)(u8_strlen(basename) + 2) >= seg_size) 586 { 587 if (state->dirty) 588 basename[seg_size - 4] = 0; 589 else 590 basename[seg_size - 3] = 0; 591 snprintf(result, result_size, format, basename, 592 state->dirty ? "…*" : "…"); 593 } 594 else 595 snprintf(result, result_size, format, basename, 596 state->dirty ? "*" : ""); 597 free(basename); 598 } 599 600 void 601 format_mode(char* result, ssize_t result_size, const char* format, 602 const ssize_t seg_size, const struct DrawState* state) 603 { 604 const char* mode_label = mode_labels[UNKNOWN_MODE]; 605 assert((state != NULL) && (format != NULL)); 606 UNUSED(seg_size); 607 if (!result) 608 goto format_print; 609 if (IN(state->mode, M_INPUT_NUM)) 610 mode_label = mode_labels[INPUT_NUM_MODE]; 611 else if (IN(state->mode, M_INPUT)) 612 mode_label = mode_labels[INPUT_MDOE]; 613 else if (IN(state->mode, M_HELP)) 614 mode_label = mode_labels[HELP_MODE]; 615 else if (IN(state->mode, M_PROMPT)) 616 mode_label = mode_labels[PROMPT_MODE]; 617 else if (IN(state->mode, M_SELECTION)) 618 mode_label = mode_labels[SELECTION_MODE]; 619 else if (IN(state->mode, M_COMMAND)) 620 mode_label = mode_labels[COMMAND_MODE]; 621 else if (IN(state->mode, M_OVERWRITE)) 622 mode_label = mode_labels[OVERWRITE_MODE]; 623 else 624 mode_label = mode_labels[INSERT_MODE]; 625 626 format_print: 627 snprintf(result, result_size, format, mode_label); 628 } 629 630 void 631 format_position(char* result, ssize_t result_size, const char* format, 632 const ssize_t seg_size, const struct DrawState* state) 633 { 634 assert((state != NULL) && (format != NULL)); 635 UNUSED(seg_size); 636 if (!result) 637 return; 638 snprintf(result, result_size, format, state->first_shown.col, 639 state->display_column + 1, state->coord.row + 1, 640 state->rows_count > 1 641 ? (state->coord.row * 100) / (state->rows_count - 1) 642 : 0); 643 } 644 645 void 646 free_bufferline(struct BufferLine* bl) 647 { 648 if (!bl) 649 return; 650 free(bl->text); 651 bl->text = NULL; 652 bl->size = 0; 653 bl->length = 0; 654 bl->fg = 0; 655 bl->bg = 0; 656 } 657 658 void 659 free_drawstate(struct DrawState* state) 660 { 661 ssize_t row = 0; 662 assert(state != NULL); 663 for (row = 0; row < state->buffer_size; row++) 664 free_bufferline(state->buffer + row); 665 free(state->buffer); 666 free(state->input); 667 free(state->search); 668 } 669 670 void 671 init_bufferline(struct BufferLine* bl) 672 { 673 if (!bl) 674 return; 675 bl->text = NULL; 676 bl->length = 0; 677 bl->size = 0; 678 bl->fg = 0; 679 bl->bg = 0; 680 } 681 682 void 683 init_coord(struct Coord* c, const int col, const int row) 684 { 685 assert(c != NULL); 686 c->col = col; 687 c->row = row; 688 } 689 690 void 691 init_drawstate(struct DrawState* state, char* filename, char* prompt, 692 const int skip_paste_and_mode_init, const int skip_filenames_init) 693 { 694 assert(state != NULL); 695 state->error = ERR_NONE; 696 state->action = ACT_IDLE; 697 if (!skip_filenames_init) 698 { 699 state->filename = filename; 700 state->real_filename = NULL; 701 state->orig_real_filename = NULL; 702 state->backup_filename = NULL; 703 state->rfn_len = 0; 704 } 705 state->input = NULL; 706 state->search = NULL; 707 state->chain_dialog = 0; 708 state->buffer = NULL; 709 state->buffer_size = 0; 710 init_coord(&state->coord, 0, 0); 711 state->display_column = 0; 712 init_coord(&state->first_shown, 0, 0); 713 state->input_first_shown_column = 0; 714 state->input_display_column = 0; 715 state->input_column = 0; 716 state->input_title = NULL; 717 state->rows_count = 0; 718 state->saved_column = -1; 719 state->help_width = calculate_help_width(state); 720 state->input_dialog_width = calculate_input_dialog_width(state); 721 state->line_numbers = line_numbers; 722 if (!skip_paste_and_mode_init) 723 { 724 state->paste = NULL; 725 state->paste_count = 0; 726 state->paste_size = 0; 727 CLEAR(state->mode, M_OVERWRITE | M_COMMAND); 728 } 729 state->prompt = prompt; 730 state->prompt_callback = NULL; 731 state->input_callback = NULL; 732 state->input_update_callback = NULL; 733 state->running = 1; 734 state->dirty = 0; 735 state->new_file = 0; 736 state->recalculate_size = 1; 737 CLEAR(state->mode, 738 M_HELP | M_INPUT | M_INPUT_NUM | M_PROMPT | M_SELECTION); 739 init_coord(&state->sel_start, 0, 0); 740 init_coord(&state->sel_end, 0, 0); 741 state->backup_possible = 1; 742 } 743 744 void 745 recalculate_line_numbers(struct DrawState* state) 746 { 747 assert(state != NULL); 748 state->line_numbers_width = calculate_line_numbers_width(state); 749 } 750 751 void 752 recalculate_size(struct DrawState* state) 753 { 754 assert(state != NULL); 755 state->maxx = tb_width(); 756 state->help_width = calculate_help_width(state); 757 state->input_dialog_width = calculate_input_dialog_width(state); 758 state->maxy = tb_height(); 759 recalculate_line_numbers(state); 760 } 761 762 void 763 redraw_status(struct DrawState* state) 764 { 765 assert(state != NULL); 766 if (state->recalculate_size) 767 { 768 recalculate_size(state); 769 state->recalculate_size = 0; 770 } 771 (void)draw_status(state); 772 tb_present(); 773 } 774 775 /* mc - (max cols) pack text and padding into this many columns 776 * fc - (fill cols) this many columns from the left/right/center will be 777 * filled 778 * p_? - (padding) this many cells will be skipped from ? side 779 * ds - (display start) string starts being displayed from this display column 780 * counted from the start of the string; an "entry point" joining string 781 * index with screen 782 * fill - boolean; 1 = fill fc, 0 = don't fill fc 783 * srch - search string; if empty or NULL no highlighting happens 784 * sfg - fg color of search highlight 785 * sbg - bg color of search highlight 786 * ssi - selection start index (>=) 787 * sei - selection end index (<) 788 * slfg - selection fg color 789 * slbg - selection bg color 790 * */ 791 void 792 u32_draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, 793 const uint32_t* s, const int mc, const int fc, const int p_l, 794 const int p_r, const Alignment align, const int ds, const int fill, 795 const uint32_t* srch, const uint16_t sfg, const uint16_t sbg, 796 const int ssi, const int sei, const uint16_t slfg, const uint16_t slbg) 797 { 798 /* 799 * <-------- visible part ----------> 800 * +---------+-------+----------------+---------+ 801 * | | | | | 802 * |<-- d -->|<-p_l->|<------ c ----->|<- p_r ->| 803 * | |<-------------- mc -------------->| 804 * |<------------------ fc -------------------->| 805 * x | | 806 * ds ds+c 807 * <.....(sc).....> 808 * (align == RIGHT in this case) 809 */ 810 811 int d = 0; /* distance, in screen columns, from x to 812 * visible part of the string, followed by 813 * p_l */ 814 815 int c = 0; /* distance in screen columns from 816 * d + p_l to d + mc - p_r (part of the 817 * displayed string containing text, between 818 * padding; string fits within this many columns 819 */ 820 821 int sc = 0; /* current screen column; goes from 0..c-1 822 * only increased when actual characters are 823 * written 824 */ 825 826 int ssc = 0; /* current string screen column; goes from start 827 * of the string until ds+c */ 828 829 int w = 0; /* actual on-screen rune width */ 830 831 const int ts = TAB_SIZE; 832 833 const uint32_t* ps = s; /* pointer to current character in 834 * string; string index: ps-s */ 835 836 uint16_t cfg = fg; /* current fg */ 837 uint16_t cbg = bg; /* current bg */ 838 839 int hctl = 0; /* highlight "characters to live", when this 840 * reaches zero the highlighting is turned 841 * off */ 842 843 assert(s != NULL); 844 c = mc - p_l - p_r; 845 846 if (align == LEFT) 847 d = 0; 848 else if (align == RIGHT) 849 d = fc >= mc ? fc - mc : 0; 850 else if (align == CENTER) 851 d = fc >= mc ? (fc - mc) / 2 : 0; 852 853 /* fill */ 854 if (fill) 855 for (int j = 0; j < fc; j++) 856 tb_set_cell(x + j, y, L' ', fg, bg); 857 858 /* left padding */ 859 for (int j = 0; j < p_l; j++) 860 tb_set_cell(x + d + j, y, L' ', fg, bg); 861 862 sc = 0; 863 864 /* 865 * main idea: we go from start of the string to the end and 866 * count displayed characters until we get to ds, then we also 867 * output characters; tabs count as spaces up to multiple of ts, 868 * counted from the start of the string 869 * 870 * exit condition is also sc == c, when we have reached the 871 * maximum number of characters to output 872 */ 873 while (*ps && sc < c) 874 { 875 if ((hctl == 0) && ((ps - s) >= ssi) && ((ps - s) < sei)) 876 { 877 cfg = slfg; 878 cbg = slbg; 879 } 880 881 if (srch && *srch && hctl == 0) 882 { 883 if (u32_starts_with(ps, srch)) 884 { 885 hctl = u32_strlen(srch) 886 - (ssc < ds ? ds - ssc : 0); 887 cfg = sfg; 888 cbg = sbg; 889 } 890 } 891 892 if (*ps == '\t') 893 { 894 if (ssc < ds) 895 { 896 /* clang-format off */ 897 /* ssc-(ssc%ts)+ts 898 * \t | = ssc+(ts-(ssc%ts)) 899 * ----------|-----[ :|::: ] 900 * | | | 901 * | | ds 902 * ssc-(ssc%ts) | 903 * ssc 904 */ 905 /* clang-format on */ 906 if (ssc - (ssc % ts) + ts >= ds) 907 { 908 for (int j = 0; 909 j < ssc - (ssc % ts) + ts - ds; 910 j++) 911 tb_set_cell(x + d + p_l + sc + j, 912 y, L' ', cfg, cbg); 913 sc += ssc + (ts - (ssc % ts)) - ds; 914 hctl -= ssc + (ts - (ssc % ts)) - ds; 915 if (hctl < 0) 916 hctl = 0; 917 } 918 /* clang-format off */ 919 /* else, just increase counter 920 * 921 * \t 922 * ----------|----[ ::::: ] 923 * | | | 924 * | | ds 925 * | ssc-(ssc%ts)+ts 926 * ssc = ssc + (ts-(ssc%ts)) 927 * */ 928 /* clang-format on */ 929 ssc += (ts - (ssc % ts)); 930 } 931 else /* ssc >= ds */ 932 { 933 /* clang-format off */ 934 /* 935 * ds ssc+(ts-(ssc%ts)) 936 * | \t | 937 * [ ::::::: ]---|----- 938 * | | 939 * | ds+c 940 * ssc 941 */ 942 /* clang-format on */ 943 if (ssc + (ts - (ssc % ts)) >= ds + c) 944 { 945 for (int j = 0; j < ds + c - ssc; j++) 946 tb_set_cell(x + d + p_l + sc + j, 947 y, L' ', cfg, cbg); 948 sc = c; 949 ssc = ds + c; 950 hctl = 0; 951 cfg = fg; 952 cbg = bg; 953 } 954 else /* ssc+(ts-(ssc%ts)) < ds+c */ 955 { 956 /* clang-format off */ 957 /* 958 * \t 959 * [ :::::::::::|::: ]----- 960 * | | | | 961 * | | | ds+c 962 * | | ssc+(ts-(ssc%ts)) 963 * | ssc 964 * ds 965 */ 966 /* clang-format on */ 967 for (int j = 0; j < ts - (ssc % ts); j++) 968 tb_set_cell(x + d + p_l + sc + j, 969 y, L' ', cfg, cbg); 970 sc += ts - (ssc % ts); 971 hctl -= ts - (ssc % ts); 972 if (hctl < 0) 973 hctl = 0; 974 ssc += ts - (ssc % ts); 975 } 976 } 977 } 978 else 979 { 980 w = wcwidth((wchar_t)*ps); 981 if (w < 0) 982 w = 1; 983 if (ssc >= ds) 984 { 985 if (sc < c && w > 0) 986 { 987 /*if ((w == 0) && (x + d + p_l + 988 sc > 0)) tb_extend_cell(x + d + 989 p_l + sc 990 - 1, 991 y, *ps); 992 else*/ 993 /* TODO: Get the previous cell 994 * (if applicable) and overwrite 995 * it if it has wcwidth >1 996 */ 997 tb_set_cell(x + d + p_l + sc, y, *ps, 998 cfg, cbg); 999 sc += w; 1000 } 1001 hctl--; 1002 if (hctl < 0) 1003 hctl = 0; 1004 } 1005 ssc += w; 1006 } 1007 if (hctl == 0) 1008 { 1009 cfg = fg; 1010 cbg = bg; 1011 } 1012 ps++; 1013 } 1014 1015 /* right padding */ 1016 for (int j = 0; j < p_r; j++) 1017 tb_set_cell(x + d + p_l + c + j, y, L' ', fg, bg); 1018 }