xref: /openbsd/usr.bin/mg/word.c (revision 09467b48)
1 /*	$OpenBSD: word.c,v 1.19 2015/12/30 20:51:51 lum Exp $	*/
2 
3 /* This file is in the public domain. */
4 
5 /*
6  *		Word mode commands.
7  * The routines in this file implement commands that work word at a time.
8  * There are all sorts of word mode commands.
9  */
10 
11 #include <sys/queue.h>
12 #include <signal.h>
13 #include <errno.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 
18 #include "def.h"
19 
20 RSIZE	countfword(void);
21 int	grabword(char **);
22 
23 /*
24  * Move the cursor backward by "n" words. All of the details of motion are
25  * performed by the "backchar" and "forwchar" routines.
26  */
27 /* ARGSUSED */
28 int
29 backword(int f, int n)
30 {
31 	if (n < 0)
32 		return (forwword(f | FFRAND, -n));
33 	if (backchar(FFRAND, 1) == FALSE)
34 		return (FALSE);
35 	while (n--) {
36 		while (inword() == FALSE) {
37 			if (backchar(FFRAND, 1) == FALSE)
38 				return (TRUE);
39 		}
40 		while (inword() != FALSE) {
41 			if (backchar(FFRAND, 1) == FALSE)
42 				return (TRUE);
43 		}
44 	}
45 	return (forwchar(FFRAND, 1));
46 }
47 
48 /*
49  * Move the cursor forward by the specified number of words.  All of the
50  * motion is done by "forwchar".
51  */
52 /* ARGSUSED */
53 int
54 forwword(int f, int n)
55 {
56 	if (n < 0)
57 		return (backword(f | FFRAND, -n));
58 	while (n--) {
59 		while (inword() == FALSE) {
60 			if (forwchar(FFRAND, 1) == FALSE)
61 				return (TRUE);
62 		}
63 		while (inword() != FALSE) {
64 			if (forwchar(FFRAND, 1) == FALSE)
65 				return (TRUE);
66 		}
67 	}
68 	return (TRUE);
69 }
70 
71 /*
72  * Transpose 2 words.
73  * The function below is artifically restricted to only a maximum of 1 iteration
74  * at the moment because the 'undo' functionality within mg needs amended for
75  * multiple movements of point, backwards and forwards.
76  */
77 int
78 transposeword(int f, int n)
79 {
80 	struct line	*tmp1_w_dotp = NULL;
81 	struct line	*tmp2_w_dotp = NULL;
82 	int		 tmp2_w_doto = 0;
83 	int		 tmp1_w_dotline = 0;
84 	int		 tmp2_w_dotline = 0;
85 	int		 tmp1_w_doto;
86 	int		 i;		/* start-of-line space counter */
87 	int		 ret, s;
88 	int		 newline;
89 	int		 leave = 0;
90 	int		 tmp_len;
91 	char		*word1 = NULL;
92 	char		*word2 = NULL;
93 	char		*chr;
94 
95 	if (n == 0)
96 		return (TRUE);
97 
98 	n = 1; /* remove this line to allow muliple-iterations */
99 
100 	if ((s = checkdirty(curbp)) != TRUE)
101 		return (s);
102 	if (curbp->b_flag & BFREADONLY) {
103 		dobeep();
104 		ewprintf("Buffer is read-only");
105 		return (FALSE);
106 	}
107 	undo_boundary_enable(FFRAND, 0);
108 
109 	/* go backwards to find the start of a word to transpose. */
110 	(void)backword(FFRAND, 1);
111 	ret = grabword(&word1);
112 	if (ret == ABORT) {
113 		ewprintf("No word to the left to tranpose.");
114 		return (FALSE);
115 	}
116 	if (ret < 0) {
117 		dobeep();
118 		ewprintf("Error copying word: %s", strerror(ret));
119 		free(word1);
120 		return (FALSE);
121 	}
122 
123 	while (n-- > 0) {
124 		i = 0;
125 		newline = 0;
126 
127 		tmp1_w_doto = curwp->w_doto;
128 		tmp1_w_dotline = curwp->w_dotline;
129 		tmp1_w_dotp = curwp->w_dotp;
130 
131 		/* go forward and find next word. */
132 		while (inword() == FALSE) {
133 			if (forwchar(FFRAND, 1) == FALSE) {
134 				leave = 1;
135 				if (tmp1_w_dotline < curwp->w_dotline)
136 					curwp->w_dotline--;
137 				ewprintf("Don't have two things to transpose");
138 				break;
139 			}
140 			if (curwp->w_doto == 0) {
141 				newline = 1;
142 				i = 0;
143 			} else if (newline)
144 				i++;
145 		}
146 		if (leave) {
147 			tmp2_w_doto = tmp1_w_doto;
148 			tmp2_w_dotline = tmp1_w_dotline;
149 			tmp2_w_dotp = tmp1_w_dotp;
150 			break;
151 		}
152 		tmp2_w_doto = curwp->w_doto;
153 		tmp2_w_dotline = curwp->w_dotline;
154 		tmp2_w_dotp = curwp->w_dotp;
155 
156 		ret = grabword(&word2);
157 		if (ret < 0 || ret == ABORT) {
158 			dobeep();
159 			ewprintf("Error copying word: %s", strerror(ret));
160 			free(word1);
161 			return (FALSE);
162 		}
163 		tmp_len = strlen(word2);
164 		tmp2_w_doto += tmp_len;
165 
166 		curwp->w_doto = tmp1_w_doto;
167 		curwp->w_dotline = tmp1_w_dotline;
168 		curwp->w_dotp = tmp1_w_dotp;
169 
170 		/* insert shuffled along word */
171 		for (chr = word2; *chr != '\0'; ++chr)
172 			linsert(1, *chr);
173 
174 		if (newline)
175 			tmp2_w_doto = i;
176 
177 		curwp->w_doto = tmp2_w_doto;
178 		curwp->w_dotline = tmp2_w_dotline;
179 		curwp->w_dotp = tmp2_w_dotp;
180 
181 		word2 = NULL;
182 	}
183 	curwp->w_doto = tmp2_w_doto;
184 	curwp->w_dotline = tmp2_w_dotline;
185 	curwp->w_dotp = tmp2_w_dotp;
186 
187 	/* insert very first word in its new position */
188 	for (chr = word1; *chr != '\0'; ++chr)
189 		linsert(1, *chr);
190 
191 	if (leave)
192 		(void)backword(FFRAND, 1);
193 
194 	free(word1);
195 	free(word2);
196 
197 	undo_boundary_enable(FFRAND, 1);
198 
199 	return (TRUE);
200 }
201 
202 /*
203  * copy and delete word.
204 */
205 int
206 grabword(char **word)
207 {
208 	int c;
209 
210 	while (inword() == TRUE) {
211 		c = lgetc(curwp->w_dotp, curwp->w_doto);
212 		if (*word == NULL) {
213 			if (asprintf(word, "%c", c) == -1)
214 				return (errno);
215 		} else {
216 			if (asprintf(word, "%s%c", *word, c) == -1)
217 				return (errno);
218 		}
219 		(void)forwdel(FFRAND, 1);
220 	}
221 	if (*word == NULL)
222 		return (ABORT);
223 	return (TRUE);
224 }
225 
226 /*
227  * Move the cursor forward by the specified number of words.  As you move,
228  * convert any characters to upper case.
229  */
230 /* ARGSUSED */
231 int
232 upperword(int f, int n)
233 {
234 	int	c, s;
235 	RSIZE	size;
236 
237 	if ((s = checkdirty(curbp)) != TRUE)
238 		return (s);
239 	if (curbp->b_flag & BFREADONLY) {
240 		dobeep();
241 		ewprintf("Buffer is read-only");
242 		return (FALSE);
243 	}
244 
245 	if (n < 0)
246 		return (FALSE);
247 	while (n--) {
248 		while (inword() == FALSE) {
249 			if (forwchar(FFRAND, 1) == FALSE)
250 				return (TRUE);
251 		}
252 		size = countfword();
253 		undo_add_change(curwp->w_dotp, curwp->w_doto, size);
254 
255 		while (inword() != FALSE) {
256 			c = lgetc(curwp->w_dotp, curwp->w_doto);
257 			if (ISLOWER(c) != FALSE) {
258 				c = TOUPPER(c);
259 				lputc(curwp->w_dotp, curwp->w_doto, c);
260 				lchange(WFFULL);
261 			}
262 			if (forwchar(FFRAND, 1) == FALSE)
263 				return (TRUE);
264 		}
265 	}
266 	return (TRUE);
267 }
268 
269 /*
270  * Move the cursor forward by the specified number of words.  As you move
271  * convert characters to lower case.
272  */
273 /* ARGSUSED */
274 int
275 lowerword(int f, int n)
276 {
277 	int	c, s;
278 	RSIZE	size;
279 
280 	if ((s = checkdirty(curbp)) != TRUE)
281 		return (s);
282 	if (curbp->b_flag & BFREADONLY) {
283 		dobeep();
284 		ewprintf("Buffer is read-only");
285 		return (FALSE);
286 	}
287 	if (n < 0)
288 		return (FALSE);
289 	while (n--) {
290 		while (inword() == FALSE) {
291 			if (forwchar(FFRAND, 1) == FALSE)
292 				return (TRUE);
293 		}
294 		size = countfword();
295 		undo_add_change(curwp->w_dotp, curwp->w_doto, size);
296 
297 		while (inword() != FALSE) {
298 			c = lgetc(curwp->w_dotp, curwp->w_doto);
299 			if (ISUPPER(c) != FALSE) {
300 				c = TOLOWER(c);
301 				lputc(curwp->w_dotp, curwp->w_doto, c);
302 				lchange(WFFULL);
303 			}
304 			if (forwchar(FFRAND, 1) == FALSE)
305 				return (TRUE);
306 		}
307 	}
308 	return (TRUE);
309 }
310 
311 /*
312  * Move the cursor forward by the specified number of words.  As you move
313  * convert the first character of the word to upper case, and subsequent
314  * characters to lower case.  Error if you try to move past the end of the
315  * buffer.
316  */
317 /* ARGSUSED */
318 int
319 capword(int f, int n)
320 {
321 	int	c, s;
322 	RSIZE	size;
323 
324 	if ((s = checkdirty(curbp)) != TRUE)
325 		return (s);
326 	if (curbp->b_flag & BFREADONLY) {
327 		dobeep();
328 		ewprintf("Buffer is read-only");
329 		return (FALSE);
330 	}
331 
332 	if (n < 0)
333 		return (FALSE);
334 	while (n--) {
335 		while (inword() == FALSE) {
336 			if (forwchar(FFRAND, 1) == FALSE)
337 				return (TRUE);
338 		}
339 		size = countfword();
340 		undo_add_change(curwp->w_dotp, curwp->w_doto, size);
341 
342 		if (inword() != FALSE) {
343 			c = lgetc(curwp->w_dotp, curwp->w_doto);
344 			if (ISLOWER(c) != FALSE) {
345 				c = TOUPPER(c);
346 				lputc(curwp->w_dotp, curwp->w_doto, c);
347 				lchange(WFFULL);
348 			}
349 			if (forwchar(FFRAND, 1) == FALSE)
350 				return (TRUE);
351 			while (inword() != FALSE) {
352 				c = lgetc(curwp->w_dotp, curwp->w_doto);
353 				if (ISUPPER(c) != FALSE) {
354 					c = TOLOWER(c);
355 					lputc(curwp->w_dotp, curwp->w_doto, c);
356 					lchange(WFFULL);
357 				}
358 				if (forwchar(FFRAND, 1) == FALSE)
359 					return (TRUE);
360 			}
361 		}
362 	}
363 	return (TRUE);
364 }
365 
366 /*
367  * Count characters in word, from current position
368  */
369 RSIZE
370 countfword()
371 {
372 	RSIZE		 size;
373 	struct line	*dotp;
374 	int		 doto;
375 
376 	dotp = curwp->w_dotp;
377 	doto = curwp->w_doto;
378 	size = 0;
379 
380 	while (inword() != FALSE) {
381 		if (forwchar(FFRAND, 1) == FALSE)
382 			/* hit the end of the buffer */
383 			goto out;
384 		++size;
385 	}
386 out:
387 	curwp->w_dotp = dotp;
388 	curwp->w_doto = doto;
389 	return (size);
390 }
391 
392 
393 /*
394  * Kill forward by "n" words.
395  */
396 /* ARGSUSED */
397 int
398 delfword(int f, int n)
399 {
400 	RSIZE		 size;
401 	struct line	*dotp;
402 	int		 doto;
403 	int s;
404 
405 	if ((s = checkdirty(curbp)) != TRUE)
406 		return (s);
407 	if (curbp->b_flag & BFREADONLY) {
408 		dobeep();
409 		ewprintf("Buffer is read-only");
410 		return (FALSE);
411 	}
412 	if (n < 0)
413 		return (FALSE);
414 
415 	/* purge kill buffer */
416 	if ((lastflag & CFKILL) == 0)
417 		kdelete();
418 
419 	thisflag |= CFKILL;
420 	dotp = curwp->w_dotp;
421 	doto = curwp->w_doto;
422 	size = 0;
423 
424 	while (n--) {
425 		while (inword() == FALSE) {
426 			if (forwchar(FFRAND, 1) == FALSE)
427 				/* hit the end of the buffer */
428 				goto out;
429 			++size;
430 		}
431 		while (inword() != FALSE) {
432 			if (forwchar(FFRAND, 1) == FALSE)
433 				/* hit the end of the buffer */
434 				goto out;
435 			++size;
436 		}
437 	}
438 out:
439 	curwp->w_dotp = dotp;
440 	curwp->w_doto = doto;
441 	return (ldelete(size, KFORW));
442 }
443 
444 /*
445  * Kill backwards by "n" words.  The rules for success and failure are now
446  * different, to prevent strange behavior at the start of the buffer.  The
447  * command only fails if something goes wrong with the actual delete of the
448  * characters.  It is successful even if no characters are deleted, or if you
449  * say delete 5 words, and there are only 4 words left.  I considered making
450  * the first call to "backchar" special, but decided that that would just be
451  * weird. Normally this is bound to "M-Rubout" and to "M-Backspace".
452  */
453 /* ARGSUSED */
454 int
455 delbword(int f, int n)
456 {
457 	RSIZE	size;
458 	int s;
459 
460 	if ((s = checkdirty(curbp)) != TRUE)
461 		return (s);
462 	if (curbp->b_flag & BFREADONLY) {
463 		dobeep();
464 		ewprintf("Buffer is read-only");
465 		return (FALSE);
466 	}
467 
468 	if (n < 0)
469 		return (FALSE);
470 
471 	/* purge kill buffer */
472 	if ((lastflag & CFKILL) == 0)
473 		kdelete();
474 	thisflag |= CFKILL;
475 	if (backchar(FFRAND, 1) == FALSE)
476 		/* hit buffer start */
477 		return (TRUE);
478 
479 	/* one deleted */
480 	size = 1;
481 	while (n--) {
482 		while (inword() == FALSE) {
483 			if (backchar(FFRAND, 1) == FALSE)
484 				/* hit buffer start */
485 				goto out;
486 			++size;
487 		}
488 		while (inword() != FALSE) {
489 			if (backchar(FFRAND, 1) == FALSE)
490 				/* hit buffer start */
491 				goto out;
492 			++size;
493 		}
494 	}
495 	if (forwchar(FFRAND, 1) == FALSE)
496 		return (FALSE);
497 
498 	/* undo assumed delete */
499 	--size;
500 out:
501 	return (ldelete(size, KBACK));
502 }
503 
504 /*
505  * Return TRUE if the character at dot is a character that is considered to be
506  * part of a word. The word character list is hard coded. Should be settable.
507  */
508 int
509 inword(void)
510 {
511 	/* can't use lgetc in ISWORD due to bug in OSK cpp */
512 	return (curwp->w_doto != llength(curwp->w_dotp) &&
513 	    ISWORD(curwp->w_dotp->l_text[curwp->w_doto]));
514 }
515