xref: /openbsd/usr.bin/mg/basic.c (revision 09467b48)
1 /*	$OpenBSD: basic.c,v 1.49 2019/06/17 11:39:26 lum Exp $	*/
2 
3 /* This file is in the public domain */
4 
5 /*
6  *		Basic cursor motion commands.
7  *
8  * The routines in this file are the basic
9  * command functions for moving the cursor around on
10  * the screen, setting mark, and swapping dot with
11  * mark. Only moves between lines, which might make the
12  * current buffer framing bad, are hard.
13  */
14 
15 #include <sys/queue.h>
16 #include <ctype.h>
17 #include <limits.h>
18 #include <signal.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 
22 #include "def.h"
23 
24 #define percint(n1, n2)		((n1 * (int) n2) * 0.1)
25 
26 /*
27  * Go to beginning of line.
28  */
29 /* ARGSUSED */
30 int
31 gotobol(int f, int n)
32 {
33 	if (n == 0)
34 		return (TRUE);
35 
36 	curwp->w_doto = 0;
37 	return (TRUE);
38 }
39 
40 /*
41  * Move cursor backwards. Do the
42  * right thing if the count is less than
43  * 0. Error if you try to move back from
44  * the beginning of the buffer.
45  */
46 /* ARGSUSED */
47 int
48 backchar(int f, int n)
49 {
50 	struct line   *lp;
51 
52 	if (n < 0)
53 		return (forwchar(f, -n));
54 	while (n--) {
55 		if (curwp->w_doto == 0) {
56 			if ((lp = lback(curwp->w_dotp)) == curbp->b_headp) {
57 				if (!(f & FFRAND)) {
58 					dobeep();
59 					ewprintf("Beginning of buffer");
60 				}
61 				return (FALSE);
62 			}
63 			curwp->w_dotp = lp;
64 			curwp->w_doto = llength(lp);
65 			curwp->w_rflag |= WFMOVE;
66 			curwp->w_dotline--;
67 		} else
68 			curwp->w_doto--;
69 	}
70 	return (TRUE);
71 }
72 
73 /*
74  * Go to end of line.
75  */
76 /* ARGSUSED */
77 int
78 gotoeol(int f, int n)
79 {
80 	if (n == 0)
81 		return (TRUE);
82 
83 	curwp->w_doto = llength(curwp->w_dotp);
84 	return (TRUE);
85 }
86 
87 /*
88  * Move cursor forwards. Do the
89  * right thing if the count is less than
90  * 0. Error if you try to move forward
91  * from the end of the buffer.
92  */
93 /* ARGSUSED */
94 int
95 forwchar(int f, int n)
96 {
97 	if (n < 0)
98 		return (backchar(f, -n));
99 	while (n--) {
100 		if (curwp->w_doto == llength(curwp->w_dotp)) {
101 			curwp->w_dotp = lforw(curwp->w_dotp);
102 			if (curwp->w_dotp == curbp->b_headp) {
103 				curwp->w_dotp = lback(curwp->w_dotp);
104 				if (!(f & FFRAND)) {
105 					dobeep();
106 					ewprintf("End of buffer");
107 				}
108 				return (FALSE);
109 			}
110 			curwp->w_doto = 0;
111 			curwp->w_dotline++;
112 			curwp->w_rflag |= WFMOVE;
113 		} else
114 			curwp->w_doto++;
115 	}
116 	return (TRUE);
117 }
118 
119 /*
120  * Go to the beginning of the buffer. Setting WFFULL is conservative,
121  * but almost always the case. A universal argument of higher than 9
122  * puts the cursor back to the end of buffer.
123  */
124 int
125 gotobob(int f, int n)
126 {
127 	if (!curwp->w_markp)
128 		(void) setmark(f, n);
129 	curwp->w_dotp = bfirstlp(curbp);
130 	curwp->w_doto = 0;
131 	curwp->w_rflag |= WFFULL;
132 	curwp->w_dotline = 1;
133 	if (f & FFOTHARG && n > 0) {
134 		if (n > 9)
135 			gotoeob(0, 0);
136 		else
137 			forwline(f, percint(curwp->w_bufp->b_lines, n) - 1);
138 	}
139 	return (TRUE);
140 }
141 
142 /*
143  * Go to the end of the buffer. Leave dot 3 lines from the bottom of the
144  * window if buffer length is longer than window length; same as emacs.
145  * Setting WFFULL is conservative, but almost always the case. A universal
146  * argument of higher than 9 puts the cursor back to the start of buffer.
147  */
148 int
149 gotoeob(int f, int n)
150 {
151 	int		 ln;
152 	struct line	*lp;
153 
154 	if (!curwp->w_markp)
155 		(void) setmark(f, n);
156 	curwp->w_dotp = blastlp(curbp);
157 	curwp->w_doto = llength(curwp->w_dotp);
158 	curwp->w_dotline = curwp->w_bufp->b_lines;
159 
160 	lp = curwp->w_dotp;
161 	ln = curwp->w_ntrows - 3;
162 
163 	if (ln < curwp->w_bufp->b_lines && ln >= 3) {
164 		while (ln--)
165 			curwp->w_dotp = lback(curwp->w_dotp);
166 
167 		curwp->w_linep = curwp->w_dotp;
168 		curwp->w_dotp = lp;
169 	}
170 	if (f & FFOTHARG && n > 0) {
171 		if (n > 9)
172 			gotobob(0, 0);
173 		else
174 			backline(f, percint(curwp->w_bufp->b_lines, n));
175 	}
176 
177 	curwp->w_rflag |= WFFULL;
178 	return (TRUE);
179 }
180 
181 /*
182  * Move forward by full lines.
183  * If the number of lines to move is less
184  * than zero, call the backward line function to
185  * actually do it. The last command controls how
186  * the goal column is set.
187  */
188 /* ARGSUSED */
189 int
190 forwline(int f, int n)
191 {
192 	struct line  *dlp;
193 
194 	if (n < 0)
195 		return (backline(f | FFRAND, -n));
196 	if ((dlp = curwp->w_dotp) == curbp->b_headp) {
197 		if (!(f & FFRAND)) {
198 			dobeep();
199 			ewprintf("End of buffer");
200 		}
201 		return(TRUE);
202 	}
203 	if ((lastflag & CFCPCN) == 0)	/* Fix goal. */
204 		setgoal();
205 	thisflag |= CFCPCN;
206 	if (n == 0)
207 		return (TRUE);
208 	while (n--) {
209 		dlp = lforw(dlp);
210 		if (dlp == curbp->b_headp) {
211 			curwp->w_dotp = lback(dlp);
212 			curwp->w_doto = llength(curwp->w_dotp);
213 			curwp->w_rflag |= WFMOVE;
214 			if (!(f & FFRAND)) {
215 				dobeep();
216 				ewprintf("End of buffer");
217 			}
218 			return (TRUE);
219 		}
220 		curwp->w_dotline++;
221 	}
222 	curwp->w_rflag |= WFMOVE;
223 	curwp->w_dotp = dlp;
224 	curwp->w_doto = getgoal(dlp);
225 
226 	return (TRUE);
227 }
228 
229 /*
230  * This function is like "forwline", but
231  * goes backwards. The scheme is exactly the same.
232  * Check for arguments that are less than zero and
233  * call your alternate. Figure out the new line and
234  * call "movedot" to perform the motion.
235  */
236 /* ARGSUSED */
237 int
238 backline(int f, int n)
239 {
240 	struct line   *dlp;
241 
242 	if (n < 0)
243 		return (forwline(f | FFRAND, -n));
244 	if ((lastflag & CFCPCN) == 0)	/* Fix goal. */
245 		setgoal();
246 	thisflag |= CFCPCN;
247 	dlp = curwp->w_dotp;
248 	if (lback(dlp) == curbp->b_headp)  {
249 		if (!(f & FFRAND)) {
250 			dobeep();
251 			ewprintf("Beginning of buffer");
252 		}
253 		return(TRUE);
254 	}
255 	while (n-- && lback(dlp) != curbp->b_headp) {
256 		dlp = lback(dlp);
257 		curwp->w_dotline--;
258 	}
259 	if (n > 0 && !(f & FFRAND)) {
260 		dobeep();
261 		ewprintf("Beginning of buffer");
262 	}
263 	curwp->w_dotp = dlp;
264 	curwp->w_doto = getgoal(dlp);
265 	curwp->w_rflag |= WFMOVE;
266 	return (TRUE);
267 }
268 
269 /*
270  * Set the current goal column, which is saved in the external variable
271  * "curgoal", to the current cursor column. The column is never off
272  * the edge of the screen; it's more like display then show position.
273  */
274 void
275 setgoal(void)
276 {
277 	curgoal = getcolpos(curwp);	/* Get the position. */
278 	/* we can now display past end of display, don't chop! */
279 }
280 
281 /*
282  * This routine looks at a line (pointed
283  * to by the LINE pointer "dlp") and the current
284  * vertical motion goal column (set by the "setgoal"
285  * routine above) and returns the best offset to use
286  * when a vertical motion is made into the line.
287  */
288 int
289 getgoal(struct line *dlp)
290 {
291 	int c, i, col = 0;
292 	char tmp[5];
293 
294 
295 	for (i = 0; i < llength(dlp); i++) {
296 		c = lgetc(dlp, i);
297 		if (c == '\t'
298 #ifdef	NOTAB
299 		    && !(curbp->b_flag & BFNOTAB)
300 #endif
301 			) {
302 			col |= 0x07;
303 			col++;
304 		} else if (ISCTRL(c) != FALSE) {
305 			col += 2;
306 		} else if (isprint(c))
307 			col++;
308 		else {
309 			col += snprintf(tmp, sizeof(tmp), "\\%o", c);
310 		}
311 		if (col > curgoal)
312 			break;
313 	}
314 	return (i);
315 }
316 
317 /*
318  * Scroll forward by a specified number
319  * of lines, or by a full page if no argument.
320  * The "2" is the window overlap (this is the default
321  * value from ITS EMACS). Because the top line in
322  * the window is zapped, we have to do a hard
323  * update and get it back.
324  */
325 /* ARGSUSED */
326 int
327 forwpage(int f, int n)
328 {
329 	struct line  *lp;
330 
331 	if (!(f & FFARG)) {
332 		n = curwp->w_ntrows - 2;	/* Default scroll.	 */
333 		if (n <= 0)			/* Forget the overlap	 */
334 			n = 1;			/* if tiny window.	 */
335 	} else if (n < 0)
336 		return (backpage(f | FFRAND, -n));
337 
338 	lp = curwp->w_linep;
339 	while (n--)
340 		if ((lp = lforw(lp)) == curbp->b_headp) {
341 			dobeep();
342 			ewprintf("End of buffer");
343 			return(TRUE);
344 		}
345 
346 	curwp->w_linep = lp;
347 	curwp->w_rflag |= WFFULL;
348 
349 	/* if in current window, don't move dot */
350 	for (n = curwp->w_ntrows; n-- && lp != curbp->b_headp; lp = lforw(lp))
351 		if (lp == curwp->w_dotp)
352 			return (TRUE);
353 
354 	/* Advance the dot the slow way, for line nos */
355 	while (curwp->w_dotp != curwp->w_linep) {
356 		curwp->w_dotp = lforw(curwp->w_dotp);
357 		curwp->w_dotline++;
358 	}
359 	curwp->w_doto = 0;
360 	return (TRUE);
361 }
362 
363 /*
364  * This command is like "forwpage",
365  * but it goes backwards. The "2", like above,
366  * is the overlap between the two windows. The
367  * value is from the ITS EMACS manual. The
368  * hard update is done because the top line in
369  * the window is zapped.
370  */
371 /* ARGSUSED */
372 int
373 backpage(int f, int n)
374 {
375 	struct line  *lp, *lp2;
376 
377 	if (!(f & FFARG)) {
378 		n = curwp->w_ntrows - 2;	/* Default scroll.	 */
379 		if (n <= 0)			/* Don't blow up if the  */
380 			return (backline(f, 1));/* window is tiny.	 */
381 	} else if (n < 0)
382 		return (forwpage(f | FFRAND, -n));
383 
384 	lp = lp2 = curwp->w_linep;
385 
386 	while (n-- && lback(lp) != curbp->b_headp) {
387 		lp = lback(lp);
388 	}
389 	if (lp == curwp->w_linep) {
390 		dobeep();
391 		ewprintf("Beginning of buffer");
392 	}
393 	curwp->w_linep = lp;
394 	curwp->w_rflag |= WFFULL;
395 
396 	/* if in current window, don't move dot */
397 	for (n = curwp->w_ntrows; n-- && lp != curbp->b_headp; lp = lforw(lp))
398 		if (lp == curwp->w_dotp)
399 			return (TRUE);
400 
401         lp2 = lforw(lp2);
402 
403 	/* Move the dot the slow way, for line nos */
404 	while (curwp->w_dotp != lp2) {
405                 if (curwp->w_dotline <= curwp->w_ntrows)
406 			goto out;
407 		curwp->w_dotp = lback(curwp->w_dotp);
408 		curwp->w_dotline--;
409 	}
410 out:
411 	curwp->w_doto = 0;
412 	return (TRUE);
413 }
414 
415 /*
416  * These functions are provided for compatibility with Gosling's Emacs. They
417  * are used to scroll the display up (or down) one line at a time.
418  */
419 int
420 forw1page(int f, int n)
421 {
422 	if (!(f & FFARG)) {
423 		n = 1;
424 		f = FFUNIV;
425 	}
426 	forwpage(f | FFRAND, n);
427 	return (TRUE);
428 }
429 
430 int
431 back1page(int f, int n)
432 {
433 	if (!(f & FFARG)) {
434 		n = 1;
435 		f = FFUNIV;
436 	}
437 	backpage(f | FFRAND, n);
438 	return (TRUE);
439 }
440 
441 /*
442  * Page the other window. Check to make sure it exists, then
443  * nextwind, forwpage and restore window pointers.
444  */
445 int
446 pagenext(int f, int n)
447 {
448 	struct mgwin *wp;
449 
450 	if (wheadp->w_wndp == NULL) {
451 		dobeep();
452 		ewprintf("No other window");
453 		return (FALSE);
454 	}
455 	wp = curwp;
456 	(void) nextwind(f, n);
457 	(void) forwpage(f, n);
458 	curwp = wp;
459 	curbp = wp->w_bufp;
460 	return (TRUE);
461 }
462 
463 /*
464  * Internal set mark routine, used by other functions (daveb).
465  */
466 void
467 isetmark(void)
468 {
469 	curwp->w_markp = curwp->w_dotp;
470 	curwp->w_marko = curwp->w_doto;
471 	curwp->w_markline = curwp->w_dotline;
472 }
473 
474 /*
475  * Set the mark in the current window
476  * to the value of dot. A message is written to
477  * the echo line.  (ewprintf knows about macros)
478  */
479 /* ARGSUSED */
480 int
481 setmark(int f, int n)
482 {
483 	isetmark();
484 	ewprintf("Mark set");
485 	return (TRUE);
486 }
487 
488 /* Clear the mark, if set. */
489 /* ARGSUSED */
490 int
491 clearmark(int f, int n)
492 {
493 	if (!curwp->w_markp)
494 		return (FALSE);
495 
496 	curwp->w_markp = NULL;
497 	curwp->w_marko = 0;
498 	curwp->w_markline = 0;
499 
500 	return (TRUE);
501 }
502 
503 /*
504  * Swap the values of "dot" and "mark" in
505  * the current window. This is pretty easy, because
506  * all of the hard work gets done by the standard routine
507  * that moves the mark about. The only possible
508  * error is "no mark".
509  */
510 /* ARGSUSED */
511 int
512 swapmark(int f, int n)
513 {
514 	struct line  *odotp;
515 	int odoto, odotline;
516 
517 	if (curwp->w_markp == NULL) {
518 		dobeep();
519 		ewprintf("No mark in this window");
520 		return (FALSE);
521 	}
522 	odotp = curwp->w_dotp;
523 	odoto = curwp->w_doto;
524 	odotline = curwp->w_dotline;
525 	curwp->w_dotp = curwp->w_markp;
526 	curwp->w_doto = curwp->w_marko;
527 	curwp->w_dotline = curwp->w_markline;
528 	curwp->w_markp = odotp;
529 	curwp->w_marko = odoto;
530 	curwp->w_markline = odotline;
531 	curwp->w_rflag |= WFMOVE;
532 	return (TRUE);
533 }
534 
535 /*
536  * Go to a specific line, mostly for
537  * looking up errors in C programs, which give the
538  * error a line number. If an argument is present, then
539  * it is the line number, else prompt for a line number
540  * to use.
541  */
542 /* ARGSUSED */
543 int
544 gotoline(int f, int n)
545 {
546 	char   buf[32], *bufp;
547 	const char *err;
548 
549 	if (!(f & FFARG)) {
550 		if ((bufp = eread("Goto line: ", buf, sizeof(buf),
551 		    EFNUL | EFNEW | EFCR)) == NULL)
552 			return (ABORT);
553 		if (bufp[0] == '\0')
554 			return (ABORT);
555 		n = (int)strtonum(buf, INT_MIN, INT_MAX, &err);
556 		if (err) {
557 			dobeep();
558 			ewprintf("Line number %s", err);
559 			return (FALSE);
560 		}
561 	}
562 	return(setlineno(n));
563 }
564 
565 /*
566  * Set the line number and switch to it.
567  */
568 int
569 setlineno(int n)
570 {
571 	struct line  *clp;
572 
573 	if (n >= 0) {
574 		if (n == 0)
575 			n++;
576 		curwp->w_dotline = n;
577 		clp = lforw(curbp->b_headp);	/* "clp" is first line */
578 		while (--n > 0) {
579 			if (lforw(clp) == curbp->b_headp) {
580 				curwp->w_dotline = curwp->w_bufp->b_lines;
581 				break;
582 			}
583 			clp = lforw(clp);
584 		}
585 	} else {
586 		curwp->w_dotline = curwp->w_bufp->b_lines + n;
587 		clp = lback(curbp->b_headp);	/* "clp" is last line */
588 		while (n < 0) {
589 			if (lback(clp) == curbp->b_headp) {
590 				curwp->w_dotline = 1;
591 				break;
592 			}
593 			clp = lback(clp);
594 			n++;
595 		}
596 	}
597 	curwp->w_dotp = clp;
598 	curwp->w_doto = 0;
599 	curwp->w_rflag |= WFMOVE;
600 	return (TRUE);
601 }
602