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