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