1 1.5 gutterid /* $NetBSD: search.c,v 1.5 2023/02/13 23:08:43 gutteridge Exp $ */ 2 1.1 christos /*- 3 1.1 christos * Copyright (c) 1992, 1993, 1994 4 1.1 christos * The Regents of the University of California. All rights reserved. 5 1.1 christos * Copyright (c) 1992, 1993, 1994, 1995, 1996 6 1.1 christos * Keith Bostic. All rights reserved. 7 1.1 christos * 8 1.1 christos * See the LICENSE file for redistribution information. 9 1.1 christos */ 10 1.1 christos 11 1.1 christos #include "config.h" 12 1.1 christos 13 1.3 christos #include <sys/cdefs.h> 14 1.3 christos #if 0 15 1.1 christos #ifndef lint 16 1.1 christos static const char sccsid[] = "Id: search.c,v 10.31 2001/06/25 15:19:12 skimo Exp (Berkeley) Date: 2001/06/25 15:19:12 "; 17 1.1 christos #endif /* not lint */ 18 1.3 christos #else 19 1.5 gutterid __RCSID("$NetBSD: search.c,v 1.5 2023/02/13 23:08:43 gutteridge Exp $"); 20 1.3 christos #endif 21 1.1 christos 22 1.1 christos #include <sys/types.h> 23 1.1 christos #include <sys/queue.h> 24 1.1 christos 25 1.1 christos #include <bitstring.h> 26 1.1 christos #include <ctype.h> 27 1.1 christos #include <errno.h> 28 1.1 christos #include <limits.h> 29 1.1 christos #include <stdio.h> 30 1.1 christos #include <stdlib.h> 31 1.1 christos #include <string.h> 32 1.1 christos #include <unistd.h> 33 1.1 christos 34 1.1 christos #include "common.h" 35 1.1 christos 36 1.1 christos typedef enum { S_EMPTY, S_EOF, S_NOPREV, S_NOTFOUND, S_SOF, S_WRAP } smsg_t; 37 1.1 christos 38 1.1 christos static void search_msg __P((SCR *, smsg_t)); 39 1.1 christos static int search_init __P((SCR *, dir_t, CHAR_T *, size_t, CHAR_T **, u_int)); 40 1.1 christos 41 1.1 christos /* 42 1.1 christos * search_init -- 43 1.1 christos * Set up a search. 44 1.1 christos */ 45 1.1 christos static int 46 1.1 christos search_init(SCR *sp, dir_t dir, CHAR_T *ptrn, size_t plen, CHAR_T **epp, u_int flags) 47 1.1 christos { 48 1.1 christos db_recno_t lno; 49 1.1 christos int delim; 50 1.1 christos CHAR_T *p, *t; 51 1.1 christos 52 1.1 christos /* If the file is empty, it's a fast search. */ 53 1.1 christos if (sp->lno <= 1) { 54 1.1 christos if (db_last(sp, &lno)) 55 1.1 christos return (1); 56 1.1 christos if (lno == 0) { 57 1.1 christos if (LF_ISSET(SEARCH_MSG)) 58 1.1 christos search_msg(sp, S_EMPTY); 59 1.1 christos return (1); 60 1.1 christos } 61 1.1 christos } 62 1.1 christos 63 1.1 christos if (LF_ISSET(SEARCH_PARSE)) { /* Parse the string. */ 64 1.1 christos /* 65 1.1 christos * Use the saved pattern if no pattern specified, or if only 66 1.1 christos * one or two delimiter characters specified. 67 1.1 christos * 68 1.1 christos * !!! 69 1.1 christos * Historically, only the pattern itself was saved, vi didn't 70 1.1 christos * preserve addressing or delta information. 71 1.1 christos */ 72 1.1 christos if (ptrn == NULL) 73 1.1 christos goto prev; 74 1.1 christos if (plen == 1) { 75 1.1 christos if (epp != NULL) 76 1.1 christos *epp = ptrn + 1; 77 1.1 christos goto prev; 78 1.1 christos } 79 1.1 christos if (ptrn[0] == ptrn[1]) { 80 1.1 christos if (epp != NULL) 81 1.1 christos *epp = ptrn + 2; 82 1.1 christos 83 1.1 christos /* Complain if we don't have a previous pattern. */ 84 1.1 christos prev: if (sp->re == NULL) { 85 1.1 christos search_msg(sp, S_NOPREV); 86 1.1 christos return (1); 87 1.1 christos } 88 1.1 christos /* Re-compile the search pattern if necessary. */ 89 1.1 christos if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp, 90 1.1 christos sp->re, sp->re_len, NULL, NULL, &sp->re_c, 91 1.1 christos SEARCH_CSEARCH | SEARCH_MSG)) 92 1.1 christos return (1); 93 1.1 christos 94 1.1 christos /* Set the search direction. */ 95 1.1 christos if (LF_ISSET(SEARCH_SET)) 96 1.1 christos sp->searchdir = dir; 97 1.1 christos return (0); 98 1.1 christos } 99 1.1 christos 100 1.1 christos /* 101 1.1 christos * Set the delimiter, and move forward to the terminating 102 1.1 christos * delimiter, handling escaped delimiters. 103 1.1 christos * 104 1.1 christos * QUOTING NOTE: 105 1.1 christos * Only discard an escape character if it escapes a delimiter. 106 1.1 christos */ 107 1.1 christos for (delim = *ptrn, p = t = ++ptrn;; *t++ = *p++) { 108 1.1 christos if (--plen == 0 || p[0] == delim) { 109 1.1 christos if (plen != 0) 110 1.1 christos ++p; 111 1.1 christos break; 112 1.1 christos } 113 1.5 gutterid if (plen > 1 && p[0] == '\\') { 114 1.5 gutterid if(p[1] == delim) { 115 1.5 gutterid ++p; 116 1.5 gutterid --plen; 117 1.5 gutterid } else if(p[1] == '\\') { 118 1.5 gutterid *t++ = *p++; 119 1.5 gutterid --plen; 120 1.5 gutterid } 121 1.1 christos } 122 1.1 christos } 123 1.1 christos if (epp != NULL) 124 1.1 christos *epp = p; 125 1.1 christos 126 1.1 christos plen = t - ptrn; 127 1.1 christos } 128 1.1 christos 129 1.1 christos /* Compile the RE. */ 130 1.1 christos if (re_compile(sp, ptrn, plen, &sp->re, &sp->re_len, &sp->re_c, 131 1.1 christos SEARCH_CSEARCH | LF_ISSET(SEARCH_CSCOPE | SEARCH_EXTEND | 132 1.1 christos SEARCH_IC | SEARCH_LITERAL | SEARCH_MSG | SEARCH_TAG))) 133 1.1 christos return (1); 134 1.1 christos 135 1.1 christos /* Set the search direction. */ 136 1.1 christos if (LF_ISSET(SEARCH_SET)) 137 1.1 christos sp->searchdir = dir; 138 1.1 christos 139 1.1 christos return (0); 140 1.1 christos } 141 1.1 christos 142 1.1 christos /* 143 1.1 christos * f_search -- 144 1.1 christos * Do a forward search. 145 1.1 christos * 146 1.1 christos * PUBLIC: int f_search __P((SCR *, 147 1.1 christos * PUBLIC: MARK *, MARK *, CHAR_T *, size_t, CHAR_T **, u_int)); 148 1.1 christos */ 149 1.1 christos int 150 1.1 christos f_search(SCR *sp, MARK *fm, MARK *rm, CHAR_T *ptrn, size_t plen, CHAR_T **eptrn, u_int flags) 151 1.1 christos { 152 1.1 christos busy_t btype; 153 1.1 christos db_recno_t lno; 154 1.1 christos regmatch_t match[1]; 155 1.1 christos size_t coff, len; 156 1.4 rin int cnt, eval, rval, wrapped = 0; 157 1.1 christos CHAR_T *l; 158 1.1 christos 159 1.1 christos if (search_init(sp, FORWARD, ptrn, plen, eptrn, flags)) 160 1.1 christos return (1); 161 1.1 christos 162 1.1 christos /* Figure out if we're going to wrap. */ 163 1.1 christos if (!LF_ISSET(SEARCH_NOOPT) && O_ISSET(sp, O_WRAPSCAN)) 164 1.1 christos LF_SET(SEARCH_WRAP); 165 1.1 christos 166 1.1 christos if (LF_ISSET(SEARCH_FIRST)) { 167 1.1 christos lno = 1; 168 1.1 christos coff = 0; 169 1.1 christos } else { 170 1.1 christos if (db_get(sp, fm->lno, DBG_FATAL, &l, &len)) 171 1.1 christos return (1); 172 1.1 christos lno = fm->lno; 173 1.1 christos 174 1.1 christos /* 175 1.1 christos * If doing incremental search, start searching at the previous 176 1.1 christos * column, so that we search a minimal distance and still match 177 1.1 christos * special patterns, e.g., \< for beginning of a word. 178 1.1 christos * 179 1.1 christos * Otherwise, start searching immediately after the cursor. If 180 1.1 christos * at the end of the line, start searching on the next line. 181 1.1 christos * This is incompatible (read bug fix) with the historic vi -- 182 1.1 christos * searches for the '$' pattern never moved forward, and the 183 1.1 christos * "-t foo" didn't work if the 'f' was the first character in 184 1.1 christos * the file. 185 1.1 christos */ 186 1.1 christos if (LF_ISSET(SEARCH_INCR)) { 187 1.1 christos if ((coff = fm->cno) != 0) 188 1.1 christos --coff; 189 1.1 christos } else if (fm->cno + 1 >= len) { 190 1.1 christos coff = 0; 191 1.1 christos lno = fm->lno + 1; 192 1.1 christos if (db_get(sp, lno, 0, &l, &len)) { 193 1.1 christos if (!LF_ISSET(SEARCH_WRAP)) { 194 1.1 christos if (LF_ISSET(SEARCH_MSG)) 195 1.1 christos search_msg(sp, S_EOF); 196 1.1 christos return (1); 197 1.1 christos } 198 1.1 christos lno = 1; 199 1.4 rin wrapped = 1; 200 1.1 christos } 201 1.1 christos } else 202 1.1 christos coff = fm->cno + 1; 203 1.1 christos } 204 1.1 christos 205 1.1 christos btype = BUSY_ON; 206 1.4 rin for (cnt = INTERRUPT_CHECK, rval = 1;; ++lno, coff = 0) { 207 1.1 christos if (cnt-- == 0) { 208 1.1 christos if (INTERRUPTED(sp)) 209 1.1 christos break; 210 1.1 christos if (LF_ISSET(SEARCH_MSG)) { 211 1.1 christos search_busy(sp, btype); 212 1.1 christos btype = BUSY_UPDATE; 213 1.1 christos } 214 1.1 christos cnt = INTERRUPT_CHECK; 215 1.1 christos } 216 1.2 christos if ((wrapped && lno > fm->lno) || 217 1.2 christos db_get(sp, lno, 0, &l, &len)) { 218 1.1 christos if (wrapped) { 219 1.1 christos if (LF_ISSET(SEARCH_MSG)) 220 1.1 christos search_msg(sp, S_NOTFOUND); 221 1.1 christos break; 222 1.1 christos } 223 1.1 christos if (!LF_ISSET(SEARCH_WRAP)) { 224 1.1 christos if (LF_ISSET(SEARCH_MSG)) 225 1.1 christos search_msg(sp, S_EOF); 226 1.1 christos break; 227 1.1 christos } 228 1.1 christos lno = 0; 229 1.1 christos wrapped = 1; 230 1.1 christos continue; 231 1.1 christos } 232 1.1 christos 233 1.1 christos /* If already at EOL, just keep going. */ 234 1.1 christos if (len != 0 && coff == len) 235 1.1 christos continue; 236 1.1 christos 237 1.1 christos /* Set the termination. */ 238 1.1 christos match[0].rm_so = coff; 239 1.1 christos match[0].rm_eo = len; 240 1.1 christos 241 1.1 christos #if defined(DEBUG) && 0 242 1.1 christos vtrace(sp, "F search: %lu from %u to %u\n", 243 1.1 christos lno, coff, len != 0 ? len - 1 : len); 244 1.1 christos #endif 245 1.1 christos /* Search the line. */ 246 1.1 christos eval = regexec(&sp->re_c, l, 1, match, 247 1.1 christos (match[0].rm_so == 0 ? 0 : REG_NOTBOL) | REG_STARTEND); 248 1.1 christos if (eval == REG_NOMATCH) 249 1.1 christos continue; 250 1.1 christos if (eval != 0) { 251 1.1 christos if (LF_ISSET(SEARCH_MSG)) 252 1.1 christos re_error(sp, eval, &sp->re_c); 253 1.1 christos else 254 1.1 christos (void)sp->gp->scr_bell(sp); 255 1.1 christos break; 256 1.1 christos } 257 1.1 christos 258 1.1 christos /* Warn if the search wrapped. */ 259 1.1 christos if (wrapped && LF_ISSET(SEARCH_WMSG)) 260 1.1 christos search_msg(sp, S_WRAP); 261 1.1 christos 262 1.1 christos #if defined(DEBUG) && 0 263 1.1 christos vtrace(sp, "F search: %qu to %qu\n", 264 1.1 christos match[0].rm_so, match[0].rm_eo); 265 1.1 christos #endif 266 1.1 christos rm->lno = lno; 267 1.1 christos rm->cno = match[0].rm_so; 268 1.1 christos 269 1.1 christos /* 270 1.1 christos * If a change command, it's possible to move beyond the end 271 1.1 christos * of a line. Historic vi generally got this wrong (e.g. try 272 1.1 christos * "c?$<cr>"). Not all that sure this gets it right, there 273 1.1 christos * are lots of strange cases. 274 1.1 christos */ 275 1.1 christos if (!LF_ISSET(SEARCH_EOL) && rm->cno >= len) 276 1.1 christos rm->cno = len != 0 ? len - 1 : 0; 277 1.1 christos 278 1.1 christos rval = 0; 279 1.1 christos break; 280 1.1 christos } 281 1.1 christos 282 1.1 christos if (LF_ISSET(SEARCH_MSG)) 283 1.1 christos search_busy(sp, BUSY_OFF); 284 1.1 christos return (rval); 285 1.1 christos } 286 1.1 christos 287 1.1 christos /* 288 1.1 christos * b_search -- 289 1.1 christos * Do a backward search. 290 1.1 christos * 291 1.1 christos * PUBLIC: int b_search __P((SCR *, 292 1.1 christos * PUBLIC: MARK *, MARK *, CHAR_T *, size_t, CHAR_T **, u_int)); 293 1.1 christos */ 294 1.1 christos int 295 1.1 christos b_search(SCR *sp, MARK *fm, MARK *rm, CHAR_T *ptrn, size_t plen, CHAR_T **eptrn, u_int flags) 296 1.1 christos { 297 1.1 christos busy_t btype; 298 1.1 christos db_recno_t lno; 299 1.1 christos regmatch_t match[1]; 300 1.1 christos size_t coff, last, len; 301 1.1 christos int cnt, eval, rval, wrapped; 302 1.1 christos CHAR_T *l; 303 1.1 christos 304 1.1 christos if (search_init(sp, BACKWARD, ptrn, plen, eptrn, flags)) 305 1.1 christos return (1); 306 1.1 christos 307 1.1 christos /* Figure out if we're going to wrap. */ 308 1.1 christos if (!LF_ISSET(SEARCH_NOOPT) && O_ISSET(sp, O_WRAPSCAN)) 309 1.1 christos LF_SET(SEARCH_WRAP); 310 1.1 christos 311 1.1 christos /* 312 1.1 christos * If doing incremental search, set the "starting" position past the 313 1.1 christos * current column, so that we search a minimal distance and still 314 1.1 christos * match special patterns, e.g., \> for the end of a word. This is 315 1.1 christos * safe when the cursor is at the end of a line because we only use 316 1.1 christos * it for comparison with the location of the match. 317 1.1 christos * 318 1.1 christos * Otherwise, start searching immediately before the cursor. If in 319 1.1 christos * the first column, start search on the previous line. 320 1.1 christos */ 321 1.1 christos if (LF_ISSET(SEARCH_INCR)) { 322 1.1 christos lno = fm->lno; 323 1.1 christos coff = fm->cno + 1; 324 1.1 christos } else { 325 1.1 christos if (fm->cno == 0) { 326 1.1 christos if (fm->lno == 1 && !LF_ISSET(SEARCH_WRAP)) { 327 1.1 christos if (LF_ISSET(SEARCH_MSG)) 328 1.1 christos search_msg(sp, S_SOF); 329 1.1 christos return (1); 330 1.1 christos } 331 1.1 christos lno = fm->lno - 1; 332 1.1 christos } else 333 1.1 christos lno = fm->lno; 334 1.1 christos coff = fm->cno; 335 1.1 christos } 336 1.1 christos 337 1.1 christos btype = BUSY_ON; 338 1.1 christos for (cnt = INTERRUPT_CHECK, rval = 1, wrapped = 0;; --lno, coff = 0) { 339 1.1 christos if (cnt-- == 0) { 340 1.1 christos if (INTERRUPTED(sp)) 341 1.1 christos break; 342 1.1 christos if (LF_ISSET(SEARCH_MSG)) { 343 1.1 christos search_busy(sp, btype); 344 1.1 christos btype = BUSY_UPDATE; 345 1.1 christos } 346 1.1 christos cnt = INTERRUPT_CHECK; 347 1.1 christos } 348 1.2 christos if ((wrapped && lno < fm->lno) || lno == 0) { 349 1.1 christos if (wrapped) { 350 1.1 christos if (LF_ISSET(SEARCH_MSG)) 351 1.1 christos search_msg(sp, S_NOTFOUND); 352 1.1 christos break; 353 1.1 christos } 354 1.1 christos if (!LF_ISSET(SEARCH_WRAP)) { 355 1.1 christos if (LF_ISSET(SEARCH_MSG)) 356 1.1 christos search_msg(sp, S_SOF); 357 1.1 christos break; 358 1.1 christos } 359 1.1 christos if (db_last(sp, &lno)) 360 1.1 christos break; 361 1.1 christos if (lno == 0) { 362 1.1 christos if (LF_ISSET(SEARCH_MSG)) 363 1.1 christos search_msg(sp, S_EMPTY); 364 1.1 christos break; 365 1.1 christos } 366 1.1 christos ++lno; 367 1.1 christos wrapped = 1; 368 1.1 christos continue; 369 1.1 christos } 370 1.1 christos 371 1.1 christos if (db_get(sp, lno, 0, &l, &len)) 372 1.1 christos break; 373 1.1 christos 374 1.1 christos /* Set the termination. */ 375 1.1 christos match[0].rm_so = 0; 376 1.1 christos match[0].rm_eo = len; 377 1.1 christos 378 1.1 christos #if defined(DEBUG) && 0 379 1.1 christos vtrace(sp, 380 1.1 christos "B search: %lu from 0 to %qu\n", lno, match[0].rm_eo); 381 1.1 christos #endif 382 1.1 christos /* Search the line. */ 383 1.1 christos eval = regexec(&sp->re_c, l, 1, match, 384 1.2 christos ((size_t)match[0].rm_eo == len ? 0 : REG_NOTEOL) | REG_STARTEND); 385 1.1 christos if (eval == REG_NOMATCH) 386 1.1 christos continue; 387 1.1 christos if (eval != 0) { 388 1.1 christos if (LF_ISSET(SEARCH_MSG)) 389 1.1 christos re_error(sp, eval, &sp->re_c); 390 1.1 christos else 391 1.1 christos (void)sp->gp->scr_bell(sp); 392 1.1 christos break; 393 1.1 christos } 394 1.1 christos 395 1.1 christos /* Check for a match starting past the cursor. */ 396 1.2 christos if (coff != 0 && (size_t)match[0].rm_so >= coff) 397 1.1 christos continue; 398 1.1 christos 399 1.1 christos /* Warn if the search wrapped. */ 400 1.1 christos if (wrapped && LF_ISSET(SEARCH_WMSG)) 401 1.1 christos search_msg(sp, S_WRAP); 402 1.1 christos 403 1.1 christos #if defined(DEBUG) && 0 404 1.1 christos vtrace(sp, "B found: %qu to %qu\n", 405 1.1 christos match[0].rm_so, match[0].rm_eo); 406 1.1 christos #endif 407 1.1 christos /* 408 1.1 christos * We now have the first match on the line. Step through the 409 1.1 christos * line character by character until find the last acceptable 410 1.1 christos * match. This is painful, we need a better interface to regex 411 1.1 christos * to make this work. 412 1.1 christos */ 413 1.1 christos for (;;) { 414 1.1 christos last = match[0].rm_so++; 415 1.2 christos if ((size_t)match[0].rm_so >= len) 416 1.1 christos break; 417 1.1 christos match[0].rm_eo = len; 418 1.1 christos eval = regexec(&sp->re_c, l, 1, match, 419 1.1 christos (match[0].rm_so == 0 ? 0 : REG_NOTBOL) | 420 1.1 christos REG_STARTEND); 421 1.1 christos if (eval == REG_NOMATCH) 422 1.1 christos break; 423 1.1 christos if (eval != 0) { 424 1.1 christos if (LF_ISSET(SEARCH_MSG)) 425 1.1 christos re_error(sp, eval, &sp->re_c); 426 1.1 christos else 427 1.1 christos (void)sp->gp->scr_bell(sp); 428 1.1 christos goto err; 429 1.1 christos } 430 1.2 christos if (coff && (size_t)match[0].rm_so >= coff) 431 1.1 christos break; 432 1.1 christos } 433 1.1 christos rm->lno = lno; 434 1.1 christos 435 1.1 christos /* See comment in f_search(). */ 436 1.1 christos if (!LF_ISSET(SEARCH_EOL) && last >= len) 437 1.1 christos rm->cno = len != 0 ? len - 1 : 0; 438 1.1 christos else 439 1.1 christos rm->cno = last; 440 1.1 christos rval = 0; 441 1.1 christos break; 442 1.1 christos } 443 1.1 christos 444 1.1 christos err: if (LF_ISSET(SEARCH_MSG)) 445 1.1 christos search_busy(sp, BUSY_OFF); 446 1.1 christos return (rval); 447 1.1 christos } 448 1.1 christos 449 1.1 christos /* 450 1.1 christos * search_msg -- 451 1.1 christos * Display one of the search messages. 452 1.1 christos */ 453 1.1 christos static void 454 1.1 christos search_msg(SCR *sp, smsg_t msg) 455 1.1 christos { 456 1.1 christos switch (msg) { 457 1.1 christos case S_EMPTY: 458 1.1 christos msgq(sp, M_ERR, "072|File empty; nothing to search"); 459 1.1 christos break; 460 1.1 christos case S_EOF: 461 1.1 christos msgq(sp, M_ERR, 462 1.1 christos "073|Reached end-of-file without finding the pattern"); 463 1.1 christos break; 464 1.1 christos case S_NOPREV: 465 1.1 christos msgq(sp, M_ERR, "074|No previous search pattern"); 466 1.1 christos break; 467 1.1 christos case S_NOTFOUND: 468 1.1 christos msgq(sp, M_ERR, "075|Pattern not found"); 469 1.1 christos break; 470 1.1 christos case S_SOF: 471 1.1 christos msgq(sp, M_ERR, 472 1.1 christos "076|Reached top-of-file without finding the pattern"); 473 1.1 christos break; 474 1.1 christos case S_WRAP: 475 1.1 christos msgq(sp, M_ERR, "077|Search wrapped"); 476 1.1 christos break; 477 1.1 christos default: 478 1.1 christos abort(); 479 1.1 christos } 480 1.1 christos } 481 1.1 christos 482 1.1 christos /* 483 1.1 christos * search_busy -- 484 1.1 christos * Put up the busy searching message. 485 1.1 christos * 486 1.1 christos * PUBLIC: void search_busy __P((SCR *, busy_t)); 487 1.1 christos */ 488 1.1 christos void 489 1.1 christos search_busy(SCR *sp, busy_t btype) 490 1.1 christos { 491 1.1 christos sp->gp->scr_busy(sp, "078|Searching...", btype); 492 1.1 christos } 493