diff options
Diffstat (limited to 'examples/dte/misc.c')
| -rw-r--r-- | examples/dte/misc.c | 764 |
1 files changed, 0 insertions, 764 deletions
diff --git a/examples/dte/misc.c b/examples/dte/misc.c deleted file mode 100644 index 4ed640b..0000000 --- a/examples/dte/misc.c +++ /dev/null | |||
| @@ -1,764 +0,0 @@ | |||
| 1 | #include <stdlib.h> | ||
| 2 | #include <string.h> | ||
| 3 | #include "misc.h" | ||
| 4 | #include "buffer.h" | ||
| 5 | #include "change.h" | ||
| 6 | #include "indent.h" | ||
| 7 | #include "move.h" | ||
| 8 | #include "options.h" | ||
| 9 | #include "regexp.h" | ||
| 10 | #include "selection.h" | ||
| 11 | #include "util/debug.h" | ||
| 12 | #include "util/macros.h" | ||
| 13 | #include "util/string.h" | ||
| 14 | #include "util/string-view.h" | ||
| 15 | #include "util/utf8.h" | ||
| 16 | |||
| 17 | typedef struct { | ||
| 18 | String buf; | ||
| 19 | char *indent; | ||
| 20 | size_t indent_len; | ||
| 21 | size_t indent_width; | ||
| 22 | size_t cur_width; | ||
| 23 | size_t text_width; | ||
| 24 | } ParagraphFormatter; | ||
| 25 | |||
| 26 | static bool line_has_opening_brace(StringView line) | ||
| 27 | { | ||
| 28 | static regex_t re; | ||
| 29 | static bool compiled; | ||
| 30 | if (!compiled) { | ||
| 31 | // TODO: Reimplement without using regex | ||
| 32 | static const char pat[] = "\\{[ \t]*(//.*|/\\*.*\\*/[ \t]*)?$"; | ||
| 33 | regexp_compile_or_fatal_error(&re, pat, REG_NEWLINE | REG_NOSUB); | ||
| 34 | compiled = true; | ||
| 35 | } | ||
| 36 | |||
| 37 | regmatch_t m; | ||
| 38 | return regexp_exec(&re, line.data, line.length, 0, &m, 0); | ||
| 39 | } | ||
| 40 | |||
| 41 | static bool line_has_closing_brace(StringView line) | ||
| 42 | { | ||
| 43 | strview_trim_left(&line); | ||
| 44 | return line.length > 0 && line.data[0] == '}'; | ||
| 45 | } | ||
| 46 | |||
| 47 | /* | ||
| 48 | * Stupid { ... } block selector. | ||
| 49 | * | ||
| 50 | * Because braces can be inside strings or comments and writing real | ||
| 51 | * parser for many programming languages does not make sense the rules | ||
| 52 | * for selecting a block are made very simple. Line that matches \{\s*$ | ||
| 53 | * starts a block and line that matches ^\s*\} ends it. | ||
| 54 | */ | ||
| 55 | void select_block(View *view) | ||
| 56 | { | ||
| 57 | BlockIter sbi, ebi, bi = view->cursor; | ||
| 58 | StringView line; | ||
| 59 | int level = 0; | ||
| 60 | |||
| 61 | // If current line does not match \{\s*$ but matches ^\s*\} then | ||
| 62 | // cursor is likely at end of the block you want to select | ||
| 63 | fetch_this_line(&bi, &line); | ||
| 64 | if (!line_has_opening_brace(line) && line_has_closing_brace(line)) { | ||
| 65 | block_iter_prev_line(&bi); | ||
| 66 | } | ||
| 67 | |||
| 68 | while (1) { | ||
| 69 | fetch_this_line(&bi, &line); | ||
| 70 | if (line_has_opening_brace(line)) { | ||
| 71 | if (level++ == 0) { | ||
| 72 | sbi = bi; | ||
| 73 | block_iter_next_line(&bi); | ||
| 74 | break; | ||
| 75 | } | ||
| 76 | } | ||
| 77 | if (line_has_closing_brace(line)) { | ||
| 78 | level--; | ||
| 79 | } | ||
| 80 | |||
| 81 | if (!block_iter_prev_line(&bi)) { | ||
| 82 | return; | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | while (1) { | ||
| 87 | fetch_this_line(&bi, &line); | ||
| 88 | if (line_has_closing_brace(line)) { | ||
| 89 | if (--level == 0) { | ||
| 90 | ebi = bi; | ||
| 91 | break; | ||
| 92 | } | ||
| 93 | } | ||
| 94 | if (line_has_opening_brace(line)) { | ||
| 95 | level++; | ||
| 96 | } | ||
| 97 | |||
| 98 | if (!block_iter_next_line(&bi)) { | ||
| 99 | return; | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | view->cursor = sbi; | ||
| 104 | view->sel_so = block_iter_get_offset(&ebi); | ||
| 105 | view->sel_eo = SEL_EO_RECALC; | ||
| 106 | view->selection = SELECT_LINES; | ||
| 107 | |||
| 108 | mark_all_lines_changed(view->buffer); | ||
| 109 | } | ||
| 110 | |||
| 111 | static int get_indent_of_matching_brace(const View *view) | ||
| 112 | { | ||
| 113 | const LocalOptions *options = &view->buffer->options; | ||
| 114 | BlockIter bi = view->cursor; | ||
| 115 | StringView line; | ||
| 116 | int level = 0; | ||
| 117 | |||
| 118 | while (block_iter_prev_line(&bi)) { | ||
| 119 | fetch_this_line(&bi, &line); | ||
| 120 | if (line_has_opening_brace(line)) { | ||
| 121 | if (level++ == 0) { | ||
| 122 | return get_indent_width(options, &line); | ||
| 123 | } | ||
| 124 | } | ||
| 125 | if (line_has_closing_brace(line)) { | ||
| 126 | level--; | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | return -1; | ||
| 131 | } | ||
| 132 | |||
| 133 | void unselect(View *view) | ||
| 134 | { | ||
| 135 | view->select_mode = SELECT_NONE; | ||
| 136 | if (view->selection) { | ||
| 137 | view->selection = SELECT_NONE; | ||
| 138 | mark_all_lines_changed(view->buffer); | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 142 | void insert_text(View *view, const char *text, size_t size, bool move_after) | ||
| 143 | { | ||
| 144 | size_t del_count = 0; | ||
| 145 | if (view->selection) { | ||
| 146 | del_count = prepare_selection(view); | ||
| 147 | unselect(view); | ||
| 148 | } | ||
| 149 | buffer_replace_bytes(view, del_count, text, size); | ||
| 150 | if (move_after) { | ||
| 151 | block_iter_skip_bytes(&view->cursor, size); | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | void delete_ch(View *view) | ||
| 156 | { | ||
| 157 | size_t size = 0; | ||
| 158 | if (view->selection) { | ||
| 159 | size = prepare_selection(view); | ||
| 160 | unselect(view); | ||
| 161 | } else { | ||
| 162 | const LocalOptions *options = &view->buffer->options; | ||
| 163 | begin_change(CHANGE_MERGE_DELETE); | ||
| 164 | if (options->emulate_tab) { | ||
| 165 | size = get_indent_level_bytes_right(options, &view->cursor); | ||
| 166 | } | ||
| 167 | if (size == 0) { | ||
| 168 | BlockIter bi = view->cursor; | ||
| 169 | size = block_iter_next_column(&bi); | ||
| 170 | } | ||
| 171 | } | ||
| 172 | buffer_delete_bytes(view, size); | ||
| 173 | } | ||
| 174 | |||
| 175 | void erase(View *view) | ||
| 176 | { | ||
| 177 | size_t size = 0; | ||
| 178 | if (view->selection) { | ||
| 179 | size = prepare_selection(view); | ||
| 180 | unselect(view); | ||
| 181 | } else { | ||
| 182 | const LocalOptions *options = &view->buffer->options; | ||
| 183 | begin_change(CHANGE_MERGE_ERASE); | ||
| 184 | if (options->emulate_tab) { | ||
| 185 | size = get_indent_level_bytes_left(options, &view->cursor); | ||
| 186 | block_iter_back_bytes(&view->cursor, size); | ||
| 187 | } | ||
| 188 | if (size == 0) { | ||
| 189 | CodePoint u; | ||
| 190 | size = block_iter_prev_char(&view->cursor, &u); | ||
| 191 | } | ||
| 192 | } | ||
| 193 | buffer_erase_bytes(view, size); | ||
| 194 | } | ||
| 195 | |||
| 196 | // Go to beginning of whitespace (tabs and spaces) under cursor and | ||
| 197 | // return number of whitespace bytes after cursor after moving cursor | ||
| 198 | static size_t goto_beginning_of_whitespace(View *view) | ||
| 199 | { | ||
| 200 | BlockIter bi = view->cursor; | ||
| 201 | size_t count = 0; | ||
| 202 | CodePoint u; | ||
| 203 | |||
| 204 | // Count spaces and tabs at or after cursor | ||
| 205 | while (block_iter_next_char(&bi, &u)) { | ||
| 206 | if (u != '\t' && u != ' ') { | ||
| 207 | break; | ||
| 208 | } | ||
| 209 | count++; | ||
| 210 | } | ||
| 211 | |||
| 212 | // Count spaces and tabs before cursor | ||
| 213 | while (block_iter_prev_char(&view->cursor, &u)) { | ||
| 214 | if (u != '\t' && u != ' ') { | ||
| 215 | block_iter_next_char(&view->cursor, &u); | ||
| 216 | break; | ||
| 217 | } | ||
| 218 | count++; | ||
| 219 | } | ||
| 220 | return count; | ||
| 221 | } | ||
| 222 | |||
| 223 | static bool ws_only(const StringView *line) | ||
| 224 | { | ||
| 225 | for (size_t i = 0, n = line->length; i < n; i++) { | ||
| 226 | char ch = line->data[i]; | ||
| 227 | if (ch != ' ' && ch != '\t') { | ||
| 228 | return false; | ||
| 229 | } | ||
| 230 | } | ||
| 231 | return true; | ||
| 232 | } | ||
| 233 | |||
| 234 | // Non-empty line can be used to determine size of indentation for the next line | ||
| 235 | static bool find_non_empty_line_bwd(BlockIter *bi) | ||
| 236 | { | ||
| 237 | block_iter_bol(bi); | ||
| 238 | do { | ||
| 239 | StringView line; | ||
| 240 | fill_line_ref(bi, &line); | ||
| 241 | if (!ws_only(&line)) { | ||
| 242 | return true; | ||
| 243 | } | ||
| 244 | } while (block_iter_prev_line(bi)); | ||
| 245 | return false; | ||
| 246 | } | ||
| 247 | |||
| 248 | static void insert_nl(View *view) | ||
| 249 | { | ||
| 250 | size_t del_count = 0; | ||
| 251 | size_t ins_count = 1; | ||
| 252 | char *ins = NULL; | ||
| 253 | |||
| 254 | // Prepare deleted text (selection or whitespace around cursor) | ||
| 255 | if (view->selection) { | ||
| 256 | del_count = prepare_selection(view); | ||
| 257 | unselect(view); | ||
| 258 | } else { | ||
| 259 | // Trim whitespace around cursor | ||
| 260 | del_count = goto_beginning_of_whitespace(view); | ||
| 261 | } | ||
| 262 | |||
| 263 | // Prepare inserted indentation | ||
| 264 | const LocalOptions *options = &view->buffer->options; | ||
| 265 | if (options->auto_indent) { | ||
| 266 | // Current line will be split at cursor position | ||
| 267 | BlockIter bi = view->cursor; | ||
| 268 | size_t len = block_iter_bol(&bi); | ||
| 269 | StringView line; | ||
| 270 | fill_line_ref(&bi, &line); | ||
| 271 | line.length = len; | ||
| 272 | if (ws_only(&line)) { | ||
| 273 | // This line is (or will become) white space only; find previous, | ||
| 274 | // non whitespace only line | ||
| 275 | if (block_iter_prev_line(&bi) && find_non_empty_line_bwd(&bi)) { | ||
| 276 | fill_line_ref(&bi, &line); | ||
| 277 | ins = get_indent_for_next_line(options, &line); | ||
| 278 | } | ||
| 279 | } else { | ||
| 280 | ins = get_indent_for_next_line(options, &line); | ||
| 281 | } | ||
| 282 | } | ||
| 283 | |||
| 284 | begin_change(CHANGE_MERGE_NONE); | ||
| 285 | if (ins) { | ||
| 286 | // Add newline before indent | ||
| 287 | ins_count = strlen(ins); | ||
| 288 | memmove(ins + 1, ins, ins_count); | ||
| 289 | ins[0] = '\n'; | ||
| 290 | ins_count++; | ||
| 291 | |||
| 292 | buffer_replace_bytes(view, del_count, ins, ins_count); | ||
| 293 | free(ins); | ||
| 294 | } else { | ||
| 295 | buffer_replace_bytes(view, del_count, "\n", ins_count); | ||
| 296 | } | ||
| 297 | end_change(); | ||
| 298 | |||
| 299 | // Move after inserted text | ||
| 300 | block_iter_skip_bytes(&view->cursor, ins_count); | ||
| 301 | } | ||
| 302 | |||
| 303 | void insert_ch(View *view, CodePoint ch) | ||
| 304 | { | ||
| 305 | if (ch == '\n') { | ||
| 306 | insert_nl(view); | ||
| 307 | return; | ||
| 308 | } | ||
| 309 | |||
| 310 | const Buffer *buffer = view->buffer; | ||
| 311 | const LocalOptions *options = &buffer->options; | ||
| 312 | char buf[8]; | ||
| 313 | char *ins = buf; | ||
| 314 | char *alloc = NULL; | ||
| 315 | size_t del_count = 0; | ||
| 316 | size_t ins_count = 0; | ||
| 317 | |||
| 318 | if (view->selection) { | ||
| 319 | // Prepare deleted text (selection) | ||
| 320 | del_count = prepare_selection(view); | ||
| 321 | unselect(view); | ||
| 322 | } else if (options->overwrite) { | ||
| 323 | // Delete character under cursor unless we're at end of line | ||
| 324 | BlockIter bi = view->cursor; | ||
| 325 | del_count = block_iter_is_eol(&bi) ? 0 : block_iter_next_column(&bi); | ||
| 326 | } else if (ch == '}' && options->auto_indent && options->brace_indent) { | ||
| 327 | BlockIter bi = view->cursor; | ||
| 328 | StringView curlr; | ||
| 329 | block_iter_bol(&bi); | ||
| 330 | fill_line_ref(&bi, &curlr); | ||
| 331 | if (ws_only(&curlr)) { | ||
| 332 | int width = get_indent_of_matching_brace(view); | ||
| 333 | if (width >= 0) { | ||
| 334 | // Replace current (ws only) line with some indent + '}' | ||
| 335 | block_iter_bol(&view->cursor); | ||
| 336 | del_count = curlr.length; | ||
| 337 | if (width) { | ||
| 338 | alloc = make_indent(options, width); | ||
| 339 | ins = alloc; | ||
| 340 | ins_count = strlen(ins); | ||
| 341 | // '}' will be replace the terminating NUL | ||
| 342 | } | ||
| 343 | } | ||
| 344 | } | ||
| 345 | } | ||
| 346 | |||
| 347 | // Prepare inserted text | ||
| 348 | if (ch == '\t' && options->expand_tab) { | ||
| 349 | ins_count = options->indent_width; | ||
| 350 | static_assert(sizeof(buf) >= INDENT_WIDTH_MAX); | ||
| 351 | memset(ins, ' ', ins_count); | ||
| 352 | } else { | ||
| 353 | u_set_char_raw(ins, &ins_count, ch); | ||
| 354 | } | ||
| 355 | |||
| 356 | // Record change | ||
| 357 | begin_change(del_count ? CHANGE_MERGE_NONE : CHANGE_MERGE_INSERT); | ||
| 358 | buffer_replace_bytes(view, del_count, ins, ins_count); | ||
| 359 | end_change(); | ||
| 360 | free(alloc); | ||
| 361 | |||
| 362 | // Move after inserted text | ||
| 363 | block_iter_skip_bytes(&view->cursor, ins_count); | ||
| 364 | } | ||
| 365 | |||
| 366 | static void join_selection(View *view) | ||
| 367 | { | ||
| 368 | size_t count = prepare_selection(view); | ||
| 369 | size_t len = 0, join = 0; | ||
| 370 | BlockIter bi; | ||
| 371 | CodePoint ch = 0; | ||
| 372 | |||
| 373 | unselect(view); | ||
| 374 | bi = view->cursor; | ||
| 375 | |||
| 376 | begin_change_chain(); | ||
| 377 | while (count > 0) { | ||
| 378 | if (!len) { | ||
| 379 | view->cursor = bi; | ||
| 380 | } | ||
| 381 | |||
| 382 | count -= block_iter_next_char(&bi, &ch); | ||
| 383 | if (ch == '\t' || ch == ' ') { | ||
| 384 | len++; | ||
| 385 | } else if (ch == '\n') { | ||
| 386 | len++; | ||
| 387 | join++; | ||
| 388 | } else { | ||
| 389 | if (join) { | ||
| 390 | buffer_replace_bytes(view, len, " ", 1); | ||
| 391 | // Skip the space we inserted and the char we read last | ||
| 392 | block_iter_next_char(&view->cursor, &ch); | ||
| 393 | block_iter_next_char(&view->cursor, &ch); | ||
| 394 | bi = view->cursor; | ||
| 395 | } | ||
| 396 | len = 0; | ||
| 397 | join = 0; | ||
| 398 | } | ||
| 399 | } | ||
| 400 | |||
| 401 | // Don't replace last \n that is at end of the selection | ||
| 402 | if (join && ch == '\n') { | ||
| 403 | join--; | ||
| 404 | len--; | ||
| 405 | } | ||
| 406 | |||
| 407 | if (join) { | ||
| 408 | if (ch == '\n') { | ||
| 409 | // Don't add space to end of line | ||
| 410 | buffer_delete_bytes(view, len); | ||
| 411 | } else { | ||
| 412 | buffer_replace_bytes(view, len, " ", 1); | ||
| 413 | } | ||
| 414 | } | ||
| 415 | end_change_chain(view); | ||
| 416 | } | ||
| 417 | |||
| 418 | void join_lines(View *view) | ||
| 419 | { | ||
| 420 | BlockIter bi = view->cursor; | ||
| 421 | |||
| 422 | if (view->selection) { | ||
| 423 | join_selection(view); | ||
| 424 | return; | ||
| 425 | } | ||
| 426 | |||
| 427 | if (!block_iter_next_line(&bi)) { | ||
| 428 | return; | ||
| 429 | } | ||
| 430 | if (block_iter_is_eof(&bi)) { | ||
| 431 | return; | ||
| 432 | } | ||
| 433 | |||
| 434 | BlockIter next = bi; | ||
| 435 | CodePoint u; | ||
| 436 | size_t count = 1; | ||
| 437 | block_iter_prev_char(&bi, &u); | ||
| 438 | while (block_iter_prev_char(&bi, &u)) { | ||
| 439 | if (u != '\t' && u != ' ') { | ||
| 440 | block_iter_next_char(&bi, &u); | ||
| 441 | break; | ||
| 442 | } | ||
| 443 | count++; | ||
| 444 | } | ||
| 445 | while (block_iter_next_char(&next, &u)) { | ||
| 446 | if (u != '\t' && u != ' ') { | ||
| 447 | break; | ||
| 448 | } | ||
| 449 | count++; | ||
| 450 | } | ||
| 451 | |||
| 452 | view->cursor = bi; | ||
| 453 | if (u == '\n') { | ||
| 454 | buffer_delete_bytes(view, count); | ||
| 455 | } else { | ||
| 456 | buffer_replace_bytes(view, count, " ", 1); | ||
| 457 | } | ||
| 458 | } | ||
| 459 | |||
| 460 | void clear_lines(View *view, bool auto_indent) | ||
| 461 | { | ||
| 462 | char *indent = NULL; | ||
| 463 | if (auto_indent) { | ||
| 464 | BlockIter bi = view->cursor; | ||
| 465 | if (block_iter_prev_line(&bi) && find_non_empty_line_bwd(&bi)) { | ||
| 466 | StringView line; | ||
| 467 | fill_line_ref(&bi, &line); | ||
| 468 | indent = get_indent_for_next_line(&view->buffer->options, &line); | ||
| 469 | } | ||
| 470 | } | ||
| 471 | |||
| 472 | size_t del_count = 0; | ||
| 473 | if (view->selection) { | ||
| 474 | view->selection = SELECT_LINES; | ||
| 475 | del_count = prepare_selection(view); | ||
| 476 | unselect(view); | ||
| 477 | // Don't delete last newline | ||
| 478 | if (del_count) { | ||
| 479 | del_count--; | ||
| 480 | } | ||
| 481 | } else { | ||
| 482 | block_iter_eol(&view->cursor); | ||
| 483 | del_count = block_iter_bol(&view->cursor); | ||
| 484 | } | ||
| 485 | |||
| 486 | if (!indent && !del_count) { | ||
| 487 | return; | ||
| 488 | } | ||
| 489 | |||
| 490 | size_t ins_count = indent ? strlen(indent) : 0; | ||
| 491 | buffer_replace_bytes(view, del_count, indent, ins_count); | ||
| 492 | free(indent); | ||
| 493 | block_iter_skip_bytes(&view->cursor, ins_count); | ||
| 494 | } | ||
| 495 | |||
| 496 | void delete_lines(View *view) | ||
| 497 | { | ||
| 498 | long x = view_get_preferred_x(view); | ||
| 499 | size_t del_count; | ||
| 500 | if (view->selection) { | ||
| 501 | view->selection = SELECT_LINES; | ||
| 502 | del_count = prepare_selection(view); | ||
| 503 | unselect(view); | ||
| 504 | } else { | ||
| 505 | block_iter_bol(&view->cursor); | ||
| 506 | BlockIter tmp = view->cursor; | ||
| 507 | del_count = block_iter_eat_line(&tmp); | ||
| 508 | } | ||
| 509 | buffer_delete_bytes(view, del_count); | ||
| 510 | move_to_preferred_x(view, x); | ||
| 511 | } | ||
| 512 | |||
| 513 | void new_line(View *view, bool above) | ||
| 514 | { | ||
| 515 | if (above && block_iter_prev_line(&view->cursor) == 0) { | ||
| 516 | // Already on first line; insert newline at bof | ||
| 517 | block_iter_bol(&view->cursor); | ||
| 518 | buffer_insert_bytes(view, "\n", 1); | ||
| 519 | return; | ||
| 520 | } | ||
| 521 | |||
| 522 | const LocalOptions *options = &view->buffer->options; | ||
| 523 | char *ins = NULL; | ||
| 524 | block_iter_eol(&view->cursor); | ||
| 525 | |||
| 526 | if (options->auto_indent) { | ||
| 527 | BlockIter bi = view->cursor; | ||
| 528 | if (find_non_empty_line_bwd(&bi)) { | ||
| 529 | StringView line; | ||
| 530 | fill_line_ref(&bi, &line); | ||
| 531 | ins = get_indent_for_next_line(options, &line); | ||
| 532 | } | ||
| 533 | } | ||
| 534 | |||
| 535 | size_t ins_count; | ||
| 536 | if (ins) { | ||
| 537 | ins_count = strlen(ins); | ||
| 538 | memmove(ins + 1, ins, ins_count); | ||
| 539 | ins[0] = '\n'; | ||
| 540 | ins_count++; | ||
| 541 | buffer_insert_bytes(view, ins, ins_count); | ||
| 542 | free(ins); | ||
| 543 | } else { | ||
| 544 | ins_count = 1; | ||
| 545 | buffer_insert_bytes(view, "\n", 1); | ||
| 546 | } | ||
| 547 | |||
| 548 | block_iter_skip_bytes(&view->cursor, ins_count); | ||
| 549 | } | ||
| 550 | |||
| 551 | static void add_word(ParagraphFormatter *pf, const char *word, size_t len) | ||
| 552 | { | ||
| 553 | size_t i = 0; | ||
| 554 | size_t word_width = 0; | ||
| 555 | while (i < len) { | ||
| 556 | word_width += u_char_width(u_get_char(word, len, &i)); | ||
| 557 | } | ||
| 558 | |||
| 559 | if (pf->cur_width && pf->cur_width + 1 + word_width > pf->text_width) { | ||
| 560 | string_append_byte(&pf->buf, '\n'); | ||
| 561 | pf->cur_width = 0; | ||
| 562 | } | ||
| 563 | |||
| 564 | if (pf->cur_width == 0) { | ||
| 565 | if (pf->indent_len) { | ||
| 566 | string_append_buf(&pf->buf, pf->indent, pf->indent_len); | ||
| 567 | } | ||
| 568 | pf->cur_width = pf->indent_width; | ||
| 569 | } else { | ||
| 570 | string_append_byte(&pf->buf, ' '); | ||
| 571 | pf->cur_width++; | ||
| 572 | } | ||
| 573 | |||
| 574 | string_append_buf(&pf->buf, word, len); | ||
| 575 | pf->cur_width += word_width; | ||
| 576 | } | ||
| 577 | |||
| 578 | static bool is_paragraph_separator(const StringView *line) | ||
| 579 | { | ||
| 580 | StringView trimmed = *line; | ||
| 581 | strview_trim(&trimmed); | ||
| 582 | |||
| 583 | return | ||
| 584 | trimmed.length == 0 | ||
| 585 | // TODO: make this configurable | ||
| 586 | || strview_equal_cstring(&trimmed, "/*") | ||
| 587 | || strview_equal_cstring(&trimmed, "*/") | ||
| 588 | ; | ||
| 589 | } | ||
| 590 | |||
| 591 | static bool in_paragraph(const LocalOptions *options, const StringView *line, size_t indent_width) | ||
| 592 | { | ||
| 593 | if (get_indent_width(options, line) != indent_width) { | ||
| 594 | return false; | ||
| 595 | } | ||
| 596 | return !is_paragraph_separator(line); | ||
| 597 | } | ||
| 598 | |||
| 599 | static size_t paragraph_size(View *view) | ||
| 600 | { | ||
| 601 | const LocalOptions *options = &view->buffer->options; | ||
| 602 | BlockIter bi = view->cursor; | ||
| 603 | StringView line; | ||
| 604 | block_iter_bol(&bi); | ||
| 605 | fill_line_ref(&bi, &line); | ||
| 606 | if (is_paragraph_separator(&line)) { | ||
| 607 | // Not in paragraph | ||
| 608 | return 0; | ||
| 609 | } | ||
| 610 | size_t indent_width = get_indent_width(options, &line); | ||
| 611 | |||
| 612 | // Go to beginning of paragraph | ||
| 613 | while (block_iter_prev_line(&bi)) { | ||
| 614 | fill_line_ref(&bi, &line); | ||
| 615 | if (!in_paragraph(options, &line, indent_width)) { | ||
| 616 | block_iter_eat_line(&bi); | ||
| 617 | break; | ||
| 618 | } | ||
| 619 | } | ||
| 620 | view->cursor = bi; | ||
| 621 | |||
| 622 | // Get size of paragraph | ||
| 623 | size_t size = 0; | ||
| 624 | do { | ||
| 625 | size_t bytes = block_iter_eat_line(&bi); | ||
| 626 | if (!bytes) { | ||
| 627 | break; | ||
| 628 | } | ||
| 629 | size += bytes; | ||
| 630 | fill_line_ref(&bi, &line); | ||
| 631 | } while (in_paragraph(options, &line, indent_width)); | ||
| 632 | return size; | ||
| 633 | } | ||
| 634 | |||
| 635 | void format_paragraph(View *view, size_t text_width) | ||
| 636 | { | ||
| 637 | size_t len; | ||
| 638 | if (view->selection) { | ||
| 639 | view->selection = SELECT_LINES; | ||
| 640 | len = prepare_selection(view); | ||
| 641 | } else { | ||
| 642 | len = paragraph_size(view); | ||
| 643 | } | ||
| 644 | if (!len) { | ||
| 645 | return; | ||
| 646 | } | ||
| 647 | |||
| 648 | const LocalOptions *options = &view->buffer->options; | ||
| 649 | char *sel = block_iter_get_bytes(&view->cursor, len); | ||
| 650 | StringView sv = string_view(sel, len); | ||
| 651 | size_t indent_width = get_indent_width(options, &sv); | ||
| 652 | char *indent = make_indent(options, indent_width); | ||
| 653 | |||
| 654 | ParagraphFormatter pf = { | ||
| 655 | .buf = STRING_INIT, | ||
| 656 | .indent = indent, | ||
| 657 | .indent_len = indent ? strlen(indent) : 0, | ||
| 658 | .indent_width = indent_width, | ||
| 659 | .cur_width = 0, | ||
| 660 | .text_width = text_width | ||
| 661 | }; | ||
| 662 | |||
| 663 | for (size_t i = 0; true; ) { | ||
| 664 | while (i < len) { | ||
| 665 | size_t tmp = i; | ||
| 666 | if (!u_is_breakable_whitespace(u_get_char(sel, len, &tmp))) { | ||
| 667 | break; | ||
| 668 | } | ||
| 669 | i = tmp; | ||
| 670 | } | ||
| 671 | if (i == len) { | ||
| 672 | break; | ||
| 673 | } | ||
| 674 | |||
| 675 | size_t start = i; | ||
| 676 | while (i < len) { | ||
| 677 | size_t tmp = i; | ||
| 678 | if (u_is_breakable_whitespace(u_get_char(sel, len, &tmp))) { | ||
| 679 | break; | ||
| 680 | } | ||
| 681 | i = tmp; | ||
| 682 | } | ||
| 683 | |||
| 684 | add_word(&pf, sel + start, i - start); | ||
| 685 | } | ||
| 686 | |||
| 687 | if (pf.buf.len) { | ||
| 688 | string_append_byte(&pf.buf, '\n'); | ||
| 689 | } | ||
| 690 | buffer_replace_bytes(view, len, pf.buf.buffer, pf.buf.len); | ||
| 691 | if (pf.buf.len) { | ||
| 692 | block_iter_skip_bytes(&view->cursor, pf.buf.len - 1); | ||
| 693 | } | ||
| 694 | string_free(&pf.buf); | ||
| 695 | free(pf.indent); | ||
| 696 | free(sel); | ||
| 697 | |||
| 698 | unselect(view); | ||
| 699 | } | ||
| 700 | |||
| 701 | void change_case(View *view, char mode) | ||
| 702 | { | ||
| 703 | bool was_selecting = false; | ||
| 704 | bool move = true; | ||
| 705 | size_t text_len; | ||
| 706 | if (view->selection) { | ||
| 707 | SelectionInfo info; | ||
| 708 | init_selection(view, &info); | ||
| 709 | view->cursor = info.si; | ||
| 710 | text_len = info.eo - info.so; | ||
| 711 | unselect(view); | ||
| 712 | was_selecting = true; | ||
| 713 | move = !info.swapped; | ||
| 714 | } else { | ||
| 715 | CodePoint u; | ||
| 716 | if (!block_iter_get_char(&view->cursor, &u)) { | ||
| 717 | return; | ||
| 718 | } | ||
| 719 | text_len = u_char_size(u); | ||
| 720 | } | ||
| 721 | |||
| 722 | String dst = string_new(text_len); | ||
| 723 | char *src = block_iter_get_bytes(&view->cursor, text_len); | ||
| 724 | size_t i = 0; | ||
| 725 | switch (mode) { | ||
| 726 | case 'l': | ||
| 727 | while (i < text_len) { | ||
| 728 | CodePoint u = u_to_lower(u_get_char(src, text_len, &i)); | ||
| 729 | string_append_codepoint(&dst, u); | ||
| 730 | } | ||
| 731 | break; | ||
| 732 | case 'u': | ||
| 733 | while (i < text_len) { | ||
| 734 | CodePoint u = u_to_upper(u_get_char(src, text_len, &i)); | ||
| 735 | string_append_codepoint(&dst, u); | ||
| 736 | } | ||
| 737 | break; | ||
| 738 | case 't': | ||
| 739 | while (i < text_len) { | ||
| 740 | CodePoint u = u_get_char(src, text_len, &i); | ||
| 741 | u = u_is_upper(u) ? u_to_lower(u) : u_to_upper(u); | ||
| 742 | string_append_codepoint(&dst, u); | ||
| 743 | } | ||
| 744 | break; | ||
| 745 | default: | ||
| 746 | BUG("unhandled case mode"); | ||
| 747 | } | ||
| 748 | |||
| 749 | buffer_replace_bytes(view, text_len, dst.buffer, dst.len); | ||
| 750 | free(src); | ||
| 751 | |||
| 752 | if (move && dst.len > 0) { | ||
| 753 | if (was_selecting) { | ||
| 754 | // Move cursor back to where it was | ||
| 755 | size_t idx = dst.len; | ||
| 756 | u_prev_char(dst.buffer, &idx); | ||
| 757 | block_iter_skip_bytes(&view->cursor, idx); | ||
| 758 | } else { | ||
| 759 | block_iter_skip_bytes(&view->cursor, dst.len); | ||
| 760 | } | ||
| 761 | } | ||
| 762 | |||
| 763 | string_free(&dst); | ||
| 764 | } | ||
