xref: /NextBSD/contrib/libreadline/isearch.c (revision eb1a5f8de9f7ea602c373a710f531abbf81141c4)
1 /* **************************************************************** */
2 /*								    */
3 /*			I-Search and Searching			    */
4 /*								    */
5 /* **************************************************************** */
6 
7 /* Copyright (C) 1987-2005 Free Software Foundation, Inc.
8 
9    This file contains the Readline Library (the Library), a set of
10    routines for providing Emacs style line input to programs that ask
11    for it.
12 
13    The Library is free software; you can redistribute it and/or modify
14    it under the terms of the GNU General Public License as published by
15    the Free Software Foundation; either version 2, or (at your option)
16    any later version.
17 
18    The Library is distributed in the hope that it will be useful, but
19    WITHOUT ANY WARRANTY; without even the implied warranty of
20    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21    General Public License for more details.
22 
23    The GNU General Public License is often shipped with GNU software, and
24    is generally kept in a file called COPYING or LICENSE.  If you do not
25    have a copy of the license, write to the Free Software Foundation,
26    59 Temple Place, Suite 330, Boston, MA 02111 USA. */
27 #define READLINE_LIBRARY
28 
29 #if defined (HAVE_CONFIG_H)
30 #  include <config.h>
31 #endif
32 
33 #include <sys/types.h>
34 
35 #include <stdio.h>
36 
37 #if defined (HAVE_UNISTD_H)
38 #  include <unistd.h>
39 #endif
40 
41 #if defined (HAVE_STDLIB_H)
42 #  include <stdlib.h>
43 #else
44 #  include "ansi_stdlib.h"
45 #endif
46 
47 #include "rldefs.h"
48 #include "rlmbutil.h"
49 
50 #include "readline.h"
51 #include "history.h"
52 
53 #include "rlprivate.h"
54 #include "xmalloc.h"
55 
56 /* Variables exported to other files in the readline library. */
57 char *_rl_isearch_terminators = (char *)NULL;
58 
59 _rl_search_cxt *_rl_iscxt = 0;
60 
61 /* Variables imported from other files in the readline library. */
62 extern HIST_ENTRY *_rl_saved_line_for_history;
63 
64 static int rl_search_history PARAMS((int, int));
65 
66 static _rl_search_cxt *_rl_isearch_init PARAMS((int));
67 static void _rl_isearch_fini PARAMS((_rl_search_cxt *));
68 static int _rl_isearch_cleanup PARAMS((_rl_search_cxt *, int));
69 
70 /* Last line found by the current incremental search, so we don't `find'
71    identical lines many times in a row.  Now part of isearch context. */
72 /* static char *prev_line_found; */
73 
74 /* Last search string and its length. */
75 static char *last_isearch_string;
76 static int last_isearch_string_len;
77 
78 static char *default_isearch_terminators = "\033\012";
79 
80 _rl_search_cxt *
_rl_scxt_alloc(type,flags)81 _rl_scxt_alloc (type, flags)
82      int type, flags;
83 {
84   _rl_search_cxt *cxt;
85 
86   cxt = (_rl_search_cxt *)xmalloc (sizeof (_rl_search_cxt));
87 
88   cxt->type = type;
89   cxt->sflags = flags;
90 
91   cxt->search_string = 0;
92   cxt->search_string_size = cxt->search_string_index = 0;
93 
94   cxt->lines = 0;
95   cxt->allocated_line = 0;
96   cxt->hlen = cxt->hindex = 0;
97 
98   cxt->save_point = rl_point;
99   cxt->save_mark = rl_mark;
100   cxt->save_line = where_history ();
101   cxt->last_found_line = cxt->save_line;
102   cxt->prev_line_found = 0;
103 
104   cxt->save_undo_list = 0;
105 
106   cxt->history_pos = 0;
107   cxt->direction = 0;
108 
109   cxt->lastc = 0;
110 
111   cxt->sline = 0;
112   cxt->sline_len = cxt->sline_index = 0;
113 
114   cxt->search_terminators = 0;
115 
116   return cxt;
117 }
118 
119 void
_rl_scxt_dispose(cxt,flags)120 _rl_scxt_dispose (cxt, flags)
121      _rl_search_cxt *cxt;
122      int flags;
123 {
124   FREE (cxt->search_string);
125   FREE (cxt->allocated_line);
126   FREE (cxt->lines);
127 
128   free (cxt);
129 }
130 
131 /* Search backwards through the history looking for a string which is typed
132    interactively.  Start with the current line. */
133 int
rl_reverse_search_history(sign,key)134 rl_reverse_search_history (sign, key)
135      int sign, key;
136 {
137   return (rl_search_history (-sign, key));
138 }
139 
140 /* Search forwards through the history looking for a string which is typed
141    interactively.  Start with the current line. */
142 int
rl_forward_search_history(sign,key)143 rl_forward_search_history (sign, key)
144      int sign, key;
145 {
146   return (rl_search_history (sign, key));
147 }
148 
149 /* Display the current state of the search in the echo-area.
150    SEARCH_STRING contains the string that is being searched for,
151    DIRECTION is zero for forward, or non-zero for reverse,
152    WHERE is the history list number of the current line.  If it is
153    -1, then this line is the starting one. */
154 static void
rl_display_search(search_string,reverse_p,where)155 rl_display_search (search_string, reverse_p, where)
156      char *search_string;
157      int reverse_p, where;
158 {
159   char *message;
160   int msglen, searchlen;
161 
162   searchlen = (search_string && *search_string) ? strlen (search_string) : 0;
163 
164   message = (char *)xmalloc (searchlen + 33);
165   msglen = 0;
166 
167 #if defined (NOTDEF)
168   if (where != -1)
169     {
170       sprintf (message, "[%d]", where + history_base);
171       msglen = strlen (message);
172     }
173 #endif /* NOTDEF */
174 
175   message[msglen++] = '(';
176 
177   if (reverse_p)
178     {
179       strcpy (message + msglen, "reverse-");
180       msglen += 8;
181     }
182 
183   strcpy (message + msglen, "i-search)`");
184   msglen += 10;
185 
186   if (search_string)
187     {
188       strcpy (message + msglen, search_string);
189       msglen += searchlen;
190     }
191 
192   strcpy (message + msglen, "': ");
193 
194   rl_message ("%s", message);
195   free (message);
196   (*rl_redisplay_function) ();
197 }
198 
199 static _rl_search_cxt *
_rl_isearch_init(direction)200 _rl_isearch_init (direction)
201      int direction;
202 {
203   _rl_search_cxt *cxt;
204   register int i;
205   HIST_ENTRY **hlist;
206 
207   cxt = _rl_scxt_alloc (RL_SEARCH_ISEARCH, 0);
208   if (direction < 0)
209     cxt->sflags |= SF_REVERSE;
210 
211   cxt->search_terminators = _rl_isearch_terminators ? _rl_isearch_terminators
212 						: default_isearch_terminators;
213 
214   /* Create an arrary of pointers to the lines that we want to search. */
215   hlist = history_list ();
216   rl_maybe_replace_line ();
217   i = 0;
218   if (hlist)
219     for (i = 0; hlist[i]; i++);
220 
221   /* Allocate space for this many lines, +1 for the current input line,
222      and remember those lines. */
223   cxt->lines = (char **)xmalloc ((1 + (cxt->hlen = i)) * sizeof (char *));
224   for (i = 0; i < cxt->hlen; i++)
225     cxt->lines[i] = hlist[i]->line;
226 
227   if (_rl_saved_line_for_history)
228     cxt->lines[i] = _rl_saved_line_for_history->line;
229   else
230     {
231       /* Keep track of this so we can free it. */
232       cxt->allocated_line = (char *)xmalloc (1 + strlen (rl_line_buffer));
233       strcpy (cxt->allocated_line, &rl_line_buffer[0]);
234       cxt->lines[i] = cxt->allocated_line;
235     }
236 
237   cxt->hlen++;
238 
239   /* The line where we start the search. */
240   cxt->history_pos = cxt->save_line;
241 
242   rl_save_prompt ();
243 
244   /* Initialize search parameters. */
245   cxt->search_string = (char *)xmalloc (cxt->search_string_size = 128);
246   cxt->search_string[cxt->search_string_index = 0] = '\0';
247 
248   /* Normalize DIRECTION into 1 or -1. */
249   cxt->direction = (direction >= 0) ? 1 : -1;
250 
251   cxt->sline = rl_line_buffer;
252   cxt->sline_len = strlen (cxt->sline);
253   cxt->sline_index = rl_point;
254 
255   _rl_iscxt = cxt;		/* save globally */
256 
257   return cxt;
258 }
259 
260 static void
_rl_isearch_fini(cxt)261 _rl_isearch_fini (cxt)
262      _rl_search_cxt *cxt;
263 {
264   /* First put back the original state. */
265   strcpy (rl_line_buffer, cxt->lines[cxt->save_line]);
266 
267   rl_restore_prompt ();
268 
269   /* Save the search string for possible later use. */
270   FREE (last_isearch_string);
271   last_isearch_string = cxt->search_string;
272   last_isearch_string_len = cxt->search_string_index;
273   cxt->search_string = 0;
274 
275   if (cxt->last_found_line < cxt->save_line)
276     rl_get_previous_history (cxt->save_line - cxt->last_found_line, 0);
277   else
278     rl_get_next_history (cxt->last_found_line - cxt->save_line, 0);
279 
280   /* If the string was not found, put point at the end of the last matching
281      line.  If last_found_line == orig_line, we didn't find any matching
282      history lines at all, so put point back in its original position. */
283   if (cxt->sline_index < 0)
284     {
285       if (cxt->last_found_line == cxt->save_line)
286 	cxt->sline_index = cxt->save_point;
287       else
288 	cxt->sline_index = strlen (rl_line_buffer);
289       rl_mark = cxt->save_mark;
290     }
291 
292   rl_point = cxt->sline_index;
293   /* Don't worry about where to put the mark here; rl_get_previous_history
294      and rl_get_next_history take care of it. */
295 
296   rl_clear_message ();
297 }
298 
299 int
_rl_search_getchar(cxt)300 _rl_search_getchar (cxt)
301      _rl_search_cxt *cxt;
302 {
303   int c;
304 
305   /* Read a key and decide how to proceed. */
306   RL_SETSTATE(RL_STATE_MOREINPUT);
307   c = cxt->lastc = rl_read_key ();
308   RL_UNSETSTATE(RL_STATE_MOREINPUT);
309 
310 #if defined (HANDLE_MULTIBYTE)
311   if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
312     c = cxt->lastc = _rl_read_mbstring (cxt->lastc, cxt->mb, MB_LEN_MAX);
313 #endif
314 
315   return c;
316 }
317 
318 /* Process just-read character C according to isearch context CXT.  Return
319    -1 if the caller should just free the context and return, 0 if we should
320    break out of the loop, and 1 if we should continue to read characters. */
321 int
_rl_isearch_dispatch(cxt,c)322 _rl_isearch_dispatch (cxt, c)
323      _rl_search_cxt *cxt;
324      int c;
325 {
326   int n, wstart, wlen, limit, cval;
327   rl_command_func_t *f;
328 
329   f = (rl_command_func_t *)NULL;
330 
331   if (c < 0)
332     {
333       cxt->sflags |= SF_FAILED;
334       cxt->history_pos = cxt->last_found_line;
335       return -1;
336     }
337 
338   /* Translate the keys we do something with to opcodes. */
339   if (c >= 0 && _rl_keymap[c].type == ISFUNC)
340     {
341       f = _rl_keymap[c].function;
342 
343       if (f == rl_reverse_search_history)
344 	cxt->lastc = (cxt->sflags & SF_REVERSE) ? -1 : -2;
345       else if (f == rl_forward_search_history)
346 	cxt->lastc = (cxt->sflags & SF_REVERSE) ? -2 : -1;
347       else if (f == rl_rubout)
348 	cxt->lastc = -3;
349       else if (c == CTRL ('G'))
350 	cxt->lastc = -4;
351       else if (c == CTRL ('W'))	/* XXX */
352 	cxt->lastc = -5;
353       else if (c == CTRL ('Y'))	/* XXX */
354 	cxt->lastc = -6;
355     }
356 
357   /* The characters in isearch_terminators (set from the user-settable
358      variable isearch-terminators) are used to terminate the search but
359      not subsequently execute the character as a command.  The default
360      value is "\033\012" (ESC and C-J). */
361   if (strchr (cxt->search_terminators, cxt->lastc))
362     {
363       /* ESC still terminates the search, but if there is pending
364 	 input or if input arrives within 0.1 seconds (on systems
365 	 with select(2)) it is used as a prefix character
366 	 with rl_execute_next.  WATCH OUT FOR THIS!  This is intended
367 	 to allow the arrow keys to be used like ^F and ^B are used
368 	 to terminate the search and execute the movement command.
369 	 XXX - since _rl_input_available depends on the application-
370 	 settable keyboard timeout value, this could alternatively
371 	 use _rl_input_queued(100000) */
372       if (cxt->lastc == ESC && _rl_input_available ())
373 	rl_execute_next (ESC);
374       return (0);
375     }
376 
377 #define ENDSRCH_CHAR(c) \
378   ((CTRL_CHAR (c) || META_CHAR (c) || (c) == RUBOUT) && ((c) != CTRL ('G')))
379 
380 #if defined (HANDLE_MULTIBYTE)
381   if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
382     {
383       if (cxt->lastc >= 0 && (cxt->mb[0] && cxt->mb[1] == '\0') && ENDSRCH_CHAR (cxt->lastc))
384 	{
385 	  /* This sets rl_pending_input to c; it will be picked up the next
386 	     time rl_read_key is called. */
387 	  rl_execute_next (cxt->lastc);
388 	  return (0);
389 	}
390     }
391   else
392 #endif
393     if (cxt->lastc >= 0 && ENDSRCH_CHAR (cxt->lastc))
394       {
395 	/* This sets rl_pending_input to LASTC; it will be picked up the next
396 	   time rl_read_key is called. */
397 	rl_execute_next (cxt->lastc);
398 	return (0);
399       }
400 
401   /* Now dispatch on the character.  `Opcodes' affect the search string or
402      state.  Other characters are added to the string.  */
403   switch (cxt->lastc)
404     {
405     /* search again */
406     case -1:
407       if (cxt->search_string_index == 0)
408 	{
409 	  if (last_isearch_string)
410 	    {
411 	      cxt->search_string_size = 64 + last_isearch_string_len;
412 	      cxt->search_string = (char *)xrealloc (cxt->search_string, cxt->search_string_size);
413 	      strcpy (cxt->search_string, last_isearch_string);
414 	      cxt->search_string_index = last_isearch_string_len;
415 	      rl_display_search (cxt->search_string, (cxt->sflags & SF_REVERSE), -1);
416 	      break;
417 	    }
418 	  return (1);
419 	}
420       else if (cxt->sflags & SF_REVERSE)
421 	cxt->sline_index--;
422       else if (cxt->sline_index != cxt->sline_len)
423 	cxt->sline_index++;
424       else
425 	rl_ding ();
426       break;
427 
428     /* switch directions */
429     case -2:
430       cxt->direction = -cxt->direction;
431       if (cxt->direction < 0)
432 	cxt->sflags |= SF_REVERSE;
433       else
434 	cxt->sflags &= ~SF_REVERSE;
435       break;
436 
437     /* delete character from search string. */
438     case -3:	/* C-H, DEL */
439       /* This is tricky.  To do this right, we need to keep a
440 	 stack of search positions for the current search, with
441 	 sentinels marking the beginning and end.  But this will
442 	 do until we have a real isearch-undo. */
443       if (cxt->search_string_index == 0)
444 	rl_ding ();
445       else
446 	cxt->search_string[--cxt->search_string_index] = '\0';
447       break;
448 
449     case -4:	/* C-G, abort */
450       rl_replace_line (cxt->lines[cxt->save_line], 0);
451       rl_point = cxt->save_point;
452       rl_mark = cxt->save_mark;
453       rl_restore_prompt();
454       rl_clear_message ();
455 
456       return -1;
457 
458     case -5:	/* C-W */
459       /* skip over portion of line we already matched and yank word */
460       wstart = rl_point + cxt->search_string_index;
461       if (wstart >= rl_end)
462 	{
463 	  rl_ding ();
464 	  break;
465 	}
466 
467       /* if not in a word, move to one. */
468       cval = _rl_char_value (rl_line_buffer, wstart);
469       if (_rl_walphabetic (cval) == 0)
470 	{
471 	  rl_ding ();
472 	  break;
473 	}
474       n = MB_NEXTCHAR (rl_line_buffer, wstart, 1, MB_FIND_NONZERO);;
475       while (n < rl_end)
476 	{
477 	  cval = _rl_char_value (rl_line_buffer, n);
478 	  if (_rl_walphabetic (cval) == 0)
479 	    break;
480 	  n = MB_NEXTCHAR (rl_line_buffer, n, 1, MB_FIND_NONZERO);;
481 	}
482       wlen = n - wstart + 1;
483       if (cxt->search_string_index + wlen + 1 >= cxt->search_string_size)
484 	{
485 	  cxt->search_string_size += wlen + 1;
486 	  cxt->search_string = (char *)xrealloc (cxt->search_string, cxt->search_string_size);
487 	}
488       for (; wstart < n; wstart++)
489 	cxt->search_string[cxt->search_string_index++] = rl_line_buffer[wstart];
490       cxt->search_string[cxt->search_string_index] = '\0';
491       break;
492 
493     case -6:	/* C-Y */
494       /* skip over portion of line we already matched and yank rest */
495       wstart = rl_point + cxt->search_string_index;
496       if (wstart >= rl_end)
497 	{
498 	  rl_ding ();
499 	  break;
500 	}
501       n = rl_end - wstart + 1;
502       if (cxt->search_string_index + n + 1 >= cxt->search_string_size)
503 	{
504 	  cxt->search_string_size += n + 1;
505 	  cxt->search_string = (char *)xrealloc (cxt->search_string, cxt->search_string_size);
506 	}
507       for (n = wstart; n < rl_end; n++)
508 	cxt->search_string[cxt->search_string_index++] = rl_line_buffer[n];
509       cxt->search_string[cxt->search_string_index] = '\0';
510       break;
511 
512     /* Add character to search string and continue search. */
513     default:
514       if (cxt->search_string_index + 2 >= cxt->search_string_size)
515 	{
516 	  cxt->search_string_size += 128;
517 	  cxt->search_string = (char *)xrealloc (cxt->search_string, cxt->search_string_size);
518 	}
519 #if defined (HANDLE_MULTIBYTE)
520       if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
521 	{
522 	  int j, l;
523 	  for (j = 0, l = strlen (cxt->mb); j < l; )
524 	    cxt->search_string[cxt->search_string_index++] = cxt->mb[j++];
525 	}
526       else
527 #endif
528 	cxt->search_string[cxt->search_string_index++] = c;
529       cxt->search_string[cxt->search_string_index] = '\0';
530       break;
531     }
532 
533   for (cxt->sflags &= ~(SF_FOUND|SF_FAILED);; )
534     {
535       limit = cxt->sline_len - cxt->search_string_index + 1;
536 
537       /* Search the current line. */
538       while ((cxt->sflags & SF_REVERSE) ? (cxt->sline_index >= 0) : (cxt->sline_index < limit))
539 	{
540 	  if (STREQN (cxt->search_string, cxt->sline + cxt->sline_index, cxt->search_string_index))
541 	    {
542 	      cxt->sflags |= SF_FOUND;
543 	      break;
544 	    }
545 	  else
546 	    cxt->sline_index += cxt->direction;
547 	}
548       if (cxt->sflags & SF_FOUND)
549 	break;
550 
551       /* Move to the next line, but skip new copies of the line
552 	 we just found and lines shorter than the string we're
553 	 searching for. */
554       do
555 	{
556 	  /* Move to the next line. */
557 	  cxt->history_pos += cxt->direction;
558 
559 	  /* At limit for direction? */
560 	  if ((cxt->sflags & SF_REVERSE) ? (cxt->history_pos < 0) : (cxt->history_pos == cxt->hlen))
561 	    {
562 	      cxt->sflags |= SF_FAILED;
563 	      break;
564 	    }
565 
566 	  /* We will need these later. */
567 	  cxt->sline = cxt->lines[cxt->history_pos];
568 	  cxt->sline_len = strlen (cxt->sline);
569 	}
570       while ((cxt->prev_line_found && STREQ (cxt->prev_line_found, cxt->lines[cxt->history_pos])) ||
571 	     (cxt->search_string_index > cxt->sline_len));
572 
573       if (cxt->sflags & SF_FAILED)
574 	break;
575 
576       /* Now set up the line for searching... */
577       cxt->sline_index = (cxt->sflags & SF_REVERSE) ? cxt->sline_len - cxt->search_string_index : 0;
578     }
579 
580   if (cxt->sflags & SF_FAILED)
581     {
582       /* We cannot find the search string.  Ding the bell. */
583       rl_ding ();
584       cxt->history_pos = cxt->last_found_line;
585       return 1;
586     }
587 
588   /* We have found the search string.  Just display it.  But don't
589      actually move there in the history list until the user accepts
590      the location. */
591   if (cxt->sflags & SF_FOUND)
592     {
593       cxt->prev_line_found = cxt->lines[cxt->history_pos];
594       rl_replace_line (cxt->lines[cxt->history_pos], 0);
595       rl_point = cxt->sline_index;
596       cxt->last_found_line = cxt->history_pos;
597       rl_display_search (cxt->search_string, (cxt->sflags & SF_REVERSE), (cxt->history_pos == cxt->save_line) ? -1 : cxt->history_pos);
598     }
599 
600   return 1;
601 }
602 
603 static int
_rl_isearch_cleanup(cxt,r)604 _rl_isearch_cleanup (cxt, r)
605      _rl_search_cxt *cxt;
606      int r;
607 {
608   if (r >= 0)
609     _rl_isearch_fini (cxt);
610   _rl_scxt_dispose (cxt, 0);
611   _rl_iscxt = 0;
612 
613   RL_UNSETSTATE(RL_STATE_ISEARCH);
614 
615   return (r != 0);
616 }
617 
618 /* Search through the history looking for an interactively typed string.
619    This is analogous to i-search.  We start the search in the current line.
620    DIRECTION is which direction to search; >= 0 means forward, < 0 means
621    backwards. */
622 static int
rl_search_history(direction,invoking_key)623 rl_search_history (direction, invoking_key)
624      int direction, invoking_key;
625 {
626   _rl_search_cxt *cxt;		/* local for now, but saved globally */
627   int c, r;
628 
629   RL_SETSTATE(RL_STATE_ISEARCH);
630   cxt = _rl_isearch_init (direction);
631 
632   rl_display_search (cxt->search_string, (cxt->sflags & SF_REVERSE), -1);
633 
634   /* If we are using the callback interface, all we do is set up here and
635       return.  The key is that we leave RL_STATE_ISEARCH set. */
636   if (RL_ISSTATE (RL_STATE_CALLBACK))
637     return (0);
638 
639   r = -1;
640   for (;;)
641     {
642       c = _rl_search_getchar (cxt);
643       /* We might want to handle EOF here (c == 0) */
644       r = _rl_isearch_dispatch (cxt, cxt->lastc);
645       if (r <= 0)
646         break;
647     }
648 
649   /* The searching is over.  The user may have found the string that she
650      was looking for, or else she may have exited a failing search.  If
651      LINE_INDEX is -1, then that shows that the string searched for was
652      not found.  We use this to determine where to place rl_point. */
653   return (_rl_isearch_cleanup (cxt, r));
654 }
655 
656 #if defined (READLINE_CALLBACKS)
657 /* Called from the callback functions when we are ready to read a key.  The
658    callback functions know to call this because RL_ISSTATE(RL_STATE_ISEARCH).
659    If _rl_isearch_dispatch finishes searching, this function is responsible
660    for turning off RL_STATE_ISEARCH, which it does using _rl_isearch_cleanup. */
661 int
_rl_isearch_callback(cxt)662 _rl_isearch_callback (cxt)
663      _rl_search_cxt *cxt;
664 {
665   int c, r;
666 
667   c = _rl_search_getchar (cxt);
668   /* We might want to handle EOF here */
669   r = _rl_isearch_dispatch (cxt, cxt->lastc);
670 
671   return (r <= 0) ? _rl_isearch_cleanup (cxt, r) : 0;
672 }
673 #endif
674