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