1/* $Id: move.c,v 1.67 2007/01/01 05:15:32 dolorous Exp $ */ 2/************************************************************************** 3 * move.c * 4 * * 5 * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 Chris Allegretta * 6 * Copyright (C) 2005, 2006, 2007 David Lawrence Ramsey * 7 * This program is free software; you can redistribute it and/or modify * 8 * it under the terms of the GNU General Public License as published by * 9 * the Free Software Foundation; either version 2, or (at your option) * 10 * any later version. * 11 * * 12 * This program is distributed in the hope that it will be useful, but * 13 * WITHOUT ANY WARRANTY; without even the implied warranty of * 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * 15 * 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, write to the Free Software * 19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 20 * 02110-1301, USA. * 21 * * 22 **************************************************************************/ 23 24#include "proto.h" 25 26#include <string.h> 27#include <ctype.h> 28 29/* Move to the first line of the file. */ 30void do_first_line(void) 31{ 32 const filestruct *current_save = openfile->current; 33 size_t pww_save = openfile->placewewant; 34 35 openfile->current = openfile->fileage; 36 openfile->current_x = 0; 37 openfile->placewewant = 0; 38 39 edit_redraw(current_save, pww_save); 40} 41 42/* Move to the last line of the file. */ 43void do_last_line(void) 44{ 45 const filestruct *current_save = openfile->current; 46 size_t pww_save = openfile->placewewant; 47 48 openfile->current = openfile->filebot; 49 openfile->current_x = strlen(openfile->filebot->data); 50 openfile->placewewant = xplustabs(); 51 openfile->current_y = editwinrows - 1; 52 53 edit_redraw(current_save, pww_save); 54} 55 56/* Move up one page. */ 57void do_page_up(void) 58{ 59 int i; 60 61 /* If there's less than a page of text left on the screen, put the 62 * cursor at the beginning of the first line of the file, and then 63 * update the edit window. */ 64 if (openfile->current->lineno <= editwinrows - 2) { 65 do_first_line(); 66 return; 67 } 68 69 /* If we're not in smooth scrolling mode, put the cursor at the 70 * beginning of the top line of the edit window, as Pico does. */ 71#ifndef NANO_TINY 72 if (!ISSET(SMOOTH_SCROLL)) { 73#endif 74 openfile->current = openfile->edittop; 75 openfile->placewewant = 0; 76#ifndef NANO_TINY 77 } 78#endif 79 80 for (i = editwinrows - 2; i > 0 && openfile->current != 81 openfile->fileage; i--) 82 openfile->current = openfile->current->prev; 83 84 openfile->current_x = actual_x(openfile->current->data, 85 openfile->placewewant); 86 87 /* Scroll the edit window up a page. */ 88 edit_scroll(UP_DIR, editwinrows - 2); 89} 90 91/* Move down one page. */ 92void do_page_down(void) 93{ 94 int i; 95 96 /* If there's less than a page of text left on the screen, put the 97 * cursor at the beginning of the last line of the file, and then 98 * update the edit window. */ 99 if (openfile->current->lineno + editwinrows - 2 >= 100 openfile->filebot->lineno) { 101 do_last_line(); 102 return; 103 } 104 105 /* If we're not in smooth scrolling mode, put the cursor at the 106 * beginning of the top line of the edit window, as Pico does. */ 107#ifndef NANO_TINY 108 if (!ISSET(SMOOTH_SCROLL)) { 109#endif 110 openfile->current = openfile->edittop; 111 openfile->placewewant = 0; 112#ifndef NANO_TINY 113 } 114#endif 115 116 for (i = editwinrows - 2; i > 0 && openfile->current != 117 openfile->filebot; i--) 118 openfile->current = openfile->current->next; 119 120 openfile->current_x = actual_x(openfile->current->data, 121 openfile->placewewant); 122 123 /* Scroll the edit window down a page. */ 124 edit_scroll(DOWN_DIR, editwinrows - 2); 125} 126 127#ifndef DISABLE_JUSTIFY 128/* Move up to the beginning of the last beginning-of-paragraph line 129 * before the current line. If allow_update is TRUE, update the screen 130 * afterwards. */ 131void do_para_begin(bool allow_update) 132{ 133 const filestruct *current_save = openfile->current; 134 const size_t pww_save = openfile->placewewant; 135 136 if (openfile->current != openfile->fileage) { 137 do { 138 openfile->current = openfile->current->prev; 139 openfile->current_y--; 140 } while (!begpar(openfile->current)); 141 } 142 143 openfile->current_x = 0; 144 openfile->placewewant = 0; 145 146 if (allow_update) 147 edit_redraw(current_save, pww_save); 148} 149 150/* Move up to the beginning of the last beginning-of-paragraph line 151 * before the current line, and update the screen afterwards. */ 152void do_para_begin_void(void) 153{ 154 do_para_begin(TRUE); 155} 156 157/* Move down to the beginning of the last line of the current paragraph. 158 * Then move down one line farther if there is such a line, or to the 159 * end of the current line if not. If allow_update is TRUE, update the 160 * screen afterwards. A line is the last line of a paragraph if it is 161 * in a paragraph, and the next line either is the beginning line of a 162 * paragraph or isn't in a paragraph. */ 163void do_para_end(bool allow_update) 164{ 165 const filestruct *const current_save = openfile->current; 166 const size_t pww_save = openfile->placewewant; 167 168 while (openfile->current != openfile->filebot && 169 !inpar(openfile->current)) 170 openfile->current = openfile->current->next; 171 172 while (openfile->current != openfile->filebot && 173 inpar(openfile->current->next) && 174 !begpar(openfile->current->next)) { 175 openfile->current = openfile->current->next; 176 openfile->current_y++; 177 } 178 179 if (openfile->current != openfile->filebot) { 180 openfile->current = openfile->current->next; 181 openfile->current_x = 0; 182 openfile->placewewant = 0; 183 } else { 184 openfile->current_x = strlen(openfile->current->data); 185 openfile->placewewant = xplustabs(); 186 } 187 188 if (allow_update) 189 edit_redraw(current_save, pww_save); 190} 191 192/* Move down to the beginning of the last line of the current paragraph. 193 * Then move down one line farther if there is such a line, or to the 194 * end of the current line if not, and update the screen afterwards. */ 195void do_para_end_void(void) 196{ 197 do_para_end(TRUE); 198} 199#endif /* !DISABLE_JUSTIFY */ 200 201#ifndef NANO_TINY 202/* Move to the next word in the file. If allow_punct is TRUE, treat 203 * punctuation as part of a word. If allow_update is TRUE, update the 204 * screen afterwards. Return TRUE if we started on a word, and FALSE 205 * otherwise. */ 206bool do_next_word(bool allow_punct, bool allow_update) 207{ 208 size_t pww_save = openfile->placewewant; 209 const filestruct *current_save = openfile->current; 210 char *char_mb; 211 int char_mb_len; 212 bool end_line = FALSE, started_on_word = FALSE; 213 214 assert(openfile->current != NULL && openfile->current->data != NULL); 215 216 char_mb = charalloc(mb_cur_max()); 217 218 /* Move forward until we find the character after the last letter of 219 * the current word. */ 220 while (!end_line) { 221 char_mb_len = parse_mbchar(openfile->current->data + 222 openfile->current_x, char_mb, NULL); 223 224 /* If we've found it, stop moving forward through the current 225 * line. */ 226 if (!is_word_mbchar(char_mb, allow_punct)) 227 break; 228 229 /* If we haven't found it, then we've started on a word, so set 230 * started_on_word to TRUE. */ 231 started_on_word = TRUE; 232 233 if (openfile->current->data[openfile->current_x] == '\0') 234 end_line = TRUE; 235 else 236 openfile->current_x += char_mb_len; 237 } 238 239 /* Move forward until we find the first letter of the next word. */ 240 if (openfile->current->data[openfile->current_x] == '\0') 241 end_line = TRUE; 242 else 243 openfile->current_x += char_mb_len; 244 245 for (; openfile->current != NULL; 246 openfile->current = openfile->current->next) { 247 while (!end_line) { 248 char_mb_len = parse_mbchar(openfile->current->data + 249 openfile->current_x, char_mb, NULL); 250 251 /* If we've found it, stop moving forward through the 252 * current line. */ 253 if (is_word_mbchar(char_mb, allow_punct)) 254 break; 255 256 if (openfile->current->data[openfile->current_x] == '\0') 257 end_line = TRUE; 258 else 259 openfile->current_x += char_mb_len; 260 } 261 262 /* If we've found it, stop moving forward to the beginnings of 263 * subsequent lines. */ 264 if (!end_line) 265 break; 266 267 if (openfile->current != openfile->filebot) { 268 end_line = FALSE; 269 openfile->current_x = 0; 270 } 271 } 272 273 free(char_mb); 274 275 /* If we haven't found it, move to the end of the file. */ 276 if (openfile->current == NULL) 277 openfile->current = openfile->filebot; 278 279 openfile->placewewant = xplustabs(); 280 281 /* If allow_update is TRUE, update the screen. */ 282 if (allow_update) 283 edit_redraw(current_save, pww_save); 284 285 /* Return whether we started on a word. */ 286 return started_on_word; 287} 288 289/* Move to the next word in the file, treating punctuation as part of a 290 * word if the WORD_BOUNDS flag is set, and update the screen 291 * afterwards. */ 292void do_next_word_void(void) 293{ 294 do_next_word(ISSET(WORD_BOUNDS), TRUE); 295} 296 297/* Move to the previous word in the file. If allow_punct is TRUE, treat 298 * punctuation as part of a word. If allow_update is TRUE, update the 299 * screen afterwards. Return TRUE if we started on a word, and FALSE 300 * otherwise. */ 301bool do_prev_word(bool allow_punct, bool allow_update) 302{ 303 size_t pww_save = openfile->placewewant; 304 const filestruct *current_save = openfile->current; 305 char *char_mb; 306 int char_mb_len; 307 bool begin_line = FALSE, started_on_word = FALSE; 308 309 assert(openfile->current != NULL && openfile->current->data != NULL); 310 311 char_mb = charalloc(mb_cur_max()); 312 313 /* Move backward until we find the character before the first letter 314 * of the current word. */ 315 while (!begin_line) { 316 char_mb_len = parse_mbchar(openfile->current->data + 317 openfile->current_x, char_mb, NULL); 318 319 /* If we've found it, stop moving backward through the current 320 * line. */ 321 if (!is_word_mbchar(char_mb, allow_punct)) 322 break; 323 324 /* If we haven't found it, then we've started on a word, so set 325 * started_on_word to TRUE. */ 326 started_on_word = TRUE; 327 328 if (openfile->current_x == 0) 329 begin_line = TRUE; 330 else 331 openfile->current_x = move_mbleft(openfile->current->data, 332 openfile->current_x); 333 } 334 335 /* Move backward until we find the last letter of the previous 336 * word. */ 337 if (openfile->current_x == 0) 338 begin_line = TRUE; 339 else 340 openfile->current_x = move_mbleft(openfile->current->data, 341 openfile->current_x); 342 343 for (; openfile->current != NULL; 344 openfile->current = openfile->current->prev) { 345 while (!begin_line) { 346 char_mb_len = parse_mbchar(openfile->current->data + 347 openfile->current_x, char_mb, NULL); 348 349 /* If we've found it, stop moving backward through the 350 * current line. */ 351 if (is_word_mbchar(char_mb, allow_punct)) 352 break; 353 354 if (openfile->current_x == 0) 355 begin_line = TRUE; 356 else 357 openfile->current_x = 358 move_mbleft(openfile->current->data, 359 openfile->current_x); 360 } 361 362 /* If we've found it, stop moving backward to the ends of 363 * previous lines. */ 364 if (!begin_line) 365 break; 366 367 if (openfile->current != openfile->fileage) { 368 begin_line = FALSE; 369 openfile->current_x = strlen(openfile->current->prev->data); 370 } 371 } 372 373 /* If we haven't found it, move to the beginning of the file. */ 374 if (openfile->current == NULL) 375 openfile->current = openfile->fileage; 376 /* If we've found it, move backward until we find the character 377 * before the first letter of the previous word. */ 378 else if (!begin_line) { 379 if (openfile->current_x == 0) 380 begin_line = TRUE; 381 else 382 openfile->current_x = move_mbleft(openfile->current->data, 383 openfile->current_x); 384 385 while (!begin_line) { 386 char_mb_len = parse_mbchar(openfile->current->data + 387 openfile->current_x, char_mb, NULL); 388 389 /* If we've found it, stop moving backward through the 390 * current line. */ 391 if (!is_word_mbchar(char_mb, allow_punct)) 392 break; 393 394 if (openfile->current_x == 0) 395 begin_line = TRUE; 396 else 397 openfile->current_x = 398 move_mbleft(openfile->current->data, 399 openfile->current_x); 400 } 401 402 /* If we've found it, move forward to the first letter of the 403 * previous word. */ 404 if (!begin_line) 405 openfile->current_x += char_mb_len; 406 } 407 408 free(char_mb); 409 410 openfile->placewewant = xplustabs(); 411 412 /* If allow_update is TRUE, update the screen. */ 413 if (allow_update) 414 edit_redraw(current_save, pww_save); 415 416 /* Return whether we started on a word. */ 417 return started_on_word; 418} 419 420/* Move to the previous word in the file, treating punctuation as part 421 * of a word if the WORD_BOUNDS flag is set, and update the screen 422 * afterwards. */ 423void do_prev_word_void(void) 424{ 425 do_prev_word(ISSET(WORD_BOUNDS), TRUE); 426} 427#endif /* !NANO_TINY */ 428 429/* Move to the beginning of the current line. If the SMART_HOME flag is 430 * set, move to the first non-whitespace character of the current line 431 * if we aren't already there, or to the beginning of the current line 432 * if we are. */ 433void do_home(void) 434{ 435 size_t pww_save = openfile->placewewant; 436 437#ifndef NANO_TINY 438 if (ISSET(SMART_HOME)) { 439 size_t current_x_save = openfile->current_x; 440 441 openfile->current_x = indent_length(openfile->current->data); 442 443 if (openfile->current_x == current_x_save || 444 openfile->current->data[openfile->current_x] == '\0') 445 openfile->current_x = 0; 446 447 openfile->placewewant = xplustabs(); 448 } else { 449#endif 450 openfile->current_x = 0; 451 openfile->placewewant = 0; 452#ifndef NANO_TINY 453 } 454#endif 455 456 if (need_horizontal_update(pww_save)) 457 update_line(openfile->current, openfile->current_x); 458} 459 460/* Move to the end of the current line. */ 461void do_end(void) 462{ 463 size_t pww_save = openfile->placewewant; 464 465 openfile->current_x = strlen(openfile->current->data); 466 openfile->placewewant = xplustabs(); 467 468 if (need_horizontal_update(pww_save)) 469 update_line(openfile->current, openfile->current_x); 470} 471 472/* If scroll_only is FALSE, move up one line. If scroll_only is TRUE, 473 * scroll up one line without scrolling the cursor. */ 474void do_up( 475#ifndef NANO_TINY 476 bool scroll_only 477#else 478 void 479#endif 480 ) 481{ 482 /* If we're at the top of the file, or if scroll_only is TRUE and 483 * the top of the file is onscreen, get out. */ 484 if (openfile->current == openfile->fileage 485#ifndef NANO_TINY 486 || (scroll_only && openfile->edittop == openfile->fileage) 487#endif 488 ) 489 return; 490 491 assert(openfile->current_y == openfile->current->lineno - openfile->edittop->lineno); 492 493 /* Move the current line of the edit window up. */ 494 openfile->current = openfile->current->prev; 495 openfile->current_x = actual_x(openfile->current->data, 496 openfile->placewewant); 497 498 /* If scroll_only is FALSE and if we're on the first line of the 499 * edit window, scroll the edit window up one line if we're in 500 * smooth scrolling mode, or up half a page if we're not. If 501 * scroll_only is TRUE, scroll the edit window up one line 502 * unconditionally. */ 503 if (openfile->current_y == 0 504#ifndef NANO_TINY 505 || scroll_only 506#endif 507 ) 508 edit_scroll(UP_DIR, 509#ifndef NANO_TINY 510 (ISSET(SMOOTH_SCROLL) || scroll_only) ? 1 : 511#endif 512 editwinrows / 2); 513 514 /* If we're below the first line of the edit window, update the 515 * line we were on before and the line we're on now. The former 516 * needs to be redrawn if we're not on the first page, and the 517 * latter needs to be drawn unconditionally. */ 518 if (openfile->current_y > 0) { 519 if (need_vertical_update(0)) 520 update_line(openfile->current->next, 0); 521 update_line(openfile->current, openfile->current_x); 522 } 523} 524 525/* Move up one line. */ 526void do_up_void(void) 527{ 528 do_up( 529#ifndef NANO_TINY 530 FALSE 531#endif 532 ); 533} 534 535#ifndef NANO_TINY 536/* Scroll up one line without scrolling the cursor. */ 537void do_scroll_up(void) 538{ 539 do_up(TRUE); 540} 541#endif 542 543/* If scroll_only is FALSE, move down one line. If scroll_only is TRUE, 544 * scroll down one line without scrolling the cursor. */ 545void do_down( 546#ifndef NANO_TINY 547 bool scroll_only 548#else 549 void 550#endif 551 ) 552{ 553 /* If we're at the bottom of the file, get out. */ 554 if (openfile->current == openfile->filebot) 555 return; 556 557 assert(openfile->current_y == openfile->current->lineno - openfile->edittop->lineno); 558 559 /* Move the current line of the edit window down. */ 560 openfile->current = openfile->current->next; 561 openfile->current_x = actual_x(openfile->current->data, 562 openfile->placewewant); 563 564 /* If scroll_only is FALSE and if we're on the first line of the 565 * edit window, scroll the edit window down one line if we're in 566 * smooth scrolling mode, or down half a page if we're not. If 567 * scroll_only is TRUE, scroll the edit window down one line 568 * unconditionally. */ 569 if (openfile->current_y == editwinrows - 1 570#ifndef NANO_TINY 571 || scroll_only 572#endif 573 ) 574 edit_scroll(DOWN_DIR, 575#ifndef NANO_TINY 576 (ISSET(SMOOTH_SCROLL) || scroll_only) ? 1 : 577#endif 578 editwinrows / 2); 579 580 /* If we're above the last line of the edit window, update the line 581 * we were on before and the line we're on now. The former needs to 582 * be redrawn if we're not on the first page, and the latter needs 583 * to be drawn unconditionally. */ 584 if (openfile->current_y < editwinrows - 1) { 585 if (need_vertical_update(0)) 586 update_line(openfile->current->prev, 0); 587 update_line(openfile->current, openfile->current_x); 588 } 589} 590 591/* Move down one line. */ 592void do_down_void(void) 593{ 594 do_down( 595#ifndef NANO_TINY 596 FALSE 597#endif 598 ); 599} 600 601#ifndef NANO_TINY 602/* Scroll down one line without scrolling the cursor. */ 603void do_scroll_down(void) 604{ 605 do_down(TRUE); 606} 607#endif 608 609/* Move left one character. */ 610void do_left(void) 611{ 612 size_t pww_save = openfile->placewewant; 613 614 if (openfile->current_x > 0) 615 openfile->current_x = move_mbleft(openfile->current->data, 616 openfile->current_x); 617 else if (openfile->current != openfile->fileage) { 618 do_up_void(); 619 openfile->current_x = strlen(openfile->current->data); 620 } 621 622 openfile->placewewant = xplustabs(); 623 624 if (need_horizontal_update(pww_save)) 625 update_line(openfile->current, openfile->current_x); 626} 627 628/* Move right one character. */ 629void do_right(void) 630{ 631 size_t pww_save = openfile->placewewant; 632 633 assert(openfile->current_x <= strlen(openfile->current->data)); 634 635 if (openfile->current->data[openfile->current_x] != '\0') 636 openfile->current_x = move_mbright(openfile->current->data, 637 openfile->current_x); 638 else if (openfile->current != openfile->filebot) { 639 do_down_void(); 640 openfile->current_x = 0; 641 } 642 643 openfile->placewewant = xplustabs(); 644 645 if (need_horizontal_update(pww_save)) 646 update_line(openfile->current, openfile->current_x); 647} 648