move.c (20963B)
1 /************************************************************************** 2 * move.c -- This file is part of GNU nano. * 3 * * 4 * Copyright (C) 1999-2011, 2013-2025 Free Software Foundation, Inc. * 5 * Copyright (C) 2014-2018 Benno Schulenberg * 6 * * 7 * GNU nano is free software: you can redistribute it and/or modify * 8 * it under the terms of the GNU General Public License as published * 9 * by the Free Software Foundation, either version 3 of the License, * 10 * or (at your option) any later version. * 11 * * 12 * GNU nano is distributed in the hope that it will be useful, * 13 * but WITHOUT ANY WARRANTY; without even the implied warranty * 14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * 15 * See the GNU General Public License for more details. * 16 * * 17 * You should have received a copy of the GNU General Public License * 18 * along with this program. If not, see https://gnu.org/licenses/. * 19 * * 20 **************************************************************************/ 21 22 #include "prototypes.h" 23 24 #include <string.h> 25 26 /* Move to the first line of the file. */ 27 void to_first_line(void) 28 { 29 openfile->current = openfile->filetop; 30 openfile->current_x = 0; 31 openfile->placewewant = 0; 32 33 refresh_needed = TRUE; 34 } 35 36 /* Move to the last line of the file. */ 37 void to_last_line(void) 38 { 39 openfile->current = openfile->filebot; 40 openfile->current_x = (inhelp) ? 0 : strlen(openfile->filebot->data); 41 openfile->placewewant = xplustabs(); 42 43 /* Set the last line of the screen as the target for the cursor. */ 44 openfile->cursor_row = editwinrows - 1; 45 46 refresh_needed = TRUE; 47 #ifdef ENABLE_COLOR 48 recook |= perturbed; 49 #endif 50 focusing = FALSE; 51 } 52 53 /* Determine the actual current chunk and the target column. */ 54 void get_edge_and_target(size_t *leftedge, size_t *target_column) 55 { 56 #ifndef NANO_TINY 57 if (ISSET(SOFTWRAP)) { 58 size_t shim = editwincols * (1 + (tabsize / editwincols)); 59 60 *leftedge = leftedge_for(xplustabs(), openfile->current); 61 *target_column = (openfile->placewewant + shim - *leftedge) % editwincols; 62 } else 63 #endif 64 { 65 *leftedge = 0; 66 *target_column = openfile->placewewant; 67 } 68 } 69 70 /* Return the index in line->data that corresponds to the given column on the 71 * chunk that starts at the given leftedge. If the target column has landed 72 * on a tab, prevent the cursor from falling back a row when moving forward, 73 * or from skipping a row when moving backward, by incrementing the index. */ 74 size_t proper_x(linestruct *line, size_t *leftedge, bool forward, 75 size_t column, bool *shifted) 76 { 77 size_t index = actual_x(line->data, column); 78 79 #ifndef NANO_TINY 80 if (ISSET(SOFTWRAP) && line->data[index] == '\t' && 81 ((forward && wideness(line->data, index) < *leftedge) || 82 (!forward && column / tabsize == (*leftedge - 1) / tabsize && 83 column / tabsize < (*leftedge + editwincols - 1) / tabsize))) { 84 index++; 85 86 if (shifted != NULL) 87 *shifted = TRUE; 88 } 89 90 if (ISSET(SOFTWRAP)) 91 *leftedge = leftedge_for(wideness(line->data, index), line); 92 #endif 93 94 return index; 95 } 96 97 /* Adjust the values for current_x and placewewant in case we have landed in 98 * the middle of a tab that crosses a row boundary. */ 99 void set_proper_index_and_pww(size_t *leftedge, size_t target, bool forward) 100 { 101 size_t was_edge = *leftedge; 102 bool shifted = FALSE; 103 104 openfile->current_x = proper_x(openfile->current, leftedge, forward, 105 actual_last_column(*leftedge, target), &shifted); 106 107 /* If the index was incremented, try going to the target column. */ 108 if (shifted || *leftedge < was_edge) 109 openfile->current_x = proper_x(openfile->current, leftedge, forward, 110 actual_last_column(*leftedge, target), &shifted); 111 112 openfile->placewewant = *leftedge + target; 113 } 114 115 /* Move up almost one screenful. */ 116 void do_page_up(void) 117 { 118 int mustmove = (editwinrows < 3) ? 1 : editwinrows - 2; 119 size_t leftedge, target_column; 120 121 #ifndef NANO_TINY 122 /* If we're not in smooth scrolling mode, put the cursor at the 123 * beginning of the top line of the edit window, as Pico does. */ 124 if (ISSET(JUMPY_SCROLLING)) { 125 openfile->current = openfile->edittop; 126 leftedge = openfile->firstcolumn; 127 openfile->cursor_row = 0; 128 target_column = 0; 129 } else 130 #endif 131 get_edge_and_target(&leftedge, &target_column); 132 133 /* Move up the required number of lines or chunks. If we can't, we're 134 * at the top of the file, so put the cursor there and get out. */ 135 if (go_back_chunks(mustmove, &openfile->current, &leftedge) > 0) { 136 to_first_line(); 137 return; 138 } 139 140 set_proper_index_and_pww(&leftedge, target_column, FALSE); 141 142 /* Move the viewport so that the cursor stays immobile, if possible. */ 143 adjust_viewport(STATIONARY); 144 refresh_needed = TRUE; 145 } 146 147 /* Move down almost one screenful. */ 148 void do_page_down(void) 149 { 150 int mustmove = (editwinrows < 3) ? 1 : editwinrows - 2; 151 size_t leftedge, target_column; 152 153 #ifndef NANO_TINY 154 /* If we're not in smooth scrolling mode, put the cursor at the 155 * beginning of the top line of the edit window, as Pico does. */ 156 if (ISSET(JUMPY_SCROLLING)) { 157 openfile->current = openfile->edittop; 158 leftedge = openfile->firstcolumn; 159 openfile->cursor_row = 0; 160 target_column = 0; 161 } else 162 #endif 163 get_edge_and_target(&leftedge, &target_column); 164 165 /* Move down the required number of lines or chunks. If we can't, we're 166 * at the bottom of the file, so put the cursor there and get out. */ 167 if (go_forward_chunks(mustmove, &openfile->current, &leftedge) > 0) { 168 to_last_line(); 169 return; 170 } 171 172 set_proper_index_and_pww(&leftedge, target_column, TRUE); 173 174 /* Move the viewport so that the cursor stays immobile, if possible. */ 175 adjust_viewport(STATIONARY); 176 refresh_needed = TRUE; 177 } 178 179 #ifndef NANO_TINY 180 /* Place the cursor on the first row in the viewport. */ 181 void to_top_row(void) 182 { 183 size_t leftedge, offset; 184 185 get_edge_and_target(&leftedge, &offset); 186 187 openfile->current = openfile->edittop; 188 leftedge = openfile->firstcolumn; 189 190 set_proper_index_and_pww(&leftedge, offset, FALSE); 191 192 refresh_needed = (openfile->mark != NULL); 193 } 194 195 /* Place the cursor on the last row in the viewport, when possible. */ 196 void to_bottom_row(void) 197 { 198 size_t leftedge, offset; 199 200 get_edge_and_target(&leftedge, &offset); 201 202 openfile->current = openfile->edittop; 203 leftedge = openfile->firstcolumn; 204 205 go_forward_chunks(editwinrows - 1, &openfile->current, &leftedge); 206 set_proper_index_and_pww(&leftedge, offset, TRUE); 207 208 refresh_needed = (openfile->mark != NULL); 209 } 210 211 /* Put the cursor line at the center, then the top, then the bottom. */ 212 void do_cycle(void) 213 { 214 if (cycling_aim == 0) 215 adjust_viewport(CENTERING); 216 else { 217 openfile->cursor_row = (cycling_aim == 1) ? 0 : editwinrows - 1; 218 adjust_viewport(STATIONARY); 219 } 220 221 cycling_aim = (cycling_aim + 1) % 3; 222 223 draw_all_subwindows(); 224 full_refresh(); 225 } 226 227 /* Scroll the line with the cursor to the center of the screen. */ 228 void do_center(void) 229 { 230 adjust_viewport(CENTERING); 231 draw_all_subwindows(); 232 full_refresh(); 233 } 234 #endif /* !NANO_TINY */ 235 236 #ifdef ENABLE_JUSTIFY 237 /* Move to the first beginning of a paragraph before the current line. */ 238 void do_para_begin(linestruct **line) 239 { 240 if ((*line)->prev != NULL) 241 *line = (*line)->prev; 242 243 while (!begpar(*line, 0)) 244 *line = (*line)->prev; 245 } 246 247 /* Move down to the last line of the first found paragraph. */ 248 void do_para_end(linestruct **line) 249 { 250 while ((*line)->next != NULL && !inpar(*line)) 251 *line = (*line)->next; 252 253 while ((*line)->next != NULL && inpar((*line)->next) && 254 !begpar((*line)->next, 0)) 255 *line = (*line)->next; 256 } 257 258 /* Move up to first start of a paragraph before the current line. */ 259 void to_para_begin(void) 260 { 261 linestruct *was_current = openfile->current; 262 263 do_para_begin(&openfile->current); 264 openfile->current_x = 0; 265 266 edit_redraw(was_current, CENTERING); 267 } 268 269 /* Move down to just after the first found end of a paragraph. */ 270 void to_para_end(void) 271 { 272 linestruct *was_current = openfile->current; 273 274 do_para_end(&openfile->current); 275 276 /* Step beyond the last line of the paragraph, if possible; 277 * otherwise, move to the end of the line. */ 278 if (openfile->current->next != NULL) { 279 openfile->current = openfile->current->next; 280 openfile->current_x = 0; 281 } else 282 openfile->current_x = strlen(openfile->current->data); 283 284 edit_redraw(was_current, CENTERING); 285 #ifdef ENABLE_COLOR 286 recook |= perturbed; 287 #endif 288 } 289 #endif /* ENABLE_JUSTIFY */ 290 291 /* Move to the preceding block of text. */ 292 void to_prev_block(void) 293 { 294 linestruct *was_current = openfile->current; 295 bool is_text = FALSE, seen_text = FALSE; 296 297 /* Skip backward until first blank line after some nonblank line(s). */ 298 while (openfile->current->prev != NULL && (!seen_text || is_text)) { 299 openfile->current = openfile->current->prev; 300 is_text = !white_string(openfile->current->data); 301 seen_text = seen_text || is_text; 302 } 303 304 /* Step forward one line again if we passed text but this line is blank. */ 305 if (seen_text && openfile->current->next != NULL && 306 white_string(openfile->current->data)) 307 openfile->current = openfile->current->next; 308 309 openfile->current_x = 0; 310 edit_redraw(was_current, CENTERING); 311 } 312 313 /* Move to the next block of text. */ 314 void to_next_block(void) 315 { 316 linestruct *was_current = openfile->current; 317 bool is_white = white_string(openfile->current->data); 318 bool seen_white = is_white; 319 320 /* Skip forward until first nonblank line after some blank line(s). */ 321 while (openfile->current->next != NULL && (!seen_white || is_white)) { 322 openfile->current = openfile->current->next; 323 is_white = white_string(openfile->current->data); 324 seen_white = seen_white || is_white; 325 } 326 327 openfile->current_x = 0; 328 edit_redraw(was_current, CENTERING); 329 #ifdef ENABLE_COLOR 330 recook |= perturbed; 331 #endif 332 } 333 334 /* Move to the previous word. */ 335 void do_prev_word(void) 336 { 337 bool punctuation_as_letters = ISSET(WORD_BOUNDS); 338 bool seen_a_word = FALSE, step_forward = FALSE; 339 340 /* Move backward until we pass over the start of a word. */ 341 while (TRUE) { 342 /* If at the head of a line, move to the end of the preceding one. */ 343 if (openfile->current_x == 0) { 344 if (openfile->current->prev == NULL) 345 break; 346 openfile->current = openfile->current->prev; 347 openfile->current_x = strlen(openfile->current->data); 348 } 349 350 /* Step back one character. */ 351 openfile->current_x = step_left(openfile->current->data, 352 openfile->current_x); 353 354 if (is_word_char(openfile->current->data + openfile->current_x, 355 punctuation_as_letters)) { 356 seen_a_word = TRUE; 357 /* If at the head of a line now, this surely is a word start. */ 358 if (openfile->current_x == 0) 359 break; 360 #ifdef ENABLE_UTF8 361 } else if (is_zerowidth(openfile->current->data + openfile->current_x)) { 362 ; /* skip */ 363 #endif 364 } else if (seen_a_word) { 365 /* This is space now: we've overshot the start of the word. */ 366 step_forward = TRUE; 367 break; 368 } 369 } 370 371 if (step_forward) 372 /* Move one character forward again to sit on the start of the word. */ 373 openfile->current_x = step_right(openfile->current->data, 374 openfile->current_x); 375 } 376 377 /* Move to the next word. If after_ends is TRUE, stop at the ends of words 378 * instead of at their beginnings. Return TRUE if we started on a word. */ 379 bool do_next_word(bool after_ends) 380 { 381 bool punctuation_as_letters = ISSET(WORD_BOUNDS); 382 bool started_on_word = is_word_char(openfile->current->data + 383 openfile->current_x, punctuation_as_letters); 384 bool seen_space = !started_on_word; 385 #ifndef NANO_TINY 386 bool seen_word = started_on_word; 387 #endif 388 389 /* Move forward until we reach the start of a word. */ 390 while (TRUE) { 391 /* If at the end of a line, move to the beginning of the next one. */ 392 if (openfile->current->data[openfile->current_x] == '\0') { 393 /* When at end of file, stop. */ 394 if (openfile->current->next == NULL) 395 break; 396 openfile->current = openfile->current->next; 397 openfile->current_x = 0; 398 seen_space = TRUE; 399 } else { 400 /* Step forward one character. */ 401 openfile->current_x = step_right(openfile->current->data, 402 openfile->current_x); 403 } 404 405 #ifndef NANO_TINY 406 if (after_ends) { 407 /* If this is a word character, continue; else it's a separator, 408 * and if we've already seen a word, then it's a word end. */ 409 if (is_word_char(openfile->current->data + openfile->current_x, 410 punctuation_as_letters)) 411 seen_word = TRUE; 412 #ifdef ENABLE_UTF8 413 else if (is_zerowidth(openfile->current->data + openfile->current_x)) 414 ; /* skip */ 415 #endif 416 else if (seen_word) 417 break; 418 } else 419 #endif 420 { 421 #ifdef ENABLE_UTF8 422 if (is_zerowidth(openfile->current->data + openfile->current_x)) 423 ; /* skip */ 424 else 425 #endif 426 /* If this is not a word character, then it's a separator; else 427 * if we've already seen a separator, then it's a word start. */ 428 if (!is_word_char(openfile->current->data + openfile->current_x, 429 punctuation_as_letters)) 430 seen_space = TRUE; 431 else if (seen_space) 432 break; 433 } 434 } 435 436 return started_on_word; 437 } 438 439 /* Move to the previous word in the file, and update the screen afterwards. */ 440 void to_prev_word(void) 441 { 442 linestruct *was_current = openfile->current; 443 444 do_prev_word(); 445 446 edit_redraw(was_current, FLOWING); 447 } 448 449 /* Move to the next word in the file. If the AFTER_ENDS flag is set, stop 450 * at word ends instead of beginnings. Update the screen afterwards. */ 451 void to_next_word(void) 452 { 453 linestruct *was_current = openfile->current; 454 455 do_next_word(ISSET(AFTER_ENDS)); 456 457 edit_redraw(was_current, FLOWING); 458 } 459 460 /* Move to the beginning of the current line (or softwrapped chunk). 461 * When enabled, do a smart home. When softwrapping, go the beginning 462 * of the full line when already at the start of a chunk. */ 463 void do_home(void) 464 { 465 linestruct *was_current = openfile->current; 466 size_t was_column = xplustabs(); 467 bool moved_off_chunk = TRUE; 468 #ifndef NANO_TINY 469 bool moved = FALSE; 470 size_t leftedge = 0; 471 size_t left_x = 0; 472 473 if (ISSET(SOFTWRAP)) { 474 leftedge = leftedge_for(was_column, openfile->current); 475 left_x = proper_x(openfile->current, &leftedge, FALSE, leftedge, NULL); 476 } 477 478 if (ISSET(SMART_HOME)) { 479 size_t indent_x = indent_length(openfile->current->data); 480 481 if (openfile->current->data[indent_x] != '\0') { 482 /* If we're exactly on the indent, move fully home. Otherwise, 483 * when not softwrapping or not after the first nonblank chunk, 484 * move to the first nonblank character. */ 485 if (openfile->current_x == indent_x) { 486 openfile->current_x = 0; 487 moved = TRUE; 488 } else if (left_x <= indent_x) { 489 openfile->current_x = indent_x; 490 moved = TRUE; 491 } 492 } 493 } 494 495 if (!moved && ISSET(SOFTWRAP)) { 496 /* If already at the left edge of the screen, move fully home. 497 * Otherwise, move to the left edge. */ 498 if (openfile->current_x == left_x) 499 openfile->current_x = 0; 500 else { 501 openfile->current_x = left_x; 502 openfile->placewewant = leftedge; 503 moved_off_chunk = FALSE; 504 } 505 } else if (!moved) 506 #endif 507 openfile->current_x = 0; 508 509 if (moved_off_chunk) 510 openfile->placewewant = xplustabs(); 511 512 /* If we changed chunk, we might be offscreen. Otherwise, 513 * update current if the mark is on or we changed "page". */ 514 if (ISSET(SOFTWRAP) && moved_off_chunk) 515 edit_redraw(was_current, FLOWING); 516 else if (line_needs_update(was_column, openfile->placewewant)) 517 update_line(openfile->current, openfile->current_x); 518 } 519 520 /* Move to the end of the current line (or softwrapped chunk). 521 * When softwrapping and already at the end of a chunk, go to the 522 * end of the full line. */ 523 void do_end(void) 524 { 525 linestruct *was_current = openfile->current; 526 size_t was_column = xplustabs(); 527 size_t line_len = strlen(openfile->current->data); 528 bool moved_off_chunk = TRUE; 529 530 #ifndef NANO_TINY 531 if (ISSET(SOFTWRAP)) { 532 bool kickoff = TRUE; 533 bool last_chunk = FALSE; 534 size_t leftedge = leftedge_for(was_column, openfile->current); 535 size_t rightedge = get_softwrap_breakpoint(openfile->current->data, 536 leftedge, &kickoff, &last_chunk); 537 size_t right_x; 538 539 /* If we're on the last chunk, we're already at the end of the line. 540 * Otherwise, we're one column past the end of the line. Shifting 541 * backwards one column might put us in the middle of a multi-column 542 * character, but actual_x() will fix that. */ 543 if (!last_chunk) 544 rightedge--; 545 546 right_x = actual_x(openfile->current->data, rightedge); 547 548 /* If already at the right edge of the screen, move fully to 549 * the end of the line. Otherwise, move to the right edge. */ 550 if (openfile->current_x == right_x) 551 openfile->current_x = line_len; 552 else { 553 openfile->current_x = right_x; 554 openfile->placewewant = rightedge; 555 moved_off_chunk = FALSE; 556 } 557 } else 558 #endif 559 openfile->current_x = line_len; 560 561 if (moved_off_chunk) 562 openfile->placewewant = xplustabs(); 563 564 /* If we changed chunk, we might be offscreen. Otherwise, 565 * update current if the mark is on or we changed "page". */ 566 if (ISSET(SOFTWRAP) && moved_off_chunk) 567 edit_redraw(was_current, FLOWING); 568 else if (line_needs_update(was_column, openfile->placewewant)) 569 update_line(openfile->current, openfile->current_x); 570 } 571 572 /* Move the cursor to the preceding line or chunk. */ 573 void do_up(void) 574 { 575 linestruct *was_current = openfile->current; 576 size_t leftedge, target_column; 577 578 get_edge_and_target(&leftedge, &target_column); 579 580 /* If we can't move up one line or chunk, we're at top of file. */ 581 if (go_back_chunks(1, &openfile->current, &leftedge) > 0) 582 return; 583 584 set_proper_index_and_pww(&leftedge, target_column, FALSE); 585 586 if (openfile->cursor_row == 0 && !ISSET(JUMPY_SCROLLING) && 587 (tabsize < editwincols || !ISSET(SOFTWRAP))) 588 edit_scroll(BACKWARD); 589 else 590 edit_redraw(was_current, FLOWING); 591 592 /* <Up> should not change placewewant, so restore it. */ 593 openfile->placewewant = leftedge + target_column; 594 } 595 596 /* Move the cursor to next line or chunk. */ 597 void do_down(void) 598 { 599 linestruct *was_current = openfile->current; 600 size_t leftedge, target_column; 601 602 get_edge_and_target(&leftedge, &target_column); 603 604 /* If we can't move down one line or chunk, we're at bottom of file. */ 605 if (go_forward_chunks(1, &openfile->current, &leftedge) > 0) 606 return; 607 608 set_proper_index_and_pww(&leftedge, target_column, TRUE); 609 610 if (openfile->cursor_row == editwinrows - 1 && !ISSET(JUMPY_SCROLLING) && 611 (tabsize < editwincols || !ISSET(SOFTWRAP))) 612 edit_scroll(FORWARD); 613 else 614 edit_redraw(was_current, FLOWING); 615 616 /* <Down> should not change placewewant, so restore it. */ 617 openfile->placewewant = leftedge + target_column; 618 } 619 620 #if !defined(NANO_TINY) || defined(ENABLE_HELP) 621 /* Scroll up one line or chunk without moving the cursor textwise. */ 622 void do_scroll_up(void) 623 { 624 /* When the top of the file is onscreen, we can't scroll. */ 625 if (openfile->edittop->prev == NULL && openfile->firstcolumn == 0) 626 return; 627 628 if (openfile->cursor_row == editwinrows - 1) 629 do_up(); 630 631 if (editwinrows > 1) 632 edit_scroll(BACKWARD); 633 } 634 635 /* Scroll down one line or chunk without moving the cursor textwise. */ 636 void do_scroll_down(void) 637 { 638 if (openfile->cursor_row == 0) 639 do_down(); 640 641 if (editwinrows > 1 && (openfile->edittop->next != NULL 642 #ifndef NANO_TINY 643 || (ISSET(SOFTWRAP) && (extra_chunks_in(openfile->edittop) > 644 chunk_for(openfile->firstcolumn, openfile->edittop))) 645 #endif 646 )) 647 edit_scroll(FORWARD); 648 } 649 #endif /* !NANO_TINY || ENABLE_HELP */ 650 651 /* Move left one character. */ 652 void do_left(void) 653 { 654 linestruct *was_current = openfile->current; 655 656 if (openfile->current_x > 0) { 657 openfile->current_x = step_left(openfile->current->data, 658 openfile->current_x); 659 #ifdef ENABLE_UTF8 660 while (openfile->current_x > 0 && 661 is_zerowidth(openfile->current->data + openfile->current_x)) 662 openfile->current_x = step_left(openfile->current->data, 663 openfile->current_x); 664 #endif 665 } else if (openfile->current != openfile->filetop) { 666 openfile->current = openfile->current->prev; 667 openfile->current_x = strlen(openfile->current->data); 668 } 669 670 edit_redraw(was_current, FLOWING); 671 } 672 673 /* Move right one character. */ 674 void do_right(void) 675 { 676 linestruct *was_current = openfile->current; 677 678 if (openfile->current->data[openfile->current_x] != '\0') { 679 openfile->current_x = step_right(openfile->current->data, 680 openfile->current_x); 681 #ifdef ENABLE_UTF8 682 while (openfile->current->data[openfile->current_x] != '\0' && 683 is_zerowidth(openfile->current->data + openfile->current_x)) 684 openfile->current_x = step_right(openfile->current->data, 685 openfile->current_x); 686 #endif 687 } else if (openfile->current != openfile->filebot) { 688 openfile->current = openfile->current->next; 689 openfile->current_x = 0; 690 } 691 692 edit_redraw(was_current, FLOWING); 693 }