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