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