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