| /************************************************************************** |
| * move.c -- This file is part of GNU nano. * |
| * * |
| * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, * |
| * 2008, 2009, 2010, 2011, 2013, 2014 Free Software Foundation, Inc. * |
| * Copyright (C) 2014, 2015, 2016 Benno Schulenberg * |
| * * |
| * GNU nano is free software: you can redistribute it and/or modify * |
| * it under the terms of the GNU General Public License as published * |
| * by the Free Software Foundation, either version 3 of the License, * |
| * or (at your option) any later version. * |
| * * |
| * GNU nano is distributed in the hope that it will be useful, * |
| * but WITHOUT ANY WARRANTY; without even the implied warranty * |
| * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * |
| * See the GNU General Public License for more details. * |
| * * |
| * You should have received a copy of the GNU General Public License * |
| * along with this program. If not, see http://www.gnu.org/licenses/. * |
| * * |
| **************************************************************************/ |
| |
| #include "proto.h" |
| |
| #include <string.h> |
| #include <ctype.h> |
| |
| /* Move to the first line of the file. */ |
| void do_first_line(void) |
| { |
| openfile->current = openfile->fileage; |
| openfile->current_x = 0; |
| openfile->placewewant = 0; |
| |
| refresh_needed = TRUE; |
| } |
| |
| /* Move to the last line of the file. */ |
| void do_last_line(void) |
| { |
| openfile->current = openfile->filebot; |
| openfile->current_x = strlen(openfile->filebot->data); |
| openfile->placewewant = xplustabs(); |
| |
| /* Set the last line of the screen as the target for the cursor. */ |
| openfile->current_y = editwinrows - 1; |
| ensure_line_is_visible(); |
| |
| refresh_needed = TRUE; |
| focusing = FALSE; |
| } |
| |
| /* Move up one page. */ |
| void do_page_up(void) |
| { |
| int i, mustmove, skipped = 0; |
| |
| /* If the cursor is less than a page away from the top of the file, |
| * put it at the beginning of the first line. */ |
| if (openfile->current->lineno == 1 || (!ISSET(SOFTWRAP) && |
| openfile->current->lineno <= editwinrows - 2)) { |
| do_first_line(); |
| return; |
| } |
| |
| /* If we're not in smooth scrolling mode, put the cursor at the |
| * beginning of the top line of the edit window, as Pico does. */ |
| if (!ISSET(SMOOTH_SCROLL)) { |
| openfile->current = openfile->edittop; |
| openfile->placewewant = openfile->current_y = 0; |
| } |
| |
| mustmove = (editwinrows < 3) ? 1 : editwinrows - 2; |
| |
| for (i = mustmove; i - skipped > 0 && openfile->current != openfile->fileage; i--) { |
| openfile->current = openfile->current->prev; |
| #ifndef NANO_TINY |
| if (ISSET(SOFTWRAP) && openfile->current) { |
| skipped += strlenpt(openfile->current->data) / editwincols; |
| #ifdef DEBUG |
| fprintf(stderr, "paging up: i = %d, skipped = %d based on line %ld len %lu\n", |
| i, skipped, (long)openfile->current->lineno, (unsigned long)strlenpt(openfile->current->data)); |
| #endif |
| } |
| #endif |
| } |
| |
| openfile->current_x = actual_x(openfile->current->data, |
| openfile->placewewant); |
| |
| /* Scroll the edit window up a page. */ |
| adjust_viewport(STATIONARY); |
| refresh_needed = TRUE; |
| } |
| |
| /* Move down one page. */ |
| void do_page_down(void) |
| { |
| int i, mustmove; |
| |
| /* If the cursor is less than a page away from the bottom of the file, |
| * put it at the end of the last line. */ |
| if (openfile->current->lineno + maxlines - 2 >= openfile->filebot->lineno) { |
| do_last_line(); |
| return; |
| } |
| |
| /* If we're not in smooth scrolling mode, put the cursor at the |
| * beginning of the top line of the edit window, as Pico does. */ |
| if (!ISSET(SMOOTH_SCROLL)) { |
| openfile->current = openfile->edittop; |
| openfile->placewewant = openfile->current_y = 0; |
| } |
| |
| mustmove = (maxlines < 3) ? 1 : maxlines - 2; |
| |
| for (i = mustmove; i > 0 && openfile->current != openfile->filebot; i--) { |
| openfile->current = openfile->current->next; |
| #ifdef DEBUG |
| fprintf(stderr, "paging down: moving to line %lu\n", (unsigned long)openfile->current->lineno); |
| #endif |
| |
| } |
| |
| openfile->current_x = actual_x(openfile->current->data, |
| openfile->placewewant); |
| |
| /* Scroll the edit window down a page. */ |
| adjust_viewport(STATIONARY); |
| refresh_needed = TRUE; |
| } |
| |
| #ifndef DISABLE_JUSTIFY |
| /* Move up to the beginning of the last beginning-of-paragraph line |
| * before the current line. If allow_update is TRUE, update the screen |
| * afterwards. */ |
| void do_para_begin(bool allow_update) |
| { |
| filestruct *was_current = openfile->current; |
| |
| if (openfile->current != openfile->fileage) { |
| do |
| openfile->current = openfile->current->prev; |
| while (!begpar(openfile->current)); |
| } |
| |
| openfile->current_x = 0; |
| |
| if (allow_update) |
| edit_redraw(was_current); |
| } |
| |
| /* Move up to the beginning of the last beginning-of-paragraph line |
| * before the current line, and update the screen afterwards. */ |
| void do_para_begin_void(void) |
| { |
| do_para_begin(TRUE); |
| } |
| |
| /* Move down to the beginning of the last line of the current paragraph. |
| * Then move down one line farther if there is such a line, or to the |
| * end of the current line if not. If allow_update is TRUE, update the |
| * screen afterwards. A line is the last line of a paragraph if it is |
| * in a paragraph, and the next line either is the beginning line of a |
| * paragraph or isn't in a paragraph. */ |
| void do_para_end(bool allow_update) |
| { |
| filestruct *was_current = openfile->current; |
| |
| while (openfile->current != openfile->filebot && |
| !inpar(openfile->current)) |
| openfile->current = openfile->current->next; |
| |
| while (openfile->current != openfile->filebot && |
| inpar(openfile->current->next) && |
| !begpar(openfile->current->next)) { |
| openfile->current = openfile->current->next; |
| } |
| |
| if (openfile->current != openfile->filebot) { |
| openfile->current = openfile->current->next; |
| openfile->current_x = 0; |
| } else |
| openfile->current_x = strlen(openfile->current->data); |
| |
| if (allow_update) |
| edit_redraw(was_current); |
| } |
| |
| /* Move down to the beginning of the last line of the current paragraph. |
| * Then move down one line farther if there is such a line, or to the |
| * end of the current line if not, and update the screen afterwards. */ |
| void do_para_end_void(void) |
| { |
| do_para_end(TRUE); |
| } |
| #endif /* !DISABLE_JUSTIFY */ |
| |
| /* Move to the preceding block of text in the file. */ |
| void do_prev_block(void) |
| { |
| filestruct *was_current = openfile->current; |
| bool is_text = FALSE, seen_text = FALSE; |
| |
| /* Skip backward until first blank line after some nonblank line(s). */ |
| while (openfile->current->prev != NULL && (!seen_text || is_text)) { |
| openfile->current = openfile->current->prev; |
| is_text = !white_string(openfile->current->data); |
| seen_text = seen_text || is_text; |
| } |
| |
| /* Step forward one line again if this one is blank. */ |
| if (openfile->current->next != NULL && |
| white_string(openfile->current->data)) |
| openfile->current = openfile->current->next; |
| |
| openfile->current_x = 0; |
| edit_redraw(was_current); |
| } |
| |
| /* Move to the next block of text in the file. */ |
| void do_next_block(void) |
| { |
| filestruct *was_current = openfile->current; |
| bool is_white = white_string(openfile->current->data); |
| bool seen_white = is_white; |
| |
| /* Skip forward until first nonblank line after some blank line(s). */ |
| while (openfile->current->next != NULL && (!seen_white || is_white)) { |
| openfile->current = openfile->current->next; |
| is_white = white_string(openfile->current->data); |
| seen_white = seen_white || is_white; |
| } |
| |
| openfile->current_x = 0; |
| edit_redraw(was_current); |
| } |
| |
| /* Move to the previous word in the file. If allow_punct is TRUE, treat |
| * punctuation as part of a word. If allow_update is TRUE, update the |
| * screen afterwards. */ |
| void do_prev_word(bool allow_punct, bool allow_update) |
| { |
| filestruct *was_current = openfile->current; |
| bool seen_a_word = FALSE, step_forward = FALSE; |
| |
| assert(openfile->current != NULL && openfile->current->data != NULL); |
| |
| /* Move backward until we pass over the start of a word. */ |
| while (TRUE) { |
| /* If at the head of a line, move to the end of the preceding one. */ |
| if (openfile->current_x == 0) { |
| if (openfile->current->prev == NULL) |
| break; |
| openfile->current = openfile->current->prev; |
| openfile->current_x = strlen(openfile->current->data); |
| } |
| |
| /* Step back one character. */ |
| openfile->current_x = move_mbleft(openfile->current->data, |
| openfile->current_x); |
| |
| if (is_word_mbchar(openfile->current->data + openfile->current_x, |
| allow_punct)) { |
| seen_a_word = TRUE; |
| /* If at the head of a line now, this surely is a word start. */ |
| if (openfile->current_x == 0) |
| break; |
| } else if (seen_a_word) { |
| /* This is space now: we've overshot the start of the word. */ |
| step_forward = TRUE; |
| break; |
| } |
| } |
| |
| if (step_forward) |
| /* Move one character forward again to sit on the start of the word. */ |
| openfile->current_x = move_mbright(openfile->current->data, |
| openfile->current_x); |
| |
| /* If allow_update is TRUE, update the screen. */ |
| if (allow_update) { |
| focusing = FALSE; |
| edit_redraw(was_current); |
| } |
| } |
| |
| /* Move to the previous word in the file, treating punctuation as part of a |
| * word if the WORD_BOUNDS flag is set, and update the screen afterwards. */ |
| void do_prev_word_void(void) |
| { |
| do_prev_word(ISSET(WORD_BOUNDS), TRUE); |
| } |
| |
| /* Move to the next word in the file. If allow_punct is TRUE, treat |
| * punctuation as part of a word. If allow_update is TRUE, update the |
| * screen afterwards. Return TRUE if we started on a word, and FALSE |
| * otherwise. */ |
| bool do_next_word(bool allow_punct, bool allow_update) |
| { |
| filestruct *was_current = openfile->current; |
| bool started_on_word = is_word_mbchar(openfile->current->data + |
| openfile->current_x, allow_punct); |
| bool seen_space = !started_on_word; |
| |
| assert(openfile->current != NULL && openfile->current->data != NULL); |
| |
| /* Move forward until we reach the start of a word. */ |
| while (TRUE) { |
| /* If at the end of a line, move to the beginning of the next one. */ |
| if (openfile->current->data[openfile->current_x] == '\0') { |
| /* If at the end of the file, stop. */ |
| if (openfile->current->next == NULL) |
| break; |
| openfile->current = openfile->current->next; |
| openfile->current_x = 0; |
| seen_space = TRUE; |
| } else { |
| /* Step forward one character. */ |
| openfile->current_x = move_mbright(openfile->current->data, |
| openfile->current_x); |
| } |
| |
| /* If this is not a word character, then it's a separator; else |
| * if we've already seen a separator, then it's a word start. */ |
| if (!is_word_mbchar(openfile->current->data + openfile->current_x, |
| allow_punct)) |
| seen_space = TRUE; |
| else if (seen_space) |
| break; |
| } |
| |
| /* If allow_update is TRUE, update the screen. */ |
| if (allow_update) { |
| focusing = FALSE; |
| edit_redraw(was_current); |
| } |
| |
| /* Return whether we started on a word. */ |
| return started_on_word; |
| } |
| |
| /* Move to the next word in the file, treating punctuation as part of a word |
| * if the WORD_BOUNDS flag is set, and update the screen afterwards. */ |
| void do_next_word_void(void) |
| { |
| do_next_word(ISSET(WORD_BOUNDS), TRUE); |
| } |
| |
| /* Make sure that the current line, when it is partially scrolled off the |
| * screen in softwrap mode, is scrolled fully into view. */ |
| void ensure_line_is_visible(void) |
| { |
| #ifndef NANO_TINY |
| if (ISSET(SOFTWRAP) && strlenpt(openfile->current->data) / editwincols + |
| openfile->current_y >= editwinrows) { |
| adjust_viewport(ISSET(SMOOTH_SCROLL) ? FLOWING : CENTERING); |
| refresh_needed = TRUE; |
| } |
| #endif |
| } |
| |
| /* Move to the beginning of the current line. If the SMART_HOME flag is |
| * set, move to the first non-whitespace character of the current line |
| * if we aren't already there, or to the beginning of the current line |
| * if we are. */ |
| void do_home(void) |
| { |
| size_t was_column = xplustabs(); |
| |
| #ifndef NANO_TINY |
| if (ISSET(SMART_HOME)) { |
| size_t current_x_save = openfile->current_x; |
| |
| openfile->current_x = indent_length(openfile->current->data); |
| |
| if (openfile->current_x == current_x_save || |
| openfile->current->data[openfile->current_x] == '\0') |
| openfile->current_x = 0; |
| } else |
| #endif |
| openfile->current_x = 0; |
| |
| openfile->placewewant = xplustabs(); |
| |
| if (need_horizontal_scroll(was_column, openfile->placewewant)) |
| update_line(openfile->current, openfile->current_x); |
| } |
| |
| /* Move to the end of the current line. */ |
| void do_end(void) |
| { |
| size_t was_column = xplustabs(); |
| |
| openfile->current_x = strlen(openfile->current->data); |
| openfile->placewewant = xplustabs(); |
| |
| if (need_horizontal_scroll(was_column, openfile->placewewant)) |
| update_line(openfile->current, openfile->current_x); |
| |
| ensure_line_is_visible(); |
| } |
| |
| /* If scroll_only is FALSE, move up one line. If scroll_only is TRUE, |
| * scroll up one line without scrolling the cursor. */ |
| void do_up(bool scroll_only) |
| { |
| size_t was_column = xplustabs(); |
| |
| /* If we're at the top of the file, or if scroll_only is TRUE and |
| * the top of the file is onscreen, get out. */ |
| if (openfile->current == openfile->fileage || |
| (scroll_only && openfile->edittop == openfile->fileage)) |
| return; |
| |
| assert(ISSET(SOFTWRAP) || openfile->current_y == openfile->current->lineno - openfile->edittop->lineno); |
| |
| /* Move the current line of the edit window up. */ |
| openfile->current = openfile->current->prev; |
| openfile->current_x = actual_x(openfile->current->data, |
| openfile->placewewant); |
| |
| /* When the cursor was on the first line of the edit window (or when just |
| * scrolling without moving the cursor), scroll the edit window up -- one |
| * line if we're in smooth scrolling mode, and half a page otherwise. */ |
| if (openfile->current->next == openfile->edittop || scroll_only) |
| edit_scroll(UPWARD, (ISSET(SMOOTH_SCROLL) || scroll_only) ? |
| 1 : editwinrows / 2 + 1); |
| |
| /* If the lines weren't already redrawn, see if they need to be. */ |
| if (openfile->current_y > 0) { |
| /* Redraw the prior line if it was horizontally scrolled. */ |
| if (need_horizontal_scroll(was_column, 0)) |
| update_line(openfile->current->next, 0); |
| /* Redraw the current line if it needs to be horizontally scrolled. */ |
| if (need_horizontal_scroll(0, xplustabs())) |
| update_line(openfile->current, openfile->current_x); |
| } |
| } |
| |
| /* Move up one line. */ |
| void do_up_void(void) |
| { |
| do_up(FALSE); |
| } |
| |
| /* If scroll_only is FALSE, move down one line. If scroll_only is TRUE, |
| * scroll down one line without scrolling the cursor. */ |
| void do_down(bool scroll_only) |
| { |
| #ifndef NANO_TINY |
| int amount = 0, enough; |
| filestruct *topline; |
| #endif |
| size_t was_column = xplustabs(); |
| |
| /* If we're at the bottom of the file, get out. */ |
| if (openfile->current == openfile->filebot) |
| return; |
| |
| assert(ISSET(SOFTWRAP) || openfile->current_y == |
| openfile->current->lineno - openfile->edittop->lineno); |
| assert(openfile->current->next != NULL); |
| |
| #ifndef NANO_TINY |
| if (ISSET(SOFTWRAP)) { |
| /* Compute the number of lines to scroll. */ |
| amount = strlenpt(openfile->current->data) / editwincols - |
| xplustabs() / editwincols + |
| strlenpt(openfile->current->next->data) / editwincols + |
| openfile->current_y - editwinrows + 2; |
| topline = openfile->edittop; |
| /* Reduce the amount when there are overlong lines at the top. */ |
| for (enough = 1; enough < amount; enough++) { |
| amount -= strlenpt(topline->data) / editwincols; |
| if (amount > 0) |
| topline = topline->next; |
| if (amount < enough) { |
| amount = enough; |
| break; |
| } |
| } |
| } |
| #endif |
| |
| /* Move to the next line in the file. */ |
| openfile->current = openfile->current->next; |
| openfile->current_x = actual_x(openfile->current->data, |
| openfile->placewewant); |
| |
| /* When the cursor was on the last line of the edit window (or when just |
| * scrolling without moving the cursor), scroll the edit window down -- one |
| * line if we're in smooth scrolling mode, and half a page otherwise. */ |
| #ifndef NANO_TINY |
| if (openfile->current_y == editwinrows - 1 || amount > 0 || scroll_only) { |
| if (amount < 1 || scroll_only) |
| amount = 1; |
| |
| edit_scroll(DOWNWARD, (ISSET(SMOOTH_SCROLL) || scroll_only) ? |
| amount : editwinrows / 2 + 1); |
| |
| if (ISSET(SOFTWRAP)) { |
| refresh_needed = TRUE; |
| return; |
| } |
| } |
| #else |
| if (openfile->current_y == editwinrows - 1) |
| edit_scroll(DOWNWARD, editwinrows / 2 + 1); |
| #endif |
| |
| /* If the lines weren't already redrawn, see if they need to be. */ |
| if (openfile->current_y < editwinrows - 1) { |
| /* Redraw the prior line if it was horizontally scrolled. */ |
| if (need_horizontal_scroll(was_column, 0)) |
| update_line(openfile->current->prev, 0); |
| /* Redraw the current line if it needs to be horizontally scrolled. */ |
| if (need_horizontal_scroll(0, xplustabs())) |
| update_line(openfile->current, openfile->current_x); |
| } |
| } |
| |
| /* Move down one line. */ |
| void do_down_void(void) |
| { |
| do_down(FALSE); |
| } |
| |
| #ifndef NANO_TINY |
| /* Scroll up one line without scrolling the cursor. */ |
| void do_scroll_up(void) |
| { |
| do_up(TRUE); |
| } |
| |
| /* Scroll down one line without scrolling the cursor. */ |
| void do_scroll_down(void) |
| { |
| do_down(TRUE); |
| } |
| #endif |
| |
| /* Move left one character. */ |
| void do_left(void) |
| { |
| size_t was_column = xplustabs(); |
| |
| if (openfile->current_x > 0) |
| openfile->current_x = move_mbleft(openfile->current->data, |
| openfile->current_x); |
| else if (openfile->current != openfile->fileage) { |
| do_up_void(); |
| openfile->current_x = strlen(openfile->current->data); |
| } |
| |
| openfile->placewewant = xplustabs(); |
| |
| if (need_horizontal_scroll(was_column, openfile->placewewant)) |
| update_line(openfile->current, openfile->current_x); |
| } |
| |
| /* Move right one character. */ |
| void do_right(void) |
| { |
| size_t was_column = xplustabs(); |
| |
| assert(openfile->current_x <= strlen(openfile->current->data)); |
| |
| if (openfile->current->data[openfile->current_x] != '\0') |
| openfile->current_x = move_mbright(openfile->current->data, |
| openfile->current_x); |
| else if (openfile->current != openfile->filebot) { |
| openfile->current_x = 0; |
| #ifndef NANO_TINY |
| if (ISSET(SOFTWRAP)) |
| openfile->current_y -= strlenpt(openfile->current->data) / editwincols; |
| #endif |
| } |
| |
| openfile->placewewant = xplustabs(); |
| |
| if (need_horizontal_scroll(was_column, openfile->placewewant)) |
| update_line(openfile->current, openfile->current_x); |
| |
| if (openfile->current_x == 0) |
| do_down_void(); |
| else |
| ensure_line_is_visible(); |
| } |