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