1/*	$NetBSD: v_scroll.c,v 1.2 2013/11/22 15:52:06 christos Exp $ */
2/*-
3 * Copyright (c) 1992, 1993, 1994
4 *	The Regents of the University of California.  All rights reserved.
5 * Copyright (c) 1992, 1993, 1994, 1995, 1996
6 *	Keith Bostic.  All rights reserved.
7 *
8 * See the LICENSE file for redistribution information.
9 */
10
11#include "config.h"
12
13#include <sys/cdefs.h>
14#if 0
15#ifndef lint
16static const char sccsid[] = "Id: v_scroll.c,v 10.12 2001/06/25 15:19:34 skimo Exp  (Berkeley) Date: 2001/06/25 15:19:34 ";
17#endif /* not lint */
18#else
19__RCSID("$NetBSD$");
20#endif
21
22#include <sys/types.h>
23#include <sys/queue.h>
24#include <sys/time.h>
25
26#include <bitstring.h>
27#include <errno.h>
28#include <limits.h>
29#include <stdio.h>
30
31#include "../common/common.h"
32#include "vi.h"
33
34static void goto_adjust __P((VICMD *));
35
36/*
37 * The historic vi had a problem in that all movements were by physical
38 * lines, not by logical, or screen lines.  Arguments can be made that this
39 * is the right thing to do.  For example, single line movements, such as
40 * 'j' or 'k', should probably work on physical lines.  Commands like "dj",
41 * or "j.", where '.' is a change command, make more sense for physical lines
42 * than they do for logical lines.
43 *
44 * These arguments, however, don't apply to scrolling commands like ^D and
45 * ^F -- if the window is fairly small, using physical lines can result in
46 * a half-page scroll repainting the entire screen, which is not what the
47 * user wanted.  Second, if the line is larger than the screen, using physical
48 * lines can make it impossible to display parts of the line -- there aren't
49 * any commands that don't display the beginning of the line in historic vi,
50 * and if both the beginning and end of the line can't be on the screen at
51 * the same time, you lose.  This is even worse in the case of the H, L, and
52 * M commands -- for large lines, they may all refer to the same line and
53 * will result in no movement at all.
54 *
55 * Another issue is that page and half-page scrolling commands historically
56 * moved to the first non-blank character in the new line.  If the line is
57 * approximately the same size as the screen, this loses because the cursor
58 * before and after a ^D, may refer to the same location on the screen.  In
59 * this implementation, scrolling commands set the cursor to the first non-
60 * blank character if the line changes because of the scroll.  Otherwise,
61 * the cursor is left alone.
62 *
63 * This implementation does the scrolling (^B, ^D, ^F, ^U, ^Y, ^E), and the
64 * cursor positioning commands (H, L, M) commands using logical lines, not
65 * physical.
66 */
67
68/*
69 * v_lgoto -- [count]G
70 *	Go to first non-blank character of the line count, the last line
71 *	of the file by default.
72 *
73 * PUBLIC: int v_lgoto __P((SCR *, VICMD *));
74 */
75int
76v_lgoto(SCR *sp, VICMD *vp)
77{
78	db_recno_t nlines;
79
80	if (F_ISSET(vp, VC_C1SET)) {
81		if (!db_exist(sp, vp->count)) {
82			/*
83			 * !!!
84			 * Historically, 1G was legal in an empty file.
85			 */
86			if (vp->count == 1) {
87				if (db_last(sp, &nlines))
88					return (1);
89				if (nlines == 0)
90					return (0);
91			}
92			v_eof(sp, &vp->m_start);
93			return (1);
94		}
95		vp->m_stop.lno = vp->count;
96	} else {
97		if (db_last(sp, &nlines))
98			return (1);
99		vp->m_stop.lno = nlines ? nlines : 1;
100	}
101	goto_adjust(vp);
102	return (0);
103}
104
105/*
106 * v_home -- [count]H
107 *	Move to the first non-blank character of the logical line
108 *	count - 1 from the top of the screen, 0 by default.
109 *
110 * PUBLIC: int v_home __P((SCR *, VICMD *));
111 */
112int
113v_home(SCR *sp, VICMD *vp)
114{
115	if (vs_sm_position(sp, &vp->m_stop,
116	    F_ISSET(vp, VC_C1SET) ? vp->count - 1 : 0, P_TOP))
117		return (1);
118	goto_adjust(vp);
119	return (0);
120}
121
122/*
123 * v_middle -- M
124 *	Move to the first non-blank character of the logical line
125 *	in the middle of the screen.
126 *
127 * PUBLIC: int v_middle __P((SCR *, VICMD *));
128 */
129int
130v_middle(SCR *sp, VICMD *vp)
131{
132	/*
133	 * Yielding to none in our quest for compatibility with every
134	 * historical blemish of vi, no matter how strange it might be,
135	 * we permit the user to enter a count and then ignore it.
136	 */
137	if (vs_sm_position(sp, &vp->m_stop, 0, P_MIDDLE))
138		return (1);
139	goto_adjust(vp);
140	return (0);
141}
142
143/*
144 * v_bottom -- [count]L
145 *	Move to the first non-blank character of the logical line
146 *	count - 1 from the bottom of the screen, 0 by default.
147 *
148 * PUBLIC: int v_bottom __P((SCR *, VICMD *));
149 */
150int
151v_bottom(SCR *sp, VICMD *vp)
152{
153	if (vs_sm_position(sp, &vp->m_stop,
154	    F_ISSET(vp, VC_C1SET) ? vp->count - 1 : 0, P_BOTTOM))
155		return (1);
156	goto_adjust(vp);
157	return (0);
158}
159
160static void
161goto_adjust(VICMD *vp)
162{
163	/* Guess that it's the end of the range. */
164	vp->m_final = vp->m_stop;
165
166	/*
167	 * Non-motion commands move the cursor to the end of the range, and
168	 * then to the NEXT nonblank of the line.  Historic vi always moved
169	 * to the first nonblank in the line; since the H, M, and L commands
170	 * are logical motions in this implementation, we do the next nonblank
171	 * so that it looks approximately the same to the user.  To make this
172	 * happen, the VM_RCM_SETNNB flag is set in the vcmd.c command table.
173	 *
174	 * If it's a motion, it's more complicated.  The best possible solution
175	 * is probably to display the first nonblank of the line the cursor
176	 * will eventually rest on.  This is tricky, particularly given that if
177	 * the associated command is a delete, we don't yet know what line that
178	 * will be.  So, we clear the VM_RCM_SETNNB flag, and set the first
179	 * nonblank flag (VM_RCM_SETFNB).  Note, if the lines are sufficiently
180	 * long, this can cause the cursor to warp out of the screen.  It's too
181	 * hard to fix.
182	 *
183	 * XXX
184	 * The G command is always first nonblank, so it's okay to reset it.
185	 */
186	if (ISMOTION(vp)) {
187		F_CLR(vp, VM_RCM_MASK);
188		F_SET(vp, VM_RCM_SETFNB);
189	} else
190		return;
191
192	/*
193	 * If moving backward in the file, delete and yank move to the end
194	 * of the range, unless the line didn't change, in which case yank
195	 * doesn't move.  If moving forward in the file, delete and yank
196	 * stay at the start of the range.  Ignore others.
197	 */
198	if (vp->m_stop.lno < vp->m_start.lno ||
199	    (vp->m_stop.lno == vp->m_start.lno &&
200	    vp->m_stop.cno < vp->m_start.cno)) {
201		if (ISCMD(vp->rkp, 'y') && vp->m_stop.lno == vp->m_start.lno)
202			vp->m_final = vp->m_start;
203	} else
204		vp->m_final = vp->m_start;
205}
206
207/*
208 * v_up -- [count]^P, [count]k, [count]-
209 *	Move up by lines.
210 *
211 * PUBLIC: int v_up __P((SCR *, VICMD *));
212 */
213int
214v_up(SCR *sp, VICMD *vp)
215{
216	db_recno_t lno;
217
218	lno = F_ISSET(vp, VC_C1SET) ? vp->count : 1;
219	if (vp->m_start.lno <= lno) {
220		v_sof(sp, &vp->m_start);
221		return (1);
222	}
223	vp->m_stop.lno = vp->m_start.lno - lno;
224	vp->m_final = vp->m_stop;
225	return (0);
226}
227
228/*
229 * v_cr -- [count]^M
230 *	In a script window, send the line to the shell.
231 *	In a regular window, move down by lines.
232 *
233 * PUBLIC: int v_cr __P((SCR *, VICMD *));
234 */
235int
236v_cr(SCR *sp, VICMD *vp)
237{
238	/* If it's a colon command-line edit window, it's an ex command. */
239	if (F_ISSET(sp, SC_COMEDIT))
240		return (v_ecl_exec(sp));
241
242	/* If it's a script window, exec the line. */
243	if (F_ISSET(sp, SC_SCRIPT))
244		return (sscr_exec(sp, vp->m_start.lno));
245
246	/* Otherwise, it's the same as v_down(). */
247	return (v_down(sp, vp));
248}
249
250/*
251 * v_down -- [count]^J, [count]^N, [count]j, [count]^M, [count]+
252 *	Move down by lines.
253 *
254 * PUBLIC: int v_down __P((SCR *, VICMD *));
255 */
256int
257v_down(SCR *sp, VICMD *vp)
258{
259	db_recno_t lno;
260
261	lno = vp->m_start.lno + (F_ISSET(vp, VC_C1SET) ? vp->count : 1);
262	if (!db_exist(sp, lno)) {
263		v_eof(sp, &vp->m_start);
264		return (1);
265	}
266	vp->m_stop.lno = lno;
267	vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop;
268	return (0);
269}
270
271/*
272 * v_hpageup -- [count]^U
273 *	Page up half screens.
274 *
275 * PUBLIC: int v_hpageup __P((SCR *, VICMD *));
276 */
277int
278v_hpageup(SCR *sp, VICMD *vp)
279{
280	/*
281	 * Half screens always succeed unless already at SOF.
282	 *
283	 * !!!
284	 * Half screens set the scroll value, even if the command
285	 * ultimately failed, in historic vi.  Probably a don't care.
286	 */
287	if (F_ISSET(vp, VC_C1SET))
288		sp->defscroll = vp->count;
289	if (vs_sm_scroll(sp, &vp->m_stop, sp->defscroll, CNTRL_U))
290		return (1);
291	vp->m_final = vp->m_stop;
292	return (0);
293}
294
295/*
296 * v_hpagedown -- [count]^D
297 *	Page down half screens.
298 *
299 * PUBLIC: int v_hpagedown __P((SCR *, VICMD *));
300 */
301int
302v_hpagedown(SCR *sp, VICMD *vp)
303{
304	/*
305	 * Half screens always succeed unless already at EOF.
306	 *
307	 * !!!
308	 * Half screens set the scroll value, even if the command
309	 * ultimately failed, in historic vi.  Probably a don't care.
310	 */
311	if (F_ISSET(vp, VC_C1SET))
312		sp->defscroll = vp->count;
313	if (vs_sm_scroll(sp, &vp->m_stop, sp->defscroll, CNTRL_D))
314		return (1);
315	vp->m_final = vp->m_stop;
316	return (0);
317}
318
319/*
320 * v_pagedown -- [count]^F
321 *	Page down full screens.
322 * !!!
323 * Historic vi did not move to the EOF if the screen couldn't move, i.e.
324 * if EOF was already displayed on the screen.  This implementation does
325 * move to EOF in that case, making ^F more like the the historic ^D.
326 *
327 * PUBLIC: int v_pagedown __P((SCR *, VICMD *));
328 */
329int
330v_pagedown(SCR *sp, VICMD *vp)
331{
332	db_recno_t offset;
333
334	/*
335	 * !!!
336	 * The calculation in IEEE Std 1003.2-1992 (POSIX) is:
337	 *
338	 *	top_line = top_line + count * (window - 2);
339	 *
340	 * which was historically wrong.  The correct one is:
341	 *
342	 *	top_line = top_line + count * window - 2;
343	 *
344	 * i.e. the two line "overlap" was only subtracted once.  Which
345	 * makes no sense, but then again, an overlap makes no sense for
346	 * any screen but the "next" one anyway.  We do it the historical
347	 * way as there's no good reason to change it.
348	 *
349	 * If the screen has been split horizontally, use the smaller of
350	 * the current window size and the window option value.
351	 *
352	 * It possible for this calculation to be less than 1; move at
353	 * least one line.
354	 */
355	offset = (F_ISSET(vp, VC_C1SET) ? vp->count : 1) * (IS_HSPLIT(sp) ?
356	    MIN(sp->t_maxrows, O_VAL(sp, O_WINDOW)) : O_VAL(sp, O_WINDOW));
357	offset = offset <= 2 ? 1 : offset - 2;
358	if (vs_sm_scroll(sp, &vp->m_stop, offset, CNTRL_F))
359		return (1);
360	vp->m_final = vp->m_stop;
361	return (0);
362}
363
364/*
365 * v_pageup -- [count]^B
366 *	Page up full screens.
367 *
368 * !!!
369 * Historic vi did not move to the SOF if the screen couldn't move, i.e.
370 * if SOF was already displayed on the screen.  This implementation does
371 * move to SOF in that case, making ^B more like the the historic ^U.
372 *
373 * PUBLIC: int v_pageup __P((SCR *, VICMD *));
374 */
375int
376v_pageup(SCR *sp, VICMD *vp)
377{
378	db_recno_t offset;
379
380	/*
381	 * !!!
382	 * The calculation in IEEE Std 1003.2-1992 (POSIX) is:
383	 *
384	 *	top_line = top_line - count * (window - 2);
385	 *
386	 * which was historically wrong.  The correct one is:
387	 *
388	 *	top_line = (top_line - count * window) + 2;
389	 *
390	 * A simpler expression is that, as with ^F, we scroll exactly:
391	 *
392	 *	count * window - 2
393	 *
394	 * lines.
395	 *
396	 * Bizarre.  As with ^F, an overlap makes no sense for anything
397	 * but the first screen.  We do it the historical way as there's
398	 * no good reason to change it.
399	 *
400	 * If the screen has been split horizontally, use the smaller of
401	 * the current window size and the window option value.
402	 *
403	 * It possible for this calculation to be less than 1; move at
404	 * least one line.
405	 */
406	offset = (F_ISSET(vp, VC_C1SET) ? vp->count : 1) * (IS_HSPLIT(sp) ?
407	    MIN(sp->t_maxrows, O_VAL(sp, O_WINDOW)) : O_VAL(sp, O_WINDOW));
408	offset = offset <= 2 ? 1 : offset - 2;
409	if (vs_sm_scroll(sp, &vp->m_stop, offset, CNTRL_B))
410		return (1);
411	vp->m_final = vp->m_stop;
412	return (0);
413}
414
415/*
416 * v_lineup -- [count]^Y
417 *	Page up by lines.
418 *
419 * PUBLIC: int v_lineup __P((SCR *, VICMD *));
420 */
421int
422v_lineup(SCR *sp, VICMD *vp)
423{
424	/*
425	 * The cursor moves down, staying with its original line, unless it
426	 * reaches the bottom of the screen.
427	 */
428	if (vs_sm_scroll(sp,
429	    &vp->m_stop, F_ISSET(vp, VC_C1SET) ? vp->count : 1, CNTRL_Y))
430		return (1);
431	vp->m_final = vp->m_stop;
432	return (0);
433}
434
435/*
436 * v_linedown -- [count]^E
437 *	Page down by lines.
438 *
439 * PUBLIC: int v_linedown __P((SCR *, VICMD *));
440 */
441int
442v_linedown(SCR *sp, VICMD *vp)
443{
444	/*
445	 * The cursor moves up, staying with its original line, unless it
446	 * reaches the top of the screen.
447	 */
448	if (vs_sm_scroll(sp,
449	    &vp->m_stop, F_ISSET(vp, VC_C1SET) ? vp->count : 1, CNTRL_E))
450		return (1);
451	vp->m_final = vp->m_stop;
452	return (0);
453}
454