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 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 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 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 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 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 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 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 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 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 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 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