* New version 2.21
[alpine.git] / pico / search.c
bloba8cf6033e58932c16a120dc320a39309f2eeae3c
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: search.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2013-2017 Eduardo Chappa
8 * Copyright 2006-2008 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
20 * Program: Searching routines
22 * The functions in this file implement commands that search in the forward
23 * and backward directions. There are no special characters in the search
24 * strings. Probably should have a regular expression search, or something
25 * like that.
29 #include "headers.h"
31 int eq(UCS, UCS);
32 int expandp(UCS *, UCS *, int);
33 int readnumpat(char *);
34 void get_pat_cases(UCS *, UCS *);
35 int srpat(char *, UCS *, size_t, int, int);
36 int readpattern(char *, int, int);
37 int replace_pat(UCS *, int *, int);
38 int replace_all(UCS *, UCS *, int);
39 void reverse_line(LINE *);
40 void reverse_buffer(void);
41 void reverse_ucs4(UCS *);
42 void reverse_all(UCS *, int);
43 void supdate(UCS *, int);
44 char *sucs4_to_utf8_cpystr(UCS *, int);
46 #define FWS_RETURN(RV) { \
47 thisflag |= CFSRCH; \
48 curwp->w_flag |= WFMODE; \
49 sgarbk = TRUE; \
50 return(RV); \
53 /* The search menu leaves a number of keys free, some are taken
54 * as subcommands of the search command, and some are taken are
55 * editing commands. This leaves the following keys open:
56 * ^J, ^N, ^O, ^P, ^R, ^T, ^U, ^V, ^W, ^X and ^Y.
57 * Out of these keys, ^J, ^N, ^P and ^X are not defined as commands, however,
58 * in some patches ^N, ^P and ^X are defined. ^N is defined as part of
59 * an editing command, ^P and ^X are defined to delete paragraphs and to
60 * remove text to the end of file, so only ^J is undefined.
63 #define REPLACE_KEY 2 /* the location of the replace key in the array below */
65 EXTRAKEYS menu_srchpat[] = {
66 {"^Y", N_("FirstLine"), (CTRL|'Y')},
67 {"^V", N_("LastLine"), (CTRL|'V')},
68 {"^R", N_("No Replace"), (CTRL|'R')},
69 {"^^", N_("Optns Menu"), (CTRL|'^')}, /* toggle this menu or options menu */
70 {"^T", N_("LineNumber"), (CTRL|'T')},
71 {"^W", N_("Start of Para"), (CTRL|'W')},
72 {"^O", N_("End of Para"), (CTRL|'O')},
73 {"^U", N_("FullJustify"), (CTRL|'U')},
74 {NULL, NULL, 0},
75 {NULL, NULL, 0}
78 #define EXACTSR_KEY 1 /* toggle an exact or approximate search */
79 #define BGNLINE_KEY 3 /* the location of Bgn Line command in the array below */
80 #define ENDLINE_KEY 4 /* the location of End Line command in the array below */
81 #define BSEARCH_KEY 5 /* the location of the bsearch key in the array below */
82 EXTRAKEYS menu_srchopt[] = {
83 {"^^", N_("Orig Menu"), (CTRL|'^')}, /* toggle original or options menu */
84 {"^X", N_("Exact"), (CTRL|'X')}, /* toggle exact vs non exact */
85 {"^R", N_("No Replace"), (CTRL|'R')}, /* toggle replace or not replace */
86 {"^V", N_("Bgn Line"), (CTRL|'V')}, /* toggle Bgn Line or anywhere */
87 {"^N", N_("End Line"), (CTRL|'N')}, /* toggle End Line or anywhere */
88 {"^P", N_("BackSearch"), (CTRL|'P')}, /* toggle Backward or forward */
89 {NULL, NULL, 0},
90 {NULL, NULL, 0},
91 {NULL, NULL, 0},
92 {NULL, NULL, 0}
96 * Search forward. Get a search string from the user, and search, beginning at
97 * ".", for the string. If found, reset the "." to be just after the match
98 * string, and [perhaps] repaint the display. Bound to "C-S".
101 /* string search input parameters */
103 #define PTBEG 1 /* leave the point at the begining on search */
104 #define PTEND 2 /* leave the point at the end on search */
106 #define NPMT (2*NLINE+32)
109 static char *SearchHelpText[] = {
110 /* TRANSLATORS: Some help text that goes together in a group. */
111 N_("Help for Search Command"),
112 " ",
113 N_(" Enter the words or characters you would like to search"),
114 N_("~ for, then press ~R~e~t~u~r~n. The search then takes place."),
115 N_(" When the characters or words that you entered "),
116 N_(" are found, the buffer will be redisplayed with the cursor "),
117 N_(" at the beginning of the selected text."),
118 " ",
119 N_(" The most recent string for which a search was made is"),
120 N_(" displayed in the \"Search\" prompt between the square"),
121 N_(" brackets. This string is the default search prompt."),
122 N_("~ Hitting only ~R~e~t~u~r~n or at the prompt will cause the"),
123 N_(" search to be made with the default value."),
124 " ",
125 N_(" The text search is not case sensitive, and will examine the"),
126 N_(" entire message."),
127 " ",
128 N_(" Should the search fail, a message will be displayed."),
129 " ",
130 N_("End of Search Help."),
131 " ",
132 NULL
137 * Compare two characters. The "bc" comes from the buffer. It has it's case
138 * folded out. The "pc" is from the pattern.
141 eq(UCS bc, UCS pc)
143 if ((curwp->w_bufp->b_mode & MDEXACT) == 0){
144 if (bc>='a' && bc<='z')
145 bc -= 0x20;
147 if (pc>='a' && pc<='z')
148 pc -= 0x20;
151 return(bc == pc);
156 forwsearch(int f, int n)
158 int status, flags;
159 int wrapt = FALSE, wrapt2 = FALSE;
160 int repl_mode = FALSE;
161 UCS defpat[NPAT];
162 int search = FALSE;
163 EML eml;
166 /* resolve the repeat count */
167 if (n == 0)
168 n = 1;
170 if (n < 1) /* search backwards */
171 FWS_RETURN(0);
173 defpat[0] = '\0';
174 /* defaults: usual menu, search forward, not case sensitive */
176 flags = SR_ORIGMEN | SR_FORWARD;
178 /* exact search is sticky -- that is, once one is done, so will be
179 * the next ones. This is consistent with all all searches being
180 * case insensitive by default.
182 if((curwp->w_bufp->b_mode & MDEXACT) == 0)
183 flags |= SR_NOEXACT;
184 else
185 flags |= SR_EXACTSR;
187 /* ask the user for the text of a pattern */
188 while(1){
190 if (gmode & MDREPLACE)
191 status = srpat("Search", defpat, NPAT, repl_mode, flags);
192 else
193 status = readpattern("Search", TRUE, flags);
195 switch(status){
196 case TRUE: /* user typed something */
197 search = TRUE;
198 break;
200 case HELPCH: /* help requested */
201 if(Pmaster){
202 VARS_TO_SAVE *saved_state;
204 saved_state = save_pico_state();
205 (*Pmaster->helper)(Pmaster->search_help,
206 _("Help for Searching"), 1);
207 if(saved_state){
208 restore_pico_state(saved_state);
209 free_pico_state(saved_state);
212 else
213 pico_help(SearchHelpText, _("Help for Searching"), 1);
215 case (CTRL|'L'): /* redraw requested */
216 pico_refresh(FALSE, 1);
217 update();
218 break;
221 case (CTRL|'P'):
222 if(flags & SR_ORIGMEN){
223 /* Undefined still */
225 if(flags & SR_OPTNMEN){
226 if(flags & SR_FORWARD){
227 flags &= ~SR_FORWARD;
228 flags |= SR_BACKWRD;
229 } else {
230 flags &= ~SR_BACKWRD;
231 flags |= SR_FORWARD;
234 break;
236 case (CTRL|'V'):
237 if(flags & SR_ORIGMEN){
238 gotoeob(0, 1);
239 mlerase();
240 FWS_RETURN(TRUE);
241 } else if (flags & SR_OPTNMEN){
242 if(flags & SR_ENDLINE)
243 flags &= ~SR_ENDLINE;
244 if(flags & SR_BEGLINE)
245 flags &= ~SR_BEGLINE;
246 else
247 flags |= SR_BEGLINE;
249 break;
251 case (CTRL|'N'):
252 if(flags & SR_ORIGMEN){
253 /* undefined still */
254 } else if (flags & SR_OPTNMEN){
255 if(flags & SR_BEGLINE)
256 flags &= ~SR_BEGLINE;
257 if(flags & SR_ENDLINE)
258 flags &= ~SR_ENDLINE;
259 else
260 flags |= SR_ENDLINE;
262 break;
264 case (CTRL|'Y'):
265 if(flags & SR_ORIGMEN){
266 gotobob(0, 1);
267 mlerase();
268 FWS_RETURN(TRUE);
271 case (CTRL|'^'):
272 if (flags & SR_ORIGMEN){
273 flags &= ~SR_ORIGMEN;
274 flags |= SR_OPTNMEN;
275 } else {
276 flags &= ~SR_OPTNMEN;
277 flags |= SR_ORIGMEN;
279 break;
281 case (CTRL|'X'):
282 if(flags & SR_OPTNMEN){
283 if (flags & SR_NOEXACT){
284 flags &= ~SR_NOEXACT;
285 flags |= SR_EXACTSR;
286 } else {
287 flags &= ~SR_EXACTSR;
288 flags |= SR_NOEXACT;
290 if((curwp->w_bufp->b_mode & MDEXACT) == 0)
291 curwp->w_bufp->b_mode |= MDEXACT;
292 else
293 curwp->w_bufp->b_mode &= ~MDEXACT;
295 break;
297 case (CTRL|'T') :
298 if(flags & SR_ORIGMEN){
299 switch(status = readnumpat(_("Search to Line Number : "))){
300 case -1 :
301 emlwrite(_("Search to Line Number Cancelled"), NULL);
302 FWS_RETURN(FALSE);
304 case 0 :
305 emlwrite(_("Line number must be greater than zero"), NULL);
306 FWS_RETURN(FALSE);
308 case -2 :
309 emlwrite(_("Line number must contain only digits"), NULL);
310 FWS_RETURN(FALSE);
312 case -3 :
313 continue;
315 default :
316 gotoline(0, status);
317 mlerase();
318 FWS_RETURN(TRUE);
321 break;
323 case (CTRL|'W'):
324 if(flags & SR_ORIGMEN){
325 LINE *linep = curwp->w_dotp;
326 int offset = curwp->w_doto;
328 gotobop(0, 1);
329 gotobol(0, 1);
332 * if we're asked to backup and we're already
335 if((lastflag & CFSRCH)
336 && linep == curwp->w_dotp
337 && offset == curwp->w_doto
338 && !(offset == 0 && lback(linep) == curbp->b_linep)){
339 backchar(0, 1);
340 gotobop(0, 1);
341 gotobol(0, 1);
343 mlerase();
344 FWS_RETURN(TRUE);
346 break;
348 case (CTRL|'O'):
349 if(flags & SR_ORIGMEN){
350 if(curwp->w_dotp != curbp->b_linep){
351 gotoeop(0, 1);
352 forwchar(0, 1);
354 mlerase();
355 FWS_RETURN(TRUE);
357 break;
359 case (CTRL|'U'):
360 if(flags & SR_ORIGMEN){
361 fillbuf(0, 1);
362 mlerase();
363 FWS_RETURN(TRUE);
365 break;
367 case (CTRL|'R'): /* toggle replacement option */
368 repl_mode = !repl_mode;
369 break;
371 default:
372 if(status == ABORT)
373 emlwrite(_("Search Cancelled"), NULL);
374 else
375 mlerase();
377 FWS_RETURN(FALSE);
380 /* replace option is disabled */
381 if (!(gmode & MDREPLACE)){
382 ucs4_strncpy(defpat, pat, NPAT);
383 defpat[NPAT-1] = '\0';
384 break;
386 else if (search){ /* search now */
387 ucs4_strncpy(pat, defpat, NPAT); /* remember this search for the future */
388 pat[NPAT-1] = '\0';
389 break;
393 reverse_all(defpat, flags & SR_BACKWRD);
396 * This code is kind of dumb. What I want is successive C-W 's to
397 * move dot to successive occurences of the pattern. So, if dot is
398 * already sitting at the beginning of the pattern, then we'll move
399 * forward a char before beginning the search. We'll let the
400 * automatic wrapping handle putting the dot back in the right
401 * place...
403 status = 0; /* using "status" as int temporarily! */
404 while(1){
405 if(defpat[status] == '\0'){
406 forwchar(0, 1);
407 break;
410 if(status + curwp->w_doto >= llength(curwp->w_dotp) ||
411 !eq(defpat[status],lgetc(curwp->w_dotp, curwp->w_doto + status).c))
412 break;
413 status++;
416 /* search for the pattern */
418 while (n-- > 0) {
419 if((status = forscan(&wrapt,defpat, flags, NULL,0,PTBEG)) == FALSE)
420 break;
423 /* and complain if not there */
424 if (status == FALSE){
425 char *utf8;
426 UCS x[1];
428 x[0] = '\0';
430 utf8 = sucs4_to_utf8_cpystr(defpat ? defpat : x, flags & SR_BACKWRD);
431 /* TRANSLATORS: reporting the result of a failed search */
432 eml.s = utf8;
433 emlwrite(_("\"%s\" not found"), &eml);
434 if(utf8)
435 fs_give((void **) &utf8);
437 else if((gmode & MDREPLACE) && repl_mode == TRUE){
438 status = replace_pat(defpat, &wrapt2, flags & SR_BACKWRD); /* replace pattern */
439 if (wrapt == TRUE || wrapt2 == TRUE){
440 eml.s = (status == ABORT) ? "cancelled but wrapped" : "Wrapped";
441 emlwrite("Replacement %s", &eml);
444 else if(wrapt == TRUE){
445 emlwrite("Search Wrapped", NULL);
447 else if(status == TRUE){
448 emlwrite("", NULL);
451 reverse_all(defpat, flags & SR_BACKWRD);
452 if(curwp->w_doto == -1){
453 curwp->w_doto = 0;
454 curwp->w_flag |= WFMOVE;
456 FWS_RETURN(status);
460 /* Replace a pattern with the pattern the user types in one or more times. */
462 replace_pat(UCS *defpat, int *wrapt, int bsearch)
464 register int status;
465 UCS lpat[NPAT], origpat[NPAT]; /* case sensitive pattern */
466 EXTRAKEYS menu_pat[12];
467 int repl_all = FALSE;
468 UCS *b;
469 char utf8tmp[NPMT];
470 UCS prompt[NPMT];
471 UCS *promptp;
472 int i, flags;
474 if(bsearch){
475 flags = SR_BACKWRD;
476 curwp->w_doto -= ucs4_strlen(defpat) - 1;
478 else flags = SR_FORWARD;
480 forscan(wrapt, defpat, flags, NULL, 0, PTBEG); /* go to word to be replaced */
482 lpat[0] = '\0';
483 memset((void *)&menu_pat, 0, sizeof(menu_pat));
484 /* additional 'replace all' menu option */
485 menu_pat[0].name = "^X";
486 menu_pat[0].key = (CTRL|'X');
487 menu_pat[0].label = N_("Repl All");
488 KS_OSDATASET(&menu_pat[0], KS_NONE);
490 while(1) {
492 /* we need to reverse the buffer back to its original state, so that
493 * the user will not see that we reversed it under them. The cursor
494 * is at the beginning of the reverse string, that is at the end
495 * of the string. Move it back to the beginning.
498 reverse_all(defpat, bsearch); /* reverse for normal view */
499 update();
500 (*term.t_rev)(1);
501 get_pat_cases(origpat, defpat);
502 pputs(origpat, 1); /* highlight word */
503 (*term.t_rev)(0);
505 snprintf(utf8tmp, NPMT, "Replace%s \"", repl_all ? " every" : "");
506 b = utf8_to_ucs4_cpystr(utf8tmp);
507 if(b){
508 ucs4_strncpy(prompt, b, NPMT);
509 prompt[NPMT-1] = '\0';
510 fs_give((void **) &b);
513 promptp = &prompt[ucs4_strlen(prompt)];
515 expandp(defpat, promptp, NPMT-(promptp-prompt));
516 prompt[NPMT-1] = '\0';
517 promptp += ucs4_strlen(promptp);
519 b = utf8_to_ucs4_cpystr("\" with");
520 if(b){
521 ucs4_strncpy(promptp, b, NPMT-(promptp-prompt));
522 promptp += ucs4_strlen(promptp);
523 prompt[NPMT-1] = '\0';
524 fs_give((void **) &b);
527 if(rpat[0] != '\0'){
528 if((promptp-prompt) < NPMT-2){
529 *promptp++ = ' ';
530 *promptp++ = '[';
531 *promptp = '\0';
534 expandp(rpat, promptp, NPMT-(promptp-prompt));
535 prompt[NPMT-1] = '\0';
536 promptp += ucs4_strlen(promptp);
538 if((promptp-prompt) < NPMT-1){
539 *promptp++ = ']';
540 *promptp = '\0';
544 if((promptp-prompt) < NPMT-3){
545 *promptp++ = ' ';
546 *promptp++ = ':';
547 *promptp++ = ' ';
548 *promptp = '\0';
551 prompt[NPMT-1] = '\0';
553 status = mlreplyd(prompt, lpat, NPAT, QDEFLT, menu_pat);
555 curwp->w_flag |= WFMOVE;
557 reverse_all(defpat, bsearch); /* reverse for internal use */
559 switch(status){
561 case TRUE :
562 case FALSE :
563 if(lpat[0]){
564 ucs4_strncpy(rpat, lpat, NPAT); /* remember default */
565 rpat[NPAT-1] = '\0';
567 else{
568 ucs4_strncpy(lpat, rpat, NPAT); /* use default */
569 lpat[NPAT-1] = '\0';
572 if (repl_all){
573 status = replace_all(defpat, lpat, bsearch);
575 else{
576 if(bsearch)
577 curwp->w_doto -= ucs4_strlen(defpat) - 1;
578 chword(defpat, lpat, bsearch); /* replace word */
579 /* after substitution the cursor is past the end of the
580 * replaced string, so we backdown in backward search,
581 * to make it appear at the beginning of the replaced string.
582 * We make this choice in case the next search is backward,
583 * because if we put the cursor at the end, the next backward
584 * search might hit the substituted string, and we want to
585 * avoid that, because we probably do not want to substitute
586 * the new string, but old text.
588 if(bsearch && (lback(curwp->w_dotp) != curbp->b_linep
589 || curwp->w_doto > 0))
590 curwp->w_doto--;
591 supdate(defpat, bsearch);
592 status = TRUE;
595 if(status == TRUE)
596 emlwrite("", NULL);
598 return(status);
600 case HELPCH: /* help requested */
601 if(Pmaster){
602 VARS_TO_SAVE *saved_state;
604 saved_state = save_pico_state();
605 (*Pmaster->helper)(Pmaster->search_help,
606 _("Help for Searching"), 1);
607 if(saved_state){
608 restore_pico_state(saved_state);
609 free_pico_state(saved_state);
612 else
613 pico_help(SearchHelpText, _("Help for Searching"), 1);
615 case (CTRL|'L'): /* redraw requested */
616 pico_refresh(FALSE, 1);
617 update();
618 break;
620 case (CTRL|'X'): /* toggle replace all option */
621 if (repl_all){
622 repl_all = FALSE;
623 /* TRANSLATORS: abbreviation for Replace All occurences */
624 menu_pat[0].label = N_("Repl All");
626 else{
627 repl_all = TRUE;
628 /* TRANSLATORS: Replace just one occurence */
629 menu_pat[0].label = N_("Repl One");
632 break;
634 default:
635 if(status == ABORT){
636 emlwrite(_("Replacement Cancelled"), NULL);
637 reverse_all(defpat, bsearch); /* undo reverse buffer and pattern */
638 pico_refresh(FALSE, 1);
639 reverse_all(defpat, bsearch); /* reverse buffer and pattern */
641 else{
642 mlerase();
643 chword(defpat, origpat, bsearch);
646 supdate(defpat, bsearch);
647 return(FALSE);
653 /* Since the search is not case sensitive, we must obtain the actual pattern
654 that appears in the text, so that we can highlight (and unhighlight) it
655 without using the wrong cases */
656 void
657 get_pat_cases(UCS *realpat, UCS *searchpat)
659 int i, searchpatlen, curoff;
661 curoff = curwp->w_doto;
662 searchpatlen = ucs4_strlen(searchpat);
664 for (i = 0; i < searchpatlen; i++)
665 realpat[i] = lgetc(curwp->w_dotp, curoff++).c;
667 realpat[searchpatlen] = '\0';
671 /* Ask the user about every occurence of orig pattern and replace it with a
672 repl pattern if the response is affirmative. */
674 replace_all(UCS *orig, UCS *repl, int bsearch)
676 register int status = 0;
677 UCS *b;
678 UCS realpat[NPAT];
679 char utf8tmp[NPMT];
680 UCS *promptp;
681 UCS prompt[NPMT];
682 int wrapt, n = 0;
683 LINE *stop_line = curwp->w_dotp;
684 int stop_offset = curwp->w_doto;
685 EML eml;
686 int flags;
688 /* similar to replace_pat. When we come here, if bsearch is set,
689 * the cursor is at the end of the match, so we must bring it back
690 * to the beginning.
692 if(bsearch){
693 flags = SR_BACKWRD;
694 curwp->w_doto -= ucs4_strlen(orig) - 1;
695 curwp->w_flag |= WFMOVE;
697 else
698 flags = SR_FORWARD;
700 stop_offset = curwp->w_doto;
701 while (1)
702 if (forscan(&wrapt, orig, flags, stop_line, stop_offset, PTBEG)){
703 curwp->w_flag |= WFMOVE; /* put cursor back */
705 reverse_all(orig, bsearch); /* undo reverse buffer and pattern */
706 update();
707 (*term.t_rev)(1);
708 get_pat_cases(realpat, orig);
709 pputs(realpat, 1); /* highlight word */
710 (*term.t_rev)(0);
711 fflush(stdout);
713 snprintf(utf8tmp, NPMT, "Replace \"");
714 b = utf8_to_ucs4_cpystr(utf8tmp);
715 if(b){
716 ucs4_strncpy(prompt, b, NPMT);
717 prompt[NPMT-1] = '\0';
718 fs_give((void **) &b);
721 promptp = &prompt[ucs4_strlen(prompt)];
723 expandp(orig, promptp, NPMT-(promptp-prompt));
724 reverse_all(orig, bsearch); /* reverse for internal use */
725 prompt[NPMT-1] = '\0';
726 promptp += ucs4_strlen(promptp);
728 b = utf8_to_ucs4_cpystr("\" with \"");
729 if(b){
730 ucs4_strncpy(promptp, b, NPMT-(promptp-prompt));
731 promptp += ucs4_strlen(promptp);
732 prompt[NPMT-1] = '\0';
733 fs_give((void **) &b);
736 expandp(repl, promptp, NPMT-(promptp-prompt));
737 prompt[NPMT-1] = '\0';
738 promptp += ucs4_strlen(promptp);
740 if((promptp-prompt) < NPMT-1){
741 *promptp++ = '\"';
742 *promptp = '\0';
745 prompt[NPMT-1] = '\0';
747 status = mlyesno(prompt, TRUE); /* ask user */
749 if(bsearch){
750 curwp->w_doto -= ucs4_strlen(realpat) - 1;
751 curwp->w_flag |= WFMOVE;
753 if (status == TRUE){
754 n++;
755 chword(realpat, repl, bsearch); /* replace word */
756 supdate(realpat, bsearch);
757 }else{
758 chword(realpat, realpat, bsearch); /* replace word by itself */
759 supdate(realpat, bsearch);
760 if(status == ABORT){ /* if cancelled return */
761 eml.s = comatose(n);
762 emlwrite("Replace All cancelled after %s changes", &eml);
763 return (ABORT); /* ... else keep looking */
767 else{
768 char *utf8;
770 utf8 = sucs4_to_utf8_cpystr(orig, bsearch);
771 if(utf8){
772 eml.s = utf8;
773 emlwrite(_("No more matches for \"%s\""), &eml);
774 fs_give((void **) &utf8);
776 else
777 emlwrite(_("No more matches"), NULL);
779 return (FALSE);
784 /* Read a replacement pattern. Modeled after readpattern(). */
786 srpat(char *utf8prompt, UCS *defpat, size_t defpatlen, int repl_mode, int flags)
788 register int s;
789 int i = 0;
790 int toggle, bsearch, bol, eol, exact;
791 UCS *b;
792 UCS prompt[NPMT];
793 UCS *promptp;
794 EXTRAKEYS menu_pat[12];
796 bsearch = flags & SR_BACKWRD;
797 bol = flags & SR_BEGLINE;
798 eol = flags & SR_ENDLINE;
799 exact = flags & SR_EXACTSR;
800 toggle = 0; /* reserved for future use */
802 memset(&menu_pat, 0, sizeof(menu_pat));
803 /* add exceptions here based on the location of the items in the menu */
804 for(i = 0; i < 10; i++){
805 if(flags & SR_ORIGMEN){
806 menu_pat[i].name = menu_srchpat[10*toggle + i].name;
807 menu_pat[i].label = menu_srchpat[10*toggle + i].label;
808 menu_pat[i].key = menu_srchpat[10*toggle + i].key;
809 if(toggle == 0){
810 if (i == REPLACE_KEY)
811 menu_pat[i].label = repl_mode ? N_("No Replace")
812 : N_("Replace");
814 } else if(flags & SR_OPTNMEN){
815 menu_pat[i].name = menu_srchopt[i].name;
816 menu_pat[i].label = menu_srchopt[i].label;
817 menu_pat[i].key = menu_srchopt[i].key;
818 switch(i){
819 case EXACTSR_KEY:
820 menu_pat[i].label = exact ? N_("No Exact")
821 : N_("Exact");
822 break;
823 case REPLACE_KEY:
824 menu_pat[i].label = repl_mode ? N_("No Replace")
825 : N_("Replace");
826 break;
827 case BSEARCH_KEY:
828 menu_pat[i].label = bsearch ? N_("Srch Fwd")
829 : N_("Srch Back");
830 break;
831 case BGNLINE_KEY:
832 menu_pat[i].label = bol ? N_("Anywhere")
833 : N_("Bgn Line");
834 break;
835 case ENDLINE_KEY:
836 menu_pat[i].label = eol ? N_("Anywhere")
837 : N_("End Line");
838 break;
839 default : break;
841 if(menu_pat[i].name)
842 KS_OSDATASET(&menu_pat[i], KS_NONE);
845 b = utf8_to_ucs4_cpystr(utf8prompt);
846 if(b){
847 ucs4_strncpy(prompt, b, NPMT);
848 prompt[NPMT-1] = '\0';
849 if(bsearch){
850 fs_give((void **) &b);
851 b = utf8_to_ucs4_cpystr(N_(" backward"));
852 if(b) ucs4_strncat(prompt, b, ucs4_strlen(b));
853 prompt[NPMT-1] = '\0';
855 if(bol){
856 fs_give((void **) &b);
857 b = utf8_to_ucs4_cpystr(N_(" at start of line"));
858 if(b) ucs4_strncat(prompt, b, ucs4_strlen(b));
859 prompt[NPMT-1] = '\0';
860 } else if(eol){
861 fs_give((void **) &b);
862 b = utf8_to_ucs4_cpystr(N_(" at end of line"));
863 if(b) ucs4_strncat(prompt, b, ucs4_strlen(b));
864 prompt[NPMT-1] = '\0';
866 if(exact){
867 fs_give((void **) &b);
868 b = utf8_to_ucs4_cpystr(N_(" exactly for"));
869 if(b) ucs4_strncat(prompt, b, ucs4_strlen(b));
870 prompt[NPMT-1] = '\0';
872 if(b) fs_give((void **) &b);
875 promptp = &prompt[ucs4_strlen(prompt)];
877 if(repl_mode){
878 b = utf8_to_ucs4_cpystr(" (to replace)");
879 if(b){
880 ucs4_strncpy(promptp, b, NPMT-(promptp-prompt));
881 promptp += ucs4_strlen(promptp);
882 prompt[NPMT-1] = '\0';
883 fs_give((void **) &b);
887 if(pat[0] != '\0'){
888 if((promptp-prompt) < NPMT-2){
889 *promptp++ = ' ';
890 *promptp++ = '[';
891 *promptp = '\0';
894 expandp(pat, promptp, NPMT-(promptp-prompt));
895 prompt[NPMT-1] = '\0';
896 promptp += ucs4_strlen(promptp);
898 if((promptp-prompt) < NPMT-1){
899 *promptp++ = ']';
900 *promptp = '\0';
904 if((promptp-prompt) < NPMT-2){
905 *promptp++ = ':';
906 *promptp++ = ' ';
907 *promptp = '\0';
910 prompt[NPMT-1] = '\0';
912 s = mlreplyd(prompt, defpat, defpatlen, QDEFLT, menu_pat);
914 if (s == TRUE || s == FALSE){ /* changed or not, they're done */
915 if(!defpat[0]){ /* use default */
916 ucs4_strncpy(defpat, pat, defpatlen);
917 defpat[defpatlen-1] = '\0';
919 else if(ucs4_strcmp(pat, defpat)){ /* Specified */
920 ucs4_strncpy(pat, defpat, NPAT);
921 pat[NPAT-1] = '\0';
922 rpat[0] = '\0';
925 s = TRUE; /* let caller know to proceed */
928 return(s);
933 * Read a pattern. Stash it in the external variable "pat". The "pat" is not
934 * updated if the user types in an empty line. If the user typed an empty line,
935 * and there is no old pattern, it is an error. Display the old pattern, in the
936 * style of Jeff Lomicka. There is some do-it-yourself control expansion.
937 * change to using <ESC> to delemit the end-of-pattern to allow <NL>s in
938 * the search string.
942 readnumpat(char *utf8prompt)
944 int i, n;
945 char numpat[NPMT];
946 EXTRAKEYS menu_pat[12];
948 memset(&menu_pat, 0, 10*sizeof(EXTRAKEYS));
949 menu_pat[i = 0].name = "^T";
950 menu_pat[i].label = N_("No Line Number");
951 menu_pat[i].key = (CTRL|'T');
952 KS_OSDATASET(&menu_pat[i++], KS_NONE);
954 menu_pat[i].name = NULL;
956 numpat[0] = '\0';
957 while(1)
958 switch(mlreplyd_utf8(utf8prompt, numpat, NPMT, QNORML, menu_pat)){
959 case TRUE :
960 if(*numpat){
961 for(i = n = 0; numpat[i]; i++)
962 if(strchr("0123456789", numpat[i])){
963 n = (n * 10) + (numpat[i] - '0');
965 else
966 return(-2);
968 return(n);
971 case FALSE :
972 default :
973 return(-1);
975 case (CTRL|'T') :
976 return(-3);
978 case (CTRL|'L') :
979 case HELPCH :
980 break;
986 readpattern(char *utf8prompt, int text_mode, int flags)
988 register int s;
989 int i;
990 int toggle, bsearch, bol, eol, exact;
991 UCS *b;
992 UCS tpat[NPAT+20];
993 UCS *tpatp;
994 EXTRAKEYS menu_pat[12];
996 bsearch = flags & SR_BACKWRD;
997 bol = flags & SR_BEGLINE;
998 eol = flags & SR_ENDLINE;
999 exact = flags & SR_EXACTSR;
1000 toggle = 0; /* reserved for future use */
1002 memset(&menu_pat, 0, sizeof(menu_pat));
1003 /* add exceptions here based on the location of the items in the menu */
1004 for(i = 0; i < 10; i++){
1005 if(flags & SR_ORIGMEN){
1006 menu_pat[i].name = menu_srchpat[10*toggle + i].name;
1007 menu_pat[i].label = menu_srchpat[10*toggle + i].label;
1008 menu_pat[i].key = menu_srchpat[10*toggle + i].key;
1009 if(toggle == 0){
1010 if (i == REPLACE_KEY)
1011 memset(&menu_pat[i], 0, sizeof(EXTRAKEYS));
1012 if (i > REPLACE_KEY && !text_mode)
1013 memset(&menu_pat[i], 0, sizeof(EXTRAKEYS));
1015 } else if(flags & SR_OPTNMEN){
1016 menu_pat[i].name = menu_srchopt[i].name;
1017 menu_pat[i].label = menu_srchopt[i].label;
1018 menu_pat[i].key = menu_srchopt[i].key;
1019 switch(i){
1020 case EXACTSR_KEY:
1021 menu_pat[i].label = exact ? N_("No Exact")
1022 : N_("Exact");
1023 break;
1024 case BSEARCH_KEY:
1025 menu_pat[i].label = bsearch ? N_("Srch Fwd")
1026 : N_("Srch Back");
1027 break;
1028 case BGNLINE_KEY:
1029 menu_pat[i].label = bol ? N_("Anywhere")
1030 : N_("Bgn Line");
1031 break;
1032 case ENDLINE_KEY:
1033 menu_pat[i].label = eol ? N_("Anywhere")
1034 : N_("End Line");
1035 break;
1036 default : break;
1038 if(menu_pat[i].name)
1039 KS_OSDATASET(&menu_pat[i], KS_NONE);
1043 b = utf8_to_ucs4_cpystr(utf8prompt);
1044 if(b){
1045 ucs4_strncpy(tpat, b, NPAT+20);
1046 tpat[NPAT+20-1] = '\0';
1047 if(bsearch){
1048 fs_give((void **) &b);
1049 b = utf8_to_ucs4_cpystr(N_(" backward"));
1050 if(b) ucs4_strncat(tpat, b, ucs4_strlen(b));
1051 tpat[NPAT+20-1] = '\0';
1053 if(bol){
1054 fs_give((void **) &b);
1055 b = utf8_to_ucs4_cpystr(N_(" at start of line"));
1056 if(b) ucs4_strncat(tpat, b, ucs4_strlen(b));
1057 tpat[NPAT+20-1] = '\0';
1058 } else if (eol){
1059 fs_give((void **) &b);
1060 b = utf8_to_ucs4_cpystr(N_(" at end of line"));
1061 if(b) ucs4_strncat(tpat, b, ucs4_strlen(b));
1062 tpat[NPAT+20-1] = '\0';
1064 if (exact){
1065 fs_give((void **) &b);
1066 b = utf8_to_ucs4_cpystr(N_(" exactly for"));
1067 if(b) ucs4_strncat(tpat, b, ucs4_strlen(b));
1068 tpat[NPAT+20-1] = '\0';
1070 if(b) fs_give((void **) &b);
1073 tpatp = &tpat[ucs4_strlen(tpat)];
1075 if(pat[0] != '\0'){
1076 if((tpatp-tpat) < NPAT+20-2){
1077 *tpatp++ = ' ';
1078 *tpatp++ = '[';
1079 *tpatp = '\0';
1082 expandp(pat, tpatp, NPAT+20-(tpatp-tpat));
1083 tpat[NPAT+20-1] = '\0';
1084 tpatp += ucs4_strlen(tpatp);
1086 if((tpatp-tpat) < NPAT+20-1){
1087 *tpatp++ = ']';
1088 *tpatp = '\0';
1092 if((tpatp-tpat) < NPAT+20-3){
1093 *tpatp++ = ' ';
1094 *tpatp++ = ':';
1095 *tpatp++ = ' ';
1096 *tpatp = '\0';
1099 tpat[NPAT+20-1] = '\0';
1101 s = mlreplyd(tpat, tpat, NPAT, QNORML, menu_pat);
1103 if ((s == TRUE) && ucs4_strcmp(pat,tpat)){ /* Specified */
1104 ucs4_strncpy(pat, tpat, NPAT);
1105 pat[NPAT-1] = '\0';
1106 rpat[0] = '\0';
1108 else if (s == FALSE && pat[0] != '\0') /* CR, but old one */
1109 s = TRUE;
1111 return(s);
1114 /* given a line, reverse its content */
1115 void reverse_line(LINE *l)
1117 int i, j, a;
1118 UCS u;
1120 if(l == NULL) return;
1121 j = llength(l) - 1;
1122 for(i = 0; i < j; i++, j--){
1123 u = lgetc(l, j).c; /* reverse the text */
1124 lgetc(l, j).c = lgetc(l, i).c;
1125 lgetc(l, i).c = u;
1126 a = lgetc(l, j).a; /* and the attribute */
1127 lgetc(l, j).a = lgetc(l, i).a;
1128 lgetc(l, i).a = a;
1132 void
1133 reverse_all(UCS *pat, int bsearch)
1135 if(bsearch != 0){
1136 reverse_buffer();
1137 reverse_ucs4(pat);
1141 void
1142 reverse_buffer(void)
1144 LINE *l;
1146 for(l = lforw(curbp->b_linep); l != curbp->b_linep; l = lforw(l))
1147 reverse_line(l);
1149 /* reverse links in buffer */
1150 l = curbp->b_linep;
1151 do {
1152 lforw(l) = lback(l);
1153 l = lforw(l);
1154 } while(l != curbp->b_linep);
1156 l = curbp->b_linep;
1157 do {
1158 lback(lforw(l)) = l;
1159 l = lforw(l);
1160 } while (l != curbp->b_linep);
1162 curwp->w_doto = llength(curwp->w_dotp) - 1 - curwp->w_doto;
1167 /* given a UCS4 string reverse its content */
1168 void reverse_ucs4(UCS *s)
1170 int i, j;
1171 UCS u;
1173 j = ucs4_strlen(s) - 1;
1174 for(i = 0; i < j; i++, j--){
1175 u = s[j];
1176 s[j] = s[i];
1177 s[i] = u;
1182 /* search forward for a <patrn>.
1183 * A backward search is a forward search in backward lines with backward
1184 * patterns
1187 forscan(int *wrapt, /* boolean indicating search wrapped */
1188 UCS *patrn, /* string to scan for */
1189 int flags, /* direction and position */
1190 LINE *limitp, /* stop searching if reached */
1191 int limito, /* stop searching if reached */
1192 int leavep) /* place to leave point
1193 PTBEG = begining of match
1194 PTEND = at end of match */
1197 LINE *curline; /* current line during scan */
1198 int curoff; /* position within current line */
1199 LINE *lastline; /* last line position during scan */
1200 int lastoff; /* position within last line */
1201 UCS c; /* character at current position */
1202 LINE *matchline; /* current line during matching */
1203 int matchoff; /* position in matching line */
1204 UCS *patptr; /* pointer into pattern */
1205 int stopoff; /* offset to stop search */
1206 LINE *stopline; /* line to stop search */
1207 int ftest; /* position of first character of test */
1208 int bsearch;
1209 int bol;
1210 int eol;
1212 bsearch = flags & SR_BACKWRD;
1213 bol = flags & SR_BEGLINE;
1214 eol = flags & SR_ENDLINE;
1215 *wrapt = FALSE;
1217 /* if bsearch is set we return the match at the beginning of the
1218 * matching string, so we make this algorithm return the end of
1219 * the string, so that when we reverse we be at the beginning.
1221 if(bsearch)
1222 leavep = leavep == PTBEG ? PTEND : PTBEG;
1225 * the idea is to set the character to end the search at the
1226 * next character in the buffer. thus, let the search wrap
1227 * completely around the buffer.
1229 * first, test to see if we are at the end of the line,
1230 * otherwise start searching on the next character.
1232 if(curwp->w_doto == llength(curwp->w_dotp)){
1234 * dot is not on end of a line
1235 * start at 0 offset of the next line
1237 stopoff = curoff = 0;
1238 stopline = curline = lforw(curwp->w_dotp);
1239 if (curwp->w_dotp == curbp->b_linep)
1240 *wrapt = TRUE;
1242 else{
1243 stopoff = curoff = curwp->w_doto;
1244 stopline = curline = curwp->w_dotp;
1247 /* scan each character until we hit the head link record */
1250 * maybe wrapping is a good idea
1252 while (curline){
1254 if(curline == curbp->b_linep)
1255 *wrapt = TRUE;
1257 /* save the current position in case we need to
1258 restore it on a match */
1260 lastline = curline;
1261 lastoff = curoff;
1263 /* get the current character resolving EOLs */
1264 if (curoff == llength(curline)) { /* if at EOL */
1265 curline = lforw(curline); /* skip to next line */
1266 curoff = 0;
1267 c = '\n'; /* and return a <NL> */
1269 else if(curoff == -1){
1270 stopoff = curoff = 0;
1271 continue;
1272 c = '\n';
1274 else
1275 c = lgetc(curline, curoff++).c; /* get the char */
1277 if(bol)
1278 ftest = bsearch == 0 ? 1 : llength(curline) - ucs4_strlen(patrn) + 1;
1279 else if (eol)
1280 ftest = bsearch == 0 ? llength(curline) - ucs4_strlen(patrn) + 1 : 1;
1281 /* test it against first char in pattern */
1282 if (eq(c, patrn[0]) != FALSE /* if we find it..*/
1283 && ((bol == 0 && eol == 0) /* ...and if it is anywhere */
1284 || (bol != 0 /* ...or is at the begin or line */
1285 && ((bsearch == 0 && curoff == ftest) /* and search forward and found at beginning of line */
1286 || (bsearch != 0 && curoff == ftest))) /* or search backward and found at end of line */
1287 || (eol != 0 /* ...or is at the end or line */
1288 && ((bsearch == 0 && curoff == ftest) /* and search forward and found at beginning of line */
1289 || (bsearch != 0 && curoff == ftest))) /* or search backward and found at end of line */
1291 /* setup match pointers */
1292 matchline = curline;
1293 matchoff = curoff;
1294 patptr = &patrn[0];
1296 /* scan through patrn for a match */
1297 while (*++patptr != '\0') {
1298 /* advance all the pointers */
1299 if (matchoff >= llength(matchline)) {
1300 /* advance past EOL */
1301 matchline = lforw(matchline);
1302 matchoff = 0;
1303 c = '\n';
1304 } else
1305 c = lgetc(matchline, matchoff++).c;
1307 if(matchline == limitp && matchoff == limito)
1308 return(FALSE);
1310 /* and test it against the pattern */
1311 if (eq(*patptr, c) == FALSE)
1312 goto fail;
1315 /* A SUCCESSFULL MATCH!!! */
1316 /* reset the global "." pointers */
1317 if (leavep == PTEND) { /* at end of string */
1318 curwp->w_dotp = matchline;
1319 curwp->w_doto = matchoff - 1;
1321 else { /* at begining of string */
1322 curwp->w_dotp = lastline;
1323 curwp->w_doto = lastoff;
1326 curwp->w_flag |= WFMOVE; /* flag that we have moved */
1327 return(TRUE);
1330 fail:; /* continue to search */
1331 if(((curline == stopline) && (curoff == stopoff))
1332 || (curline == limitp && curoff == limito))
1333 break; /* searched everywhere... */
1335 /* we could not find a match */
1337 return(FALSE);
1342 /* expandp: expand control key sequences for output */
1344 expandp(UCS *srcstr, /* string to expand */
1345 UCS *deststr, /* destination of expanded string */
1346 int maxlength) /* maximum chars in destination */
1348 UCS c; /* current char to translate */
1350 /* scan through the string */
1351 while ((c = *srcstr++) != 0) {
1352 if (c == '\n') { /* its an EOL */
1353 *deststr++ = '<';
1354 *deststr++ = 'N';
1355 *deststr++ = 'L';
1356 *deststr++ = '>';
1357 maxlength -= 4;
1358 } else if (c < 0x20 || c == 0x7f) { /* control character */
1359 *deststr++ = '^';
1360 *deststr++ = c ^ 0x40;
1361 maxlength -= 2;
1362 } else if (c == '%') {
1363 *deststr++ = '%';
1364 *deststr++ = '%';
1365 maxlength -= 2;
1366 } else { /* any other character */
1367 *deststr++ = c;
1368 maxlength--;
1371 /* check for maxlength */
1372 if (maxlength < 4) {
1373 *deststr++ = '$';
1374 *deststr = '\0';
1375 return(FALSE);
1379 *deststr = '\0';
1380 return(TRUE);
1385 * chword() - change the given word, wp, pointed to by the curwp->w_dot
1386 * pointers to the word in cb
1387 * if bsearch is set, then cb is supposed to come unreversed, while
1388 * the buffer is supposed to be reversed, so we must reverse cb before
1389 * inserting it.
1391 void
1392 chword(UCS *wb, UCS *cb, int bsearch)
1394 UCS *u;
1395 ldelete(ucs4_strlen(wb), NULL); /* not saved in kill buffer */
1396 if(bsearch) reverse_ucs4(cb);
1397 for(u = cb; *u != '\0'; u++)
1398 linsert(1, *u);
1399 if(bsearch) reverse_ucs4(cb);
1401 curwp->w_flag |= WFEDIT;
1404 void
1405 supdate(UCS *pat, int bsearch)
1407 reverse_all(pat, bsearch); /* undo reverse buffer and pattern */
1408 update();
1409 reverse_all(pat, bsearch); /* reverse buffer and pattern */
1412 char *
1413 sucs4_to_utf8_cpystr(UCS *orig, int bsearch)
1415 char *utf8;
1416 if(bsearch) reverse_ucs4(orig);
1417 utf8 = ucs4_to_utf8_cpystr(orig);
1418 if(bsearch) reverse_ucs4(orig);
1419 return utf8;