Add support for tab-completion when selecting by rule
[alpine.git] / pico / search.c
blob7d36beea2d9a1b9f06b1da44992d4b0e1824cb5b
1 /*
2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2008 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
16 * Program: Searching routines
18 * The functions in this file implement commands that search in the forward
19 * and backward directions. There are no special characters in the search
20 * strings. Probably should have a regular expression search, or something
21 * like that.
25 #include "headers.h"
27 int eq(UCS, UCS);
28 int expandp(UCS *, UCS *, int);
29 int readnumpat(char *);
30 void get_pat_cases(UCS *, UCS *);
31 int srpat(char *, UCS *, size_t, int, int);
32 int readpattern(char *, int, int);
33 int replace_pat(UCS *, int *, int);
34 int replace_all(UCS *, UCS *, int);
35 void reverse_line(LINE *);
36 void reverse_buffer(void);
37 void reverse_ucs4(UCS *);
38 void reverse_all(UCS *, int);
39 void supdate(UCS *, int);
40 char *sucs4_to_utf8_cpystr(UCS *, int);
42 #define FWS_RETURN(RV) { \
43 thisflag |= CFSRCH; \
44 curwp->w_flag |= WFMODE; \
45 sgarbk = TRUE; \
46 return(RV); \
49 /* The search menu leaves a number of keys free, some are taken
50 * as subcommands of the search command, and some are taken are
51 * editing commands. This leaves the following keys open:
52 * ^J, ^N, ^O, ^P, ^R, ^T, ^U, ^V, ^W, ^X and ^Y.
53 * Out of these keys, ^J, ^N, ^P and ^X are not defined as commands, however,
54 * in some patches ^N, ^P and ^X are defined. ^N is defined as part of
55 * an editing command, ^P and ^X are defined to delete paragraphs and to
56 * remove text to the end of file, so only ^J is undefined.
59 #define REPLACE_KEY 2 /* the location of the replace key in the array below */
61 EXTRAKEYS menu_srchpat[] = {
62 {"^Y", N_("FirstLine"), (CTRL|'Y')},
63 {"^V", N_("LastLine"), (CTRL|'V')},
64 {"^R", N_("No Replace"), (CTRL|'R')},
65 {"^^", N_("Optns Menu"), (CTRL|'^')}, /* toggle this menu or options menu */
66 {"^T", N_("LineNumber"), (CTRL|'T')},
67 {"^W", N_("Start of Para"), (CTRL|'W')},
68 {"^O", N_("End of Para"), (CTRL|'O')},
69 {"^U", N_("FullJustify"), (CTRL|'U')},
70 {NULL, NULL, 0},
71 {NULL, NULL, 0}
74 #define EXACTSR_KEY 1 /* toggle an exact or approximate search */
75 #define BGNLINE_KEY 3 /* the location of Bgn Line command in the array below */
76 #define ENDLINE_KEY 4 /* the location of End Line command in the array below */
77 #define BSEARCH_KEY 5 /* the location of the bsearch key in the array below */
78 EXTRAKEYS menu_srchopt[] = {
79 {"^^", N_("Orig Menu"), (CTRL|'^')}, /* toggle original or options menu */
80 {"^X", N_("Exact"), (CTRL|'X')}, /* toggle exact vs non exact */
81 {"^R", N_("No Replace"), (CTRL|'R')}, /* toggle replace or not replace */
82 {"^V", N_("Bgn Line"), (CTRL|'V')}, /* toggle Bgn Line or anywhere */
83 {"^N", N_("End Line"), (CTRL|'N')}, /* toggle End Line or anywhere */
84 {"^P", N_("BackSearch"), (CTRL|'P')}, /* toggle Backward or forward */
85 {NULL, NULL, 0},
86 {NULL, NULL, 0},
87 {NULL, NULL, 0},
88 {NULL, NULL, 0}
92 * Search forward. Get a search string from the user, and search, beginning at
93 * ".", for the string. If found, reset the "." to be just after the match
94 * string, and [perhaps] repaint the display. Bound to "C-S".
97 /* string search input parameters */
99 #define PTBEG 1 /* leave the point at the beginning on search */
100 #define PTEND 2 /* leave the point at the end on search */
102 #define NPMT (2*NLINE+32)
105 static char *SearchHelpText[] = {
106 /* TRANSLATORS: Some help text that goes together in a group. */
107 N_("Help for Search Command"),
108 " ",
109 N_(" Enter the words or characters you would like to search"),
110 N_("~ for, then press ~R~e~t~u~r~n. The search then takes place."),
111 N_(" When the characters or words that you entered "),
112 N_(" are found, the buffer will be redisplayed with the cursor "),
113 N_(" at the beginning of the selected text."),
114 " ",
115 N_(" The most recent string for which a search was made is"),
116 N_(" displayed in the \"Search\" prompt between the square"),
117 N_(" brackets. This string is the default search prompt."),
118 N_("~ Hitting only ~R~e~t~u~r~n or at the prompt will cause the"),
119 N_(" search to be made with the default value."),
120 " ",
121 N_(" The text search is not case sensitive, and will examine the"),
122 N_(" entire message."),
123 " ",
124 N_(" Should the search fail, a message will be displayed."),
125 " ",
126 N_("End of Search Help."),
127 " ",
128 NULL
133 * Compare two characters. The "bc" comes from the buffer. It has it's case
134 * folded out. The "pc" is from the pattern.
137 eq(UCS bc, UCS pc)
139 if ((curwp->w_bufp->b_mode & MDEXACT) == 0){
140 if (bc>='a' && bc<='z')
141 bc -= 0x20;
143 if (pc>='a' && pc<='z')
144 pc -= 0x20;
147 return(bc == pc);
152 forwsearch(int f, int n)
154 int status, flags;
155 int wrapt = FALSE, wrapt2 = FALSE;
156 int repl_mode = FALSE;
157 UCS defpat[NPAT];
158 int search = FALSE;
159 EML eml;
162 /* resolve the repeat count */
163 if (n == 0)
164 n = 1;
166 if (n < 1) /* search backwards */
167 FWS_RETURN(0);
169 defpat[0] = '\0';
170 /* defaults: usual menu, search forward, not case sensitive */
172 flags = SR_ORIGMEN | SR_FORWARD;
174 /* exact search is sticky -- that is, once one is done, so will be
175 * the next ones. This is consistent with all all searches being
176 * case insensitive by default.
178 if((curwp->w_bufp->b_mode & MDEXACT) == 0)
179 flags |= SR_NOEXACT;
180 else
181 flags |= SR_EXACTSR;
183 /* ask the user for the text of a pattern */
184 while(1){
186 if (gmode & MDREPLACE)
187 status = srpat("Search", defpat, NPAT, repl_mode, flags);
188 else
189 status = readpattern("Search", TRUE, flags);
191 switch(status){
192 case TRUE: /* user typed something */
193 search = TRUE;
194 break;
196 case HELPCH: /* help requested */
197 if(Pmaster){
198 VARS_TO_SAVE *saved_state;
200 saved_state = save_pico_state();
201 (*Pmaster->helper)(Pmaster->search_help,
202 _("Help for Searching"), 1);
203 if(saved_state){
204 restore_pico_state(saved_state);
205 free_pico_state(saved_state);
208 else
209 pico_help(SearchHelpText, _("Help for Searching"), 1);
211 case (CTRL|'L'): /* redraw requested */
212 pico_refresh(FALSE, 1);
213 update();
214 break;
217 case (CTRL|'P'):
218 if(flags & SR_ORIGMEN){
219 /* Undefined still */
221 if(flags & SR_OPTNMEN){
222 if(flags & SR_FORWARD){
223 flags &= ~SR_FORWARD;
224 flags |= SR_BACKWRD;
225 } else {
226 flags &= ~SR_BACKWRD;
227 flags |= SR_FORWARD;
230 break;
232 case (CTRL|'V'):
233 if(flags & SR_ORIGMEN){
234 gotoeob(0, 1);
235 mlerase();
236 FWS_RETURN(TRUE);
237 } else if (flags & SR_OPTNMEN){
238 if(flags & SR_ENDLINE)
239 flags &= ~SR_ENDLINE;
240 if(flags & SR_BEGLINE)
241 flags &= ~SR_BEGLINE;
242 else
243 flags |= SR_BEGLINE;
245 break;
247 case (CTRL|'N'):
248 if(flags & SR_ORIGMEN){
249 /* undefined still */
250 } else if (flags & SR_OPTNMEN){
251 if(flags & SR_BEGLINE)
252 flags &= ~SR_BEGLINE;
253 if(flags & SR_ENDLINE)
254 flags &= ~SR_ENDLINE;
255 else
256 flags |= SR_ENDLINE;
258 break;
260 case (CTRL|'Y'):
261 if(flags & SR_ORIGMEN){
262 gotobob(0, 1);
263 mlerase();
264 FWS_RETURN(TRUE);
267 case (CTRL|'^'):
268 if (flags & SR_ORIGMEN){
269 flags &= ~SR_ORIGMEN;
270 flags |= SR_OPTNMEN;
271 } else {
272 flags &= ~SR_OPTNMEN;
273 flags |= SR_ORIGMEN;
275 break;
277 case (CTRL|'X'):
278 if(flags & SR_OPTNMEN){
279 if (flags & SR_NOEXACT){
280 flags &= ~SR_NOEXACT;
281 flags |= SR_EXACTSR;
282 } else {
283 flags &= ~SR_EXACTSR;
284 flags |= SR_NOEXACT;
286 if((curwp->w_bufp->b_mode & MDEXACT) == 0)
287 curwp->w_bufp->b_mode |= MDEXACT;
288 else
289 curwp->w_bufp->b_mode &= ~MDEXACT;
291 break;
293 case (CTRL|'T') :
294 if(flags & SR_ORIGMEN){
295 switch(status = readnumpat(_("Search to Line Number : "))){
296 case -1 :
297 emlwrite(_("Search to Line Number Cancelled"), NULL);
298 FWS_RETURN(FALSE);
300 case 0 :
301 emlwrite(_("Line number must be greater than zero"), NULL);
302 FWS_RETURN(FALSE);
304 case -2 :
305 emlwrite(_("Line number must contain only digits"), NULL);
306 FWS_RETURN(FALSE);
308 case -3 :
309 continue;
311 default :
312 gotoline(0, status);
313 mlerase();
314 FWS_RETURN(TRUE);
317 break;
319 case (CTRL|'W'):
320 if(flags & SR_ORIGMEN){
321 LINE *linep = curwp->w_dotp;
322 int offset = curwp->w_doto;
324 gotobop(0, 1);
325 gotobol(0, 1);
328 * if we're asked to backup and we're already
331 if((lastflag & CFSRCH)
332 && linep == curwp->w_dotp
333 && offset == curwp->w_doto
334 && !(offset == 0 && lback(linep) == curbp->b_linep)){
335 backchar(0, 1);
336 gotobop(0, 1);
337 gotobol(0, 1);
339 mlerase();
340 FWS_RETURN(TRUE);
342 break;
344 case (CTRL|'O'):
345 if(flags & SR_ORIGMEN){
346 if(curwp->w_dotp != curbp->b_linep){
347 gotoeop(0, 1);
348 forwchar(0, 1);
350 mlerase();
351 FWS_RETURN(TRUE);
353 break;
355 case (CTRL|'U'):
356 if(flags & SR_ORIGMEN){
357 fillbuf(0, 1);
358 mlerase();
359 FWS_RETURN(TRUE);
361 break;
363 case (CTRL|'R'): /* toggle replacement option */
364 repl_mode = !repl_mode;
365 break;
367 default:
368 if(status == ABORT)
369 emlwrite(_("Search Cancelled"), NULL);
370 else
371 mlerase();
373 FWS_RETURN(FALSE);
376 /* replace option is disabled */
377 if (!(gmode & MDREPLACE)){
378 ucs4_strncpy(defpat, pat, NPAT);
379 defpat[NPAT-1] = '\0';
380 break;
382 else if (search){ /* search now */
383 ucs4_strncpy(pat, defpat, NPAT); /* remember this search for the future */
384 pat[NPAT-1] = '\0';
385 break;
389 reverse_all(defpat, flags & SR_BACKWRD);
392 * This code is kind of dumb. What I want is successive C-W 's to
393 * move dot to successive occurrences of the pattern. So, if dot is
394 * already sitting at the beginning of the pattern, then we'll move
395 * forward a char before beginning the search. We'll let the
396 * automatic wrapping handle putting the dot back in the right
397 * place...
399 status = 0; /* using "status" as int temporarily! */
400 while(1){
401 if(defpat[status] == '\0'){
402 forwchar(0, 1);
403 break;
406 if(status + curwp->w_doto >= llength(curwp->w_dotp) ||
407 !eq(defpat[status],lgetc(curwp->w_dotp, curwp->w_doto + status).c))
408 break;
409 status++;
412 /* search for the pattern */
414 while (n-- > 0) {
415 if((status = forscan(&wrapt,defpat, flags, NULL,0,PTBEG)) == FALSE)
416 break;
419 /* and complain if not there */
420 if (status == FALSE){
421 char *utf8;
422 UCS x[1];
424 x[0] = '\0';
426 utf8 = sucs4_to_utf8_cpystr(defpat ? defpat : x, flags & SR_BACKWRD);
427 /* TRANSLATORS: reporting the result of a failed search */
428 eml.s = utf8;
429 emlwrite(_("\"%s\" not found"), &eml);
430 if(utf8)
431 fs_give((void **) &utf8);
433 else if((gmode & MDREPLACE) && repl_mode == TRUE){
434 status = replace_pat(defpat, &wrapt2, flags & SR_BACKWRD); /* replace pattern */
435 if (wrapt == TRUE || wrapt2 == TRUE){
436 eml.s = (status == ABORT) ? "cancelled but wrapped" : "Wrapped";
437 emlwrite("Replacement %s", &eml);
440 else if(wrapt == TRUE){
441 emlwrite("Search Wrapped", NULL);
443 else if(status == TRUE){
444 emlwrite("", NULL);
447 reverse_all(defpat, flags & SR_BACKWRD);
448 if(curwp->w_doto == -1){
449 curwp->w_doto = 0;
450 curwp->w_flag |= WFMOVE;
452 FWS_RETURN(status);
456 /* Replace a pattern with the pattern the user types in one or more times. */
458 replace_pat(UCS *defpat, int *wrapt, int bsearch)
460 register int status;
461 UCS lpat[NPAT], origpat[NPAT]; /* case sensitive pattern */
462 EXTRAKEYS menu_pat[12];
463 int repl_all = FALSE;
464 UCS *b;
465 char utf8tmp[NPMT];
466 UCS prompt[NPMT];
467 UCS *promptp;
468 int flags;
470 if(bsearch){
471 flags = SR_BACKWRD;
472 curwp->w_doto -= ucs4_strlen(defpat) - 1;
474 else flags = SR_FORWARD;
476 forscan(wrapt, defpat, flags, NULL, 0, PTBEG); /* go to word to be replaced */
478 lpat[0] = '\0';
479 memset((void *)&menu_pat, 0, sizeof(menu_pat));
480 /* additional 'replace all' menu option */
481 menu_pat[0].name = "^X";
482 menu_pat[0].key = (CTRL|'X');
483 menu_pat[0].label = N_("Repl All");
484 KS_OSDATASET(&menu_pat[0], KS_NONE);
486 while(1) {
488 /* we need to reverse the buffer back to its original state, so that
489 * the user will not see that we reversed it under them. The cursor
490 * is at the beginning of the reverse string, that is at the end
491 * of the string. Move it back to the beginning.
494 reverse_all(defpat, bsearch); /* reverse for normal view */
495 update();
496 (*term.t_rev)(1);
497 get_pat_cases(origpat, defpat);
498 pputs(origpat, 1); /* highlight word */
499 (*term.t_rev)(0);
501 snprintf(utf8tmp, NPMT, "Replace%s \"", repl_all ? " every" : "");
502 b = utf8_to_ucs4_cpystr(utf8tmp);
503 if(b){
504 ucs4_strncpy(prompt, b, NPMT);
505 prompt[NPMT-1] = '\0';
506 fs_give((void **) &b);
509 promptp = &prompt[ucs4_strlen(prompt)];
511 expandp(defpat, promptp, NPMT-(promptp-prompt));
512 prompt[NPMT-1] = '\0';
513 promptp += ucs4_strlen(promptp);
515 b = utf8_to_ucs4_cpystr("\" with");
516 if(b){
517 ucs4_strncpy(promptp, b, NPMT-(promptp-prompt));
518 promptp += ucs4_strlen(promptp);
519 prompt[NPMT-1] = '\0';
520 fs_give((void **) &b);
523 if(rpat[0] != '\0'){
524 if((promptp-prompt) < NPMT-2){
525 *promptp++ = ' ';
526 *promptp++ = '[';
527 *promptp = '\0';
530 expandp(rpat, promptp, NPMT-(promptp-prompt));
531 prompt[NPMT-1] = '\0';
532 promptp += ucs4_strlen(promptp);
534 if((promptp-prompt) < NPMT-1){
535 *promptp++ = ']';
536 *promptp = '\0';
540 if((promptp-prompt) < NPMT-3){
541 *promptp++ = ' ';
542 *promptp++ = ':';
543 *promptp++ = ' ';
544 *promptp = '\0';
547 prompt[NPMT-1] = '\0';
549 status = mlreplyd(prompt, lpat, NPAT, QDEFLT, menu_pat);
551 curwp->w_flag |= WFMOVE;
553 reverse_all(defpat, bsearch); /* reverse for internal use */
555 switch(status){
557 case TRUE :
558 case FALSE :
559 if(lpat[0]){
560 ucs4_strncpy(rpat, lpat, NPAT); /* remember default */
561 rpat[NPAT-1] = '\0';
563 else{
564 ucs4_strncpy(lpat, rpat, NPAT); /* use default */
565 lpat[NPAT-1] = '\0';
568 if (repl_all){
569 status = replace_all(defpat, lpat, bsearch);
571 else{
572 if(bsearch)
573 curwp->w_doto -= ucs4_strlen(defpat) - 1;
574 chword(defpat, lpat, bsearch); /* replace word */
575 /* after substitution the cursor is past the end of the
576 * replaced string, so we backdown in backward search,
577 * to make it appear at the beginning of the replaced string.
578 * We make this choice in case the next search is backward,
579 * because if we put the cursor at the end, the next backward
580 * search might hit the substituted string, and we want to
581 * avoid that, because we probably do not want to substitute
582 * the new string, but old text.
584 if(bsearch && (lback(curwp->w_dotp) != curbp->b_linep
585 || curwp->w_doto > 0))
586 curwp->w_doto--;
587 supdate(defpat, bsearch);
588 status = TRUE;
591 if(status == TRUE)
592 emlwrite("", NULL);
594 return(status);
596 case HELPCH: /* help requested */
597 if(Pmaster){
598 VARS_TO_SAVE *saved_state;
600 saved_state = save_pico_state();
601 (*Pmaster->helper)(Pmaster->search_help,
602 _("Help for Searching"), 1);
603 if(saved_state){
604 restore_pico_state(saved_state);
605 free_pico_state(saved_state);
608 else
609 pico_help(SearchHelpText, _("Help for Searching"), 1);
611 case (CTRL|'L'): /* redraw requested */
612 pico_refresh(FALSE, 1);
613 update();
614 break;
616 case (CTRL|'X'): /* toggle replace all option */
617 if (repl_all){
618 repl_all = FALSE;
619 /* TRANSLATORS: abbreviation for Replace All occurrences */
620 menu_pat[0].label = N_("Repl All");
622 else{
623 repl_all = TRUE;
624 /* TRANSLATORS: Replace just one occurence */
625 menu_pat[0].label = N_("Repl One");
628 break;
630 default:
631 if(status == ABORT){
632 emlwrite(_("Replacement Cancelled"), NULL);
633 reverse_all(defpat, bsearch); /* undo reverse buffer and pattern */
634 pico_refresh(FALSE, 1);
635 reverse_all(defpat, bsearch); /* reverse buffer and pattern */
637 else{
638 mlerase();
639 chword(defpat, origpat, bsearch);
642 supdate(defpat, bsearch);
643 return(FALSE);
649 /* Since the search is not case sensitive, we must obtain the actual pattern
650 that appears in the text, so that we can highlight (and unhighlight) it
651 without using the wrong cases */
652 void
653 get_pat_cases(UCS *realpat, UCS *searchpat)
655 int i, searchpatlen, curoff;
657 curoff = curwp->w_doto;
658 searchpatlen = ucs4_strlen(searchpat);
660 for (i = 0; i < searchpatlen; i++)
661 realpat[i] = lgetc(curwp->w_dotp, curoff++).c;
663 realpat[searchpatlen] = '\0';
667 /* Ask the user about every occurence of orig pattern and replace it with a
668 repl pattern if the response is affirmative. */
670 replace_all(UCS *orig, UCS *repl, int bsearch)
672 register int status = 0;
673 UCS *b;
674 UCS realpat[NPAT];
675 char utf8tmp[NPMT];
676 UCS *promptp;
677 UCS prompt[NPMT];
678 int wrapt, n = 0;
679 LINE *stop_line = curwp->w_dotp;
680 int stop_offset = curwp->w_doto;
681 EML eml;
682 int flags;
684 /* similar to replace_pat. When we come here, if bsearch is set,
685 * the cursor is at the end of the match, so we must bring it back
686 * to the beginning.
688 if(bsearch){
689 flags = SR_BACKWRD;
690 curwp->w_doto -= ucs4_strlen(orig) - 1;
691 curwp->w_flag |= WFMOVE;
693 else
694 flags = SR_FORWARD;
696 stop_offset = curwp->w_doto;
697 while (1)
698 if (forscan(&wrapt, orig, flags, stop_line, stop_offset, PTBEG)){
699 curwp->w_flag |= WFMOVE; /* put cursor back */
701 reverse_all(orig, bsearch); /* undo reverse buffer and pattern */
702 update();
703 (*term.t_rev)(1);
704 get_pat_cases(realpat, orig);
705 pputs(realpat, 1); /* highlight word */
706 (*term.t_rev)(0);
707 fflush(stdout);
709 snprintf(utf8tmp, NPMT, "Replace \"");
710 b = utf8_to_ucs4_cpystr(utf8tmp);
711 if(b){
712 ucs4_strncpy(prompt, b, NPMT);
713 prompt[NPMT-1] = '\0';
714 fs_give((void **) &b);
717 promptp = &prompt[ucs4_strlen(prompt)];
719 expandp(orig, promptp, NPMT-(promptp-prompt));
720 reverse_all(orig, bsearch); /* reverse for internal use */
721 prompt[NPMT-1] = '\0';
722 promptp += ucs4_strlen(promptp);
724 b = utf8_to_ucs4_cpystr("\" with \"");
725 if(b){
726 ucs4_strncpy(promptp, b, NPMT-(promptp-prompt));
727 promptp += ucs4_strlen(promptp);
728 prompt[NPMT-1] = '\0';
729 fs_give((void **) &b);
732 expandp(repl, promptp, NPMT-(promptp-prompt));
733 prompt[NPMT-1] = '\0';
734 promptp += ucs4_strlen(promptp);
736 if((promptp-prompt) < NPMT-1){
737 *promptp++ = '\"';
738 *promptp = '\0';
741 prompt[NPMT-1] = '\0';
743 status = mlyesno(prompt, TRUE); /* ask user */
745 if(bsearch){
746 curwp->w_doto -= ucs4_strlen(realpat) - 1;
747 curwp->w_flag |= WFMOVE;
749 if (status == TRUE){
750 n++;
751 chword(realpat, repl, bsearch); /* replace word */
752 supdate(realpat, bsearch);
753 }else{
754 chword(realpat, realpat, bsearch); /* replace word by itself */
755 supdate(realpat, bsearch);
756 if(status == ABORT){ /* if cancelled return */
757 eml.s = comatose(n);
758 emlwrite("Replace All cancelled after %s changes", &eml);
759 return (ABORT); /* ... else keep looking */
763 else{
764 char *utf8;
766 utf8 = sucs4_to_utf8_cpystr(orig, bsearch);
767 if(utf8){
768 eml.s = utf8;
769 emlwrite(_("No more matches for \"%s\""), &eml);
770 fs_give((void **) &utf8);
772 else
773 emlwrite(_("No more matches"), NULL);
775 return (FALSE);
780 /* Read a replacement pattern. Modeled after readpattern(). */
782 srpat(char *utf8prompt, UCS *defpat, size_t defpatlen, int repl_mode, int flags)
784 register int s;
785 int i = 0;
786 int toggle, bsearch, bol, eol, exact;
787 UCS *b;
788 UCS prompt[NPMT];
789 UCS *promptp;
790 EXTRAKEYS menu_pat[12];
792 bsearch = flags & SR_BACKWRD;
793 bol = flags & SR_BEGLINE;
794 eol = flags & SR_ENDLINE;
795 exact = flags & SR_EXACTSR;
796 toggle = 0; /* reserved for future use */
798 memset(&menu_pat, 0, sizeof(menu_pat));
799 /* add exceptions here based on the location of the items in the menu */
800 for(i = 0; i < 10; i++){
801 if(flags & SR_ORIGMEN){
802 menu_pat[i].name = menu_srchpat[10*toggle + i].name;
803 menu_pat[i].label = menu_srchpat[10*toggle + i].label;
804 menu_pat[i].key = menu_srchpat[10*toggle + i].key;
805 if(toggle == 0){
806 if (i == REPLACE_KEY)
807 menu_pat[i].label = repl_mode ? N_("No Replace")
808 : N_("Replace");
810 } else if(flags & SR_OPTNMEN){
811 menu_pat[i].name = menu_srchopt[i].name;
812 menu_pat[i].label = menu_srchopt[i].label;
813 menu_pat[i].key = menu_srchopt[i].key;
814 switch(i){
815 case EXACTSR_KEY:
816 menu_pat[i].label = exact ? N_("No Exact")
817 : N_("Exact");
818 break;
819 case REPLACE_KEY:
820 menu_pat[i].label = repl_mode ? N_("No Replace")
821 : N_("Replace");
822 break;
823 case BSEARCH_KEY:
824 menu_pat[i].label = bsearch ? N_("Srch Fwd")
825 : N_("Srch Back");
826 break;
827 case BGNLINE_KEY:
828 menu_pat[i].label = bol ? N_("Anywhere")
829 : N_("Bgn Line");
830 break;
831 case ENDLINE_KEY:
832 menu_pat[i].label = eol ? N_("Anywhere")
833 : N_("End Line");
834 break;
835 default : break;
837 if(menu_pat[i].name)
838 KS_OSDATASET(&menu_pat[i], KS_NONE);
841 b = utf8_to_ucs4_cpystr(utf8prompt);
842 if(b){
843 ucs4_strncpy(prompt, b, NPMT);
844 prompt[NPMT-1] = '\0';
845 if(bsearch){
846 fs_give((void **) &b);
847 b = utf8_to_ucs4_cpystr(N_(" backward"));
848 if(b) ucs4_strncat(prompt, b, ucs4_strlen(b));
849 prompt[NPMT-1] = '\0';
851 if(bol){
852 fs_give((void **) &b);
853 b = utf8_to_ucs4_cpystr(N_(" at start of line"));
854 if(b) ucs4_strncat(prompt, b, ucs4_strlen(b));
855 prompt[NPMT-1] = '\0';
856 } else if(eol){
857 fs_give((void **) &b);
858 b = utf8_to_ucs4_cpystr(N_(" at end of line"));
859 if(b) ucs4_strncat(prompt, b, ucs4_strlen(b));
860 prompt[NPMT-1] = '\0';
862 if(exact){
863 fs_give((void **) &b);
864 b = utf8_to_ucs4_cpystr(N_(" exactly for"));
865 if(b) ucs4_strncat(prompt, b, ucs4_strlen(b));
866 prompt[NPMT-1] = '\0';
868 if(b) fs_give((void **) &b);
871 promptp = &prompt[ucs4_strlen(prompt)];
873 if(repl_mode){
874 b = utf8_to_ucs4_cpystr(" (to replace)");
875 if(b){
876 ucs4_strncpy(promptp, b, NPMT-(promptp-prompt));
877 promptp += ucs4_strlen(promptp);
878 prompt[NPMT-1] = '\0';
879 fs_give((void **) &b);
883 if(pat[0] != '\0'){
884 if((promptp-prompt) < NPMT-2){
885 *promptp++ = ' ';
886 *promptp++ = '[';
887 *promptp = '\0';
890 expandp(pat, promptp, NPMT-(promptp-prompt));
891 prompt[NPMT-1] = '\0';
892 promptp += ucs4_strlen(promptp);
894 if((promptp-prompt) < NPMT-1){
895 *promptp++ = ']';
896 *promptp = '\0';
900 if((promptp-prompt) < NPMT-2){
901 *promptp++ = ':';
902 *promptp++ = ' ';
903 *promptp = '\0';
906 prompt[NPMT-1] = '\0';
908 s = mlreplyd(prompt, defpat, defpatlen, QDEFLT, menu_pat);
910 if (s == TRUE || s == FALSE){ /* changed or not, they're done */
911 if(!defpat[0]){ /* use default */
912 ucs4_strncpy(defpat, pat, defpatlen);
913 defpat[defpatlen-1] = '\0';
915 else if(ucs4_strcmp(pat, defpat)){ /* Specified */
916 ucs4_strncpy(pat, defpat, NPAT);
917 pat[NPAT-1] = '\0';
918 rpat[0] = '\0';
921 s = TRUE; /* let caller know to proceed */
924 return(s);
929 * Read a pattern. Stash it in the external variable "pat". The "pat" is not
930 * updated if the user types in an empty line. If the user typed an empty line,
931 * and there is no old pattern, it is an error. Display the old pattern, in the
932 * style of Jeff Lomicka. There is some do-it-yourself control expansion.
933 * change to using <ESC> to delemit the end-of-pattern to allow <NL>s in
934 * the search string.
938 readnumpat(char *utf8prompt)
940 int i, n;
941 char numpat[NPMT];
942 EXTRAKEYS menu_pat[12];
944 memset(&menu_pat, 0, 10*sizeof(EXTRAKEYS));
945 menu_pat[i = 0].name = "^T";
946 menu_pat[i].label = N_("No Line Number");
947 menu_pat[i].key = (CTRL|'T');
948 KS_OSDATASET(&menu_pat[i++], KS_NONE);
950 menu_pat[i].name = NULL;
952 numpat[0] = '\0';
953 while(1)
954 switch(mlreplyd_utf8(utf8prompt, numpat, NPMT, QNORML, menu_pat)){
955 case TRUE :
956 if(*numpat){
957 for(i = n = 0; numpat[i]; i++)
958 if(strchr("0123456789", numpat[i])){
959 n = (n * 10) + (numpat[i] - '0');
961 else
962 return(-2);
964 return(n);
967 case FALSE :
968 default :
969 return(-1);
971 case (CTRL|'T') :
972 return(-3);
974 case (CTRL|'L') :
975 case HELPCH :
976 break;
982 readpattern(char *utf8prompt, int text_mode, int flags)
984 register int s;
985 int i;
986 int toggle, bsearch, bol, eol, exact;
987 UCS *b;
988 UCS tpat[NPAT+20];
989 UCS *tpatp;
990 EXTRAKEYS menu_pat[12];
992 bsearch = flags & SR_BACKWRD;
993 bol = flags & SR_BEGLINE;
994 eol = flags & SR_ENDLINE;
995 exact = flags & SR_EXACTSR;
996 toggle = 0; /* reserved for future use */
998 memset(&menu_pat, 0, sizeof(menu_pat));
999 /* add exceptions here based on the location of the items in the menu */
1000 for(i = 0; i < 10; i++){
1001 if(flags & SR_ORIGMEN){
1002 menu_pat[i].name = menu_srchpat[10*toggle + i].name;
1003 menu_pat[i].label = menu_srchpat[10*toggle + i].label;
1004 menu_pat[i].key = menu_srchpat[10*toggle + i].key;
1005 if(toggle == 0){
1006 if (i == REPLACE_KEY)
1007 memset(&menu_pat[i], 0, sizeof(EXTRAKEYS));
1008 if (i > REPLACE_KEY && !text_mode)
1009 memset(&menu_pat[i], 0, sizeof(EXTRAKEYS));
1011 } else if(flags & SR_OPTNMEN){
1012 menu_pat[i].name = menu_srchopt[i].name;
1013 menu_pat[i].label = menu_srchopt[i].label;
1014 menu_pat[i].key = menu_srchopt[i].key;
1015 switch(i){
1016 case EXACTSR_KEY:
1017 menu_pat[i].label = exact ? N_("No Exact")
1018 : N_("Exact");
1019 break;
1020 case BSEARCH_KEY:
1021 menu_pat[i].label = bsearch ? N_("Srch Fwd")
1022 : N_("Srch Back");
1023 break;
1024 case BGNLINE_KEY:
1025 menu_pat[i].label = bol ? N_("Anywhere")
1026 : N_("Bgn Line");
1027 break;
1028 case ENDLINE_KEY:
1029 menu_pat[i].label = eol ? N_("Anywhere")
1030 : N_("End Line");
1031 break;
1032 default : break;
1034 if(menu_pat[i].name)
1035 KS_OSDATASET(&menu_pat[i], KS_NONE);
1039 b = utf8_to_ucs4_cpystr(utf8prompt);
1040 if(b){
1041 ucs4_strncpy(tpat, b, NPAT+20);
1042 tpat[NPAT+20-1] = '\0';
1043 if(bsearch){
1044 fs_give((void **) &b);
1045 b = utf8_to_ucs4_cpystr(N_(" backward"));
1046 if(b) ucs4_strncat(tpat, b, ucs4_strlen(b));
1047 tpat[NPAT+20-1] = '\0';
1049 if(bol){
1050 fs_give((void **) &b);
1051 b = utf8_to_ucs4_cpystr(N_(" at start of line"));
1052 if(b) ucs4_strncat(tpat, b, ucs4_strlen(b));
1053 tpat[NPAT+20-1] = '\0';
1054 } else if (eol){
1055 fs_give((void **) &b);
1056 b = utf8_to_ucs4_cpystr(N_(" at end of line"));
1057 if(b) ucs4_strncat(tpat, b, ucs4_strlen(b));
1058 tpat[NPAT+20-1] = '\0';
1060 if (exact){
1061 fs_give((void **) &b);
1062 b = utf8_to_ucs4_cpystr(N_(" exactly for"));
1063 if(b) ucs4_strncat(tpat, b, ucs4_strlen(b));
1064 tpat[NPAT+20-1] = '\0';
1066 if(b) fs_give((void **) &b);
1069 tpatp = &tpat[ucs4_strlen(tpat)];
1071 if(pat[0] != '\0'){
1072 if((tpatp-tpat) < NPAT+20-2){
1073 *tpatp++ = ' ';
1074 *tpatp++ = '[';
1075 *tpatp = '\0';
1078 expandp(pat, tpatp, NPAT+20-(tpatp-tpat));
1079 tpat[NPAT+20-1] = '\0';
1080 tpatp += ucs4_strlen(tpatp);
1082 if((tpatp-tpat) < NPAT+20-1){
1083 *tpatp++ = ']';
1084 *tpatp = '\0';
1088 if((tpatp-tpat) < NPAT+20-3){
1089 *tpatp++ = ' ';
1090 *tpatp++ = ':';
1091 *tpatp++ = ' ';
1092 *tpatp = '\0';
1095 tpat[NPAT+20-1] = '\0';
1097 s = mlreplyd(tpat, tpat, NPAT, QNORML, menu_pat);
1099 if ((s == TRUE) && ucs4_strcmp(pat,tpat)){ /* Specified */
1100 ucs4_strncpy(pat, tpat, NPAT);
1101 pat[NPAT-1] = '\0';
1102 rpat[0] = '\0';
1104 else if (s == FALSE && pat[0] != '\0') /* CR, but old one */
1105 s = TRUE;
1107 return(s);
1110 /* given a line, reverse its content */
1111 void
1112 reverse_line(LINE *l)
1114 int i, j, a;
1115 UCS u;
1117 if(l == NULL) return;
1118 j = llength(l) - 1;
1119 for(i = 0; i < j; i++, j--){
1120 u = lgetc(l, j).c; /* reverse the text */
1121 lgetc(l, j).c = lgetc(l, i).c;
1122 lgetc(l, i).c = u;
1123 a = lgetc(l, j).a; /* and the attribute */
1124 lgetc(l, j).a = lgetc(l, i).a;
1125 lgetc(l, i).a = a;
1129 void
1130 reverse_all(UCS *pat, int bsearch)
1132 if(bsearch != 0){
1133 reverse_buffer();
1134 reverse_ucs4(pat);
1138 void
1139 reverse_buffer(void)
1141 LINE *l;
1143 for(l = lforw(curbp->b_linep); l != curbp->b_linep; l = lforw(l))
1144 reverse_line(l);
1146 /* reverse links in buffer */
1147 l = curbp->b_linep;
1148 do {
1149 lforw(l) = lback(l);
1150 l = lforw(l);
1151 } while(l != curbp->b_linep);
1153 l = curbp->b_linep;
1154 do {
1155 lback(lforw(l)) = l;
1156 l = lforw(l);
1157 } while (l != curbp->b_linep);
1159 curwp->w_doto = llength(curwp->w_dotp) - 1 - curwp->w_doto;
1164 /* given a UCS4 string reverse its content */
1165 void
1166 reverse_ucs4(UCS *s)
1168 int i, j;
1169 UCS u;
1171 j = ucs4_strlen(s) - 1;
1172 for(i = 0; i < j; i++, j--){
1173 u = s[j];
1174 s[j] = s[i];
1175 s[i] = u;
1180 /* search forward for a <patrn>.
1181 * A backward search is a forward search in backward lines with backward
1182 * patterns
1185 forscan(int *wrapt, /* boolean indicating search wrapped */
1186 UCS *patrn, /* string to scan for */
1187 int flags, /* direction and position */
1188 LINE *limitp, /* stop searching if reached */
1189 int limito, /* stop searching if reached */
1190 int leavep) /* place to leave point
1191 PTBEG = beginning of match
1192 PTEND = at end of match */
1195 LINE *curline; /* current line during scan */
1196 int curoff; /* position within current line */
1197 LINE *lastline; /* last line position during scan */
1198 int lastoff; /* position within last line */
1199 UCS c; /* character at current position */
1200 LINE *matchline; /* current line during matching */
1201 int matchoff; /* position in matching line */
1202 UCS *patptr; /* pointer into pattern */
1203 int stopoff; /* offset to stop search */
1204 LINE *stopline; /* line to stop search */
1205 int ftest = 0; /* position of first character of test */
1206 int bsearch;
1207 int bol;
1208 int eol;
1210 bsearch = flags & SR_BACKWRD;
1211 bol = flags & SR_BEGLINE;
1212 eol = flags & SR_ENDLINE;
1213 *wrapt = FALSE;
1215 /* if bsearch is set we return the match at the beginning of the
1216 * matching string, so we make this algorithm return the end of
1217 * the string, so that when we reverse we be at the beginning.
1219 if(bsearch)
1220 leavep = leavep == PTBEG ? PTEND : PTBEG;
1223 * the idea is to set the character to end the search at the
1224 * next character in the buffer. thus, let the search wrap
1225 * completely around the buffer.
1227 * first, test to see if we are at the end of the line,
1228 * otherwise start searching on the next character.
1230 if(curwp->w_doto == llength(curwp->w_dotp)){
1232 * dot is not on end of a line
1233 * start at 0 offset of the next line
1235 stopoff = curoff = 0;
1236 stopline = curline = lforw(curwp->w_dotp);
1237 if (curwp->w_dotp == curbp->b_linep)
1238 *wrapt = TRUE;
1240 else{
1241 stopoff = curoff = curwp->w_doto;
1242 stopline = curline = curwp->w_dotp;
1245 /* scan each character until we hit the head link record */
1248 * maybe wrapping is a good idea
1250 while (curline){
1252 if(curline == curbp->b_linep)
1253 *wrapt = TRUE;
1255 /* save the current position in case we need to
1256 restore it on a match */
1258 lastline = curline;
1259 lastoff = curoff;
1261 /* get the current character resolving EOLs */
1262 if (curoff == llength(curline)) { /* if at EOL */
1263 curline = lforw(curline); /* skip to next line */
1264 curoff = 0;
1265 c = '\n'; /* and return a <NL> */
1267 else if(curoff == -1){
1268 stopoff = curoff = 0;
1269 continue;
1270 c = '\n';
1272 else
1273 c = lgetc(curline, curoff++).c; /* get the char */
1275 if(bol)
1276 ftest = bsearch == 0 ? 1 : llength(curline) - ucs4_strlen(patrn) + 1;
1277 else if (eol)
1278 ftest = bsearch == 0 ? llength(curline) - ucs4_strlen(patrn) + 1 : 1;
1279 /* test it against first char in pattern */
1280 if (eq(c, patrn[0]) != FALSE /* if we find it..*/
1281 && ((bol == 0 && eol == 0) /* ...and if it is anywhere */
1282 || (bol != 0 /* ...or is at the begin or line */
1283 && ((bsearch == 0 && curoff == ftest) /* and search forward and found at beginning of line */
1284 || (bsearch != 0 && curoff == ftest))) /* or search backward and found at end of line */
1285 || (eol != 0 /* ...or is at the end or line */
1286 && ((bsearch == 0 && curoff == ftest) /* and search forward and found at beginning of line */
1287 || (bsearch != 0 && curoff == ftest))) /* or search backward and found at end of line */
1289 /* setup match pointers */
1290 matchline = curline;
1291 matchoff = curoff;
1292 patptr = &patrn[0];
1294 /* scan through patrn for a match */
1295 while (*++patptr != '\0') {
1296 /* advance all the pointers */
1297 if (matchoff >= llength(matchline)) {
1298 /* advance past EOL */
1299 matchline = lforw(matchline);
1300 matchoff = 0;
1301 c = '\n';
1302 } else
1303 c = lgetc(matchline, matchoff++).c;
1305 if(matchline == limitp && matchoff == limito)
1306 return(FALSE);
1308 /* and test it against the pattern */
1309 if (eq(*patptr, c) == FALSE)
1310 goto fail;
1313 /* A SUCCESSFUL MATCH!!! */
1314 /* reset the global "." pointers */
1315 if (leavep == PTEND) { /* at end of string */
1316 curwp->w_dotp = matchline;
1317 curwp->w_doto = matchoff - 1;
1319 else { /* at beginning of string */
1320 curwp->w_dotp = lastline;
1321 curwp->w_doto = lastoff;
1324 curwp->w_flag |= WFMOVE; /* flag that we have moved */
1325 return(TRUE);
1328 fail:; /* continue to search */
1329 if(((curline == stopline) && (curoff == stopoff))
1330 || (curline == limitp && curoff == limito))
1331 break; /* searched everywhere... */
1333 /* we could not find a match */
1335 return(FALSE);
1340 /* expandp: expand control key sequences for output */
1342 expandp(UCS *srcstr, /* string to expand */
1343 UCS *deststr, /* destination of expanded string */
1344 int maxlength) /* maximum chars in destination */
1346 UCS c; /* current char to translate */
1348 /* scan through the string */
1349 while ((c = *srcstr++) != 0) {
1350 if (c == '\n') { /* its an EOL */
1351 *deststr++ = '<';
1352 *deststr++ = 'N';
1353 *deststr++ = 'L';
1354 *deststr++ = '>';
1355 maxlength -= 4;
1356 } else if (c < 0x20 || c == 0x7f) { /* control character */
1357 *deststr++ = '^';
1358 *deststr++ = c ^ 0x40;
1359 maxlength -= 2;
1360 } else if (c == '%') {
1361 *deststr++ = '%';
1362 *deststr++ = '%';
1363 maxlength -= 2;
1364 } else { /* any other character */
1365 *deststr++ = c;
1366 maxlength--;
1369 /* check for maxlength */
1370 if (maxlength < 4) {
1371 *deststr++ = '$';
1372 *deststr = '\0';
1373 return(FALSE);
1377 *deststr = '\0';
1378 return(TRUE);
1383 * chword() - change the given word, wp, pointed to by the curwp->w_dot
1384 * pointers to the word in cb
1385 * if bsearch is set, then cb is supposed to come unreversed, while
1386 * the buffer is supposed to be reversed, so we must reverse cb before
1387 * inserting it.
1389 void
1390 chword(UCS *wb, UCS *cb, int bsearch)
1392 UCS *u;
1393 ldelete(ucs4_strlen(wb), NULL); /* not saved in kill buffer */
1394 if(bsearch) reverse_ucs4(cb);
1395 for(u = cb; *u != '\0'; u++)
1396 linsert(1, *u);
1397 if(bsearch) reverse_ucs4(cb);
1399 curwp->w_flag |= WFEDIT;
1402 void
1403 supdate(UCS *pat, int bsearch)
1405 reverse_all(pat, bsearch); /* undo reverse buffer and pattern */
1406 update();
1407 reverse_all(pat, bsearch); /* reverse buffer and pattern */
1410 char *
1411 sucs4_to_utf8_cpystr(UCS *orig, int bsearch)
1413 char *utf8;
1414 if(bsearch) reverse_ucs4(orig);
1415 utf8 = ucs4_to_utf8_cpystr(orig);
1416 if(bsearch) reverse_ucs4(orig);
1417 return utf8;