xref: /openbsd/usr.bin/mg/util.c (revision 3cab2bb3)
1 /*	$OpenBSD: util.c,v 1.42 2019/06/22 15:38:15 lum Exp $	*/
2 
3 /* This file is in the public domain. */
4 
5 /*
6  *		Assorted commands.
7  * This file contains the command processors for a large assortment of
8  * unrelated commands.  The only thing they have in common is that they
9  * are all command processors.
10  */
11 
12 #include <sys/queue.h>
13 #include <ctype.h>
14 #include <signal.h>
15 #include <stdio.h>
16 
17 #include "def.h"
18 
19 /*
20  * Display a bunch of useful information about the current location of dot.
21  * The character under the cursor (in octal), the current line, row, and
22  * column, and approximate position of the cursor in the file (as a
23  * percentage) is displayed.
24  * Also included at the moment are some values in parenthesis for debugging
25  * explicit newline inclusion into the buffer.
26  * The column position assumes an infinite
27  * position display; it does not truncate just because the screen does.
28  * This is normally bound to "C-x =".
29  */
30 /* ARGSUSED */
31 int
32 showcpos(int f, int n)
33 {
34 	struct line	*clp;
35 	char		*msg;
36 	long	 nchar, cchar;
37 	int	 nline, row;
38 	int	 cline, cbyte;		/* Current line/char/byte */
39 	int	 ratio;
40 
41 	/* collect the data */
42 	clp = bfirstlp(curbp);
43 	msg = "Char:";
44 	cchar = 0;
45 	cline = 0;
46 	cbyte = 0;
47 	nchar = 0;
48 	nline = 0;
49 	for (;;) {
50 		/* count lines and display total as (raw) 'lines' and
51 		   compare with b_lines */
52 		++nline;
53 		if (clp == curwp->w_dotp) {
54 			/* obtain (raw) dot line # and compare with w_dotline */
55 			cline = nline;
56 			cchar = nchar + curwp->w_doto;
57 			if (curwp->w_doto == llength(clp))
58 				/* fake a \n at end of line */
59 				cbyte = '\n';
60 			else
61 				cbyte = lgetc(clp, curwp->w_doto);
62 		}
63 		/* include # of chars in this line for point-thru-buff ratio */
64 		nchar += llength(clp);
65 		clp = lforw(clp);
66 		if (clp == curbp->b_headp) {
67 			if (cbyte == '\n' && cline == curbp->b_lines) {
68 				/* swap faked \n for EOB msg */
69 				cbyte = EOF;
70 				msg = "(EOB)";
71 			}
72 			break;
73 		}
74 		/* count the implied newline */
75 		nchar++;
76 	}
77 	/* determine row # within current window */
78 	row = curwp->w_toprow + 1;
79 	clp = curwp->w_linep;
80 	while (clp != curbp->b_headp && clp != curwp->w_dotp) {
81 		++row;
82 		clp = lforw(clp);
83 	}
84 	ratio = nchar ? (100L * cchar) / nchar : 100;
85 	ewprintf("%s %c (0%o)  point=%ld(%d%%)  line=%d  row=%d  col=%d" \
86             "  (blines=%d rlines=%d l_size=%d)", msg,
87 	    cbyte, cbyte, cchar, ratio, cline, row, getcolpos(curwp),
88 	    curbp->b_lines, nline, clp->l_size);
89 	return (TRUE);
90 }
91 
92 int
93 getcolpos(struct mgwin *wp)
94 {
95 	int	col, i, c;
96 	char tmp[5];
97 
98 	/* determine column */
99 	col = 0;
100 
101 	for (i = 0; i < wp->w_doto; ++i) {
102 		c = lgetc(wp->w_dotp, i);
103 		if (c == '\t'
104 #ifdef NOTAB
105 		    && !(wp->w_bufp->b_flag & BFNOTAB)
106 #endif /* NOTAB */
107 			) {
108 			col |= 0x07;
109 			col++;
110 		} else if (ISCTRL(c) != FALSE)
111 			col += 2;
112 		else if (isprint(c)) {
113 			col++;
114 		} else {
115 			col += snprintf(tmp, sizeof(tmp), "\\%o", c);
116 		}
117 
118 	}
119 	return (col);
120 }
121 
122 /*
123  * Twiddle the two characters in front of and under dot, then move forward
124  * one character.  Treat new-line characters the same as any other.
125  * Normally bound to "C-t".  This always works within a line, so "WFEDIT"
126  * is good enough.
127  */
128 /* ARGSUSED */
129 int
130 twiddle(int f, int n)
131 {
132 	struct line	*dotp;
133 	int	 doto, cr;
134 
135 	if (n == 0)
136 		return (TRUE);
137 
138 	dotp = curwp->w_dotp;
139 	doto = curwp->w_doto;
140 
141 	/* Don't twiddle if the dot is on the first char of buffer */
142 	if (doto == 0 && lback(dotp) == curbp->b_headp) {
143 		dobeep();
144 		ewprintf("Beginning of buffer");
145 		return(FALSE);
146 	}
147 	/* Don't twiddle if the dot is on the last char of buffer */
148 	if (doto == llength(dotp) && lforw(dotp) == curbp->b_headp) {
149 		dobeep();
150 		return(FALSE);
151 	}
152 	undo_boundary_enable(FFRAND, 0);
153 	if (doto == 0 && doto == llength(dotp)) { /* only '\n' on this line */
154 		(void)forwline(FFRAND, 1);
155 		curwp->w_doto = 0;
156 	} else {
157 		if (doto == 0) { /* 1st twiddle is on 1st character of a line */
158 			cr = lgetc(dotp, doto);
159 			(void)backdel(FFRAND, 1);
160 			(void)forwchar(FFRAND, 1);
161 			lnewline();
162 			linsert(1, cr);
163 			(void)backdel(FFRAND, 1);
164 		} else {	/* twiddle is elsewhere in line */
165 			cr = lgetc(dotp, doto - 1);
166 			(void)backdel(FFRAND, 1);
167 			(void)forwchar(FFRAND, 1);
168 			linsert(1, cr);
169 		}
170 	}
171 	undo_boundary_enable(FFRAND, 1);
172 	lchange(WFEDIT);
173 	return (TRUE);
174 }
175 
176 /*
177  * Open up some blank space.  The basic plan is to insert a bunch of
178  * newlines, and then back up over them.  Everything is done by the
179  * subcommand processors.  They even handle the looping.  Normally this
180  * is bound to "C-o".
181  */
182 /* ARGSUSED */
183 int
184 openline(int f, int n)
185 {
186 	int	i, s;
187 
188 	if (n < 0)
189 		return (FALSE);
190 	if (n == 0)
191 		return (TRUE);
192 
193 	/* insert newlines */
194 	undo_boundary_enable(FFRAND, 0);
195 	i = n;
196 	do {
197 		s = lnewline();
198 	} while (s == TRUE && --i);
199 
200 	/* then go back up overtop of them all */
201 	if (s == TRUE)
202 		s = backchar(f | FFRAND, n);
203 	undo_boundary_enable(FFRAND, 1);
204 	return (s);
205 }
206 
207 /*
208  * Insert a newline.
209  */
210 /* ARGSUSED */
211 int
212 enewline(int f, int n)
213 {
214 	int	 s;
215 
216 	if (n < 0)
217 		return (FALSE);
218 
219 	while (n--) {
220 		if ((s = lnewline()) != TRUE)
221 			return (s);
222 	}
223 	return (TRUE);
224 }
225 
226 /*
227  * Delete blank lines around dot. What this command does depends if dot is
228  * sitting on a blank line. If dot is sitting on a blank line, this command
229  * deletes all the blank lines above and below the current line. If it is
230  * sitting on a non blank line then it deletes all of the blank lines after
231  * the line. Normally this command is bound to "C-x C-o". Any argument is
232  * ignored.
233  */
234 /* ARGSUSED */
235 int
236 deblank(int f, int n)
237 {
238 	struct line	*lp1, *lp2;
239 	RSIZE	 nld;
240 
241 	lp1 = curwp->w_dotp;
242 	while (llength(lp1) == 0 && (lp2 = lback(lp1)) != curbp->b_headp)
243 		lp1 = lp2;
244 	lp2 = lp1;
245 	nld = (RSIZE)0;
246 	while ((lp2 = lforw(lp2)) != curbp->b_headp && llength(lp2) == 0)
247 		++nld;
248 	if (nld == 0)
249 		return (TRUE);
250 	curwp->w_dotp = lforw(lp1);
251 	curwp->w_doto = 0;
252 	return (ldelete((RSIZE)nld, KNONE));
253 }
254 
255 /*
256  * Delete any whitespace around dot, then insert a space.
257  */
258 int
259 justone(int f, int n)
260 {
261 	undo_boundary_enable(FFRAND, 0);
262 	(void)delwhite(f, n);
263 	linsert(1, ' ');
264 	undo_boundary_enable(FFRAND, 1);
265 	return (TRUE);
266 }
267 
268 /*
269  * Delete any whitespace around dot.
270  */
271 /* ARGSUSED */
272 int
273 delwhite(int f, int n)
274 {
275 	int	col, s;
276 
277 	col = curwp->w_doto;
278 
279 	while (col < llength(curwp->w_dotp) &&
280 	    (isspace(lgetc(curwp->w_dotp, col))))
281 		++col;
282 	do {
283 		if (curwp->w_doto == 0) {
284 			s = FALSE;
285 			break;
286 		}
287 		if ((s = backchar(FFRAND, 1)) != TRUE)
288 			break;
289 	} while (isspace(lgetc(curwp->w_dotp, curwp->w_doto)));
290 
291 	if (s == TRUE)
292 		(void)forwchar(FFRAND, 1);
293 	(void)ldelete((RSIZE)(col - curwp->w_doto), KNONE);
294 	return (TRUE);
295 }
296 
297 /*
298  * Delete any leading whitespace on the current line
299  */
300 int
301 delleadwhite(int f, int n)
302 {
303 	int soff, ls;
304 	struct line *slp;
305 
306 	/* Save current position */
307 	slp = curwp->w_dotp;
308 	soff = curwp->w_doto;
309 
310 	for (ls = 0; ls < llength(slp); ls++)
311                  if (!isspace(lgetc(slp, ls)))
312                         break;
313 	gotobol(FFRAND, 1);
314 	forwdel(FFRAND, ls);
315 	soff -= ls;
316 	if (soff < 0)
317 		soff = 0;
318 	forwchar(FFRAND, soff);
319 
320 	return (TRUE);
321 }
322 
323 /*
324  * Delete any trailing whitespace on the current line
325  */
326 int
327 deltrailwhite(int f, int n)
328 {
329 	int soff;
330 
331 	/* Save current position */
332 	soff = curwp->w_doto;
333 
334 	gotoeol(FFRAND, 1);
335 	delwhite(FFRAND, 1);
336 
337 	/* restore original position, if possible */
338 	if (soff < curwp->w_doto)
339 		curwp->w_doto = soff;
340 
341 	return (TRUE);
342 }
343 
344 
345 
346 /*
347  * Insert a newline, then enough tabs and spaces to duplicate the indentation
348  * of the previous line.  Assumes tabs are every eight characters.  Quite
349  * simple.  Figure out the indentation of the current line.  Insert a newline
350  * by calling the standard routine.  Insert the indentation by inserting the
351  * right number of tabs and spaces.  Return TRUE if all ok.  Return FALSE if
352  * one of the subcommands failed. Normally bound to "C-m".
353  */
354 /* ARGSUSED */
355 int
356 lfindent(int f, int n)
357 {
358 	int	c, i, nicol;
359 	int	s = TRUE;
360 
361 	if (n < 0)
362 		return (FALSE);
363 
364 	undo_boundary_enable(FFRAND, 0);
365 	while (n--) {
366 		nicol = 0;
367 		for (i = 0; i < llength(curwp->w_dotp); ++i) {
368 			c = lgetc(curwp->w_dotp, i);
369 			if (c != ' ' && c != '\t')
370 				break;
371 			if (c == '\t')
372 				nicol |= 0x07;
373 			++nicol;
374 		}
375 		if (lnewline() == FALSE || ((
376 #ifdef	NOTAB
377 		    curbp->b_flag & BFNOTAB) ? linsert(nicol, ' ') == FALSE : (
378 #endif /* NOTAB */
379 		    ((i = nicol / 8) != 0 && linsert(i, '\t') == FALSE) ||
380 		    ((i = nicol % 8) != 0 && linsert(i, ' ') == FALSE)))) {
381 			s = FALSE;
382 			break;
383 		}
384 	}
385 	undo_boundary_enable(FFRAND, 1);
386 	return (s);
387 }
388 
389 /*
390  * Indent the current line. Delete existing leading whitespace,
391  * and use tabs/spaces to achieve correct indentation. Try
392  * to leave dot where it started.
393  */
394 int
395 indent(int f, int n)
396 {
397 	int soff, i;
398 
399 	if (n < 0)
400 		return (FALSE);
401 
402 	delleadwhite(FFRAND, 1);
403 
404 	/* If not invoked with a numerical argument, done */
405 	if (!(f & FFARG))
406 		return (TRUE);
407 
408 	/* insert appropriate whitespace */
409 	soff = curwp->w_doto;
410 	(void)gotobol(FFRAND, 1);
411 	if (
412 #ifdef	NOTAB
413 	    (curbp->b_flag & BFNOTAB) ? linsert(n, ' ') == FALSE :
414 #endif /* NOTAB */
415 	    (((i = n / 8) != 0 && linsert(i, '\t') == FALSE) ||
416 	    ((i = n % 8) != 0 && linsert(i, ' ') == FALSE)))
417 		return (FALSE);
418 
419 	forwchar(FFRAND, soff);
420 
421 	return (TRUE);
422 }
423 
424 
425 /*
426  * Delete forward.  This is real easy, because the basic delete routine does
427  * all of the work.  Watches for negative arguments, and does the right thing.
428  * If any argument is present, it kills rather than deletes, to prevent loss
429  * of text if typed with a big argument.  Normally bound to "C-d".
430  */
431 /* ARGSUSED */
432 int
433 forwdel(int f, int n)
434 {
435 	if (n < 0)
436 		return (backdel(f | FFRAND, -n));
437 
438 	/* really a kill */
439 	if (f & FFARG) {
440 		if ((lastflag & CFKILL) == 0)
441 			kdelete();
442 		thisflag |= CFKILL;
443 	}
444 
445 	return (ldelete((RSIZE) n, (f & FFARG) ? KFORW : KNONE));
446 }
447 
448 /*
449  * Delete backwards.  This is quite easy too, because it's all done with
450  * other functions.  Just move the cursor back, and delete forwards.  Like
451  * delete forward, this actually does a kill if presented with an argument.
452  */
453 /* ARGSUSED */
454 int
455 backdel(int f, int n)
456 {
457 	int	s;
458 
459 	if (n < 0)
460 		return (forwdel(f | FFRAND, -n));
461 
462 	/* really a kill */
463 	if (f & FFARG) {
464 		if ((lastflag & CFKILL) == 0)
465 			kdelete();
466 		thisflag |= CFKILL;
467 	}
468 	if ((s = backchar(f | FFRAND, n)) == TRUE)
469 		s = ldelete((RSIZE)n, (f & FFARG) ? KFORW : KNONE);
470 
471 	return (s);
472 }
473 
474 #ifdef	NOTAB
475 /* ARGSUSED */
476 int
477 space_to_tabstop(int f, int n)
478 {
479 	if (n < 0)
480 		return (FALSE);
481 	if (n == 0)
482 		return (TRUE);
483 	return (linsert((n << 3) - (curwp->w_doto & 7), ' '));
484 }
485 #endif /* NOTAB */
486 
487 /*
488  * Move the dot to the first non-whitespace character of the current line.
489  */
490 int
491 backtoindent(int f, int n)
492 {
493 	gotobol(FFRAND, 1);
494 	while (curwp->w_doto < llength(curwp->w_dotp) &&
495 	    (isspace(lgetc(curwp->w_dotp, curwp->w_doto))))
496 		++curwp->w_doto;
497 	return (TRUE);
498 }
499 
500 /*
501  * Join the current line to the previous, or with arg, the next line
502  * to the current one.  If the former line is not empty, leave exactly
503  * one space at the joint.  Otherwise, leave no whitespace.
504  */
505 int
506 joinline(int f, int n)
507 {
508 	int doto;
509 
510 	undo_boundary_enable(FFRAND, 0);
511 	if (f & FFARG) {
512 		gotoeol(FFRAND, 1);
513 		forwdel(FFRAND, 1);
514 	} else {
515 		gotobol(FFRAND, 1);
516 		backdel(FFRAND, 1);
517 	}
518 
519 	delwhite(FFRAND, 1);
520 
521 	if ((doto = curwp->w_doto) > 0) {
522 		linsert(1, ' ');
523 		curwp->w_doto = doto;
524 	}
525 	undo_boundary_enable(FFRAND, 1);
526 
527 	return (TRUE);
528 }
529