xref: /openbsd/gnu/lib/libreadline/isearch.c (revision d485f761)
1 /* **************************************************************** */
2 /*								    */
3 /*			I-Search and Searching			    */
4 /*								    */
5 /* **************************************************************** */
6 
7 /* Copyright (C) 1987,1989 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 "readline.h"
49 #include "history.h"
50 
51 #include "rlprivate.h"
52 #include "xmalloc.h"
53 
54 /* Variables exported to other files in the readline library. */
55 unsigned char *_rl_isearch_terminators = (unsigned char *)NULL;
56 
57 /* Variables imported from other files in the readline library. */
58 extern HIST_ENTRY *saved_line_for_history;
59 
60 /* Forward declarations */
61 static int rl_search_history __P((int, int));
62 
63 /* Last line found by the current incremental search, so we don't `find'
64    identical lines many times in a row. */
65 static char *prev_line_found;
66 
67 /* Search backwards through the history looking for a string which is typed
68    interactively.  Start with the current line. */
69 int
70 rl_reverse_search_history (sign, key)
71      int sign, key;
72 {
73   return (rl_search_history (-sign, key));
74 }
75 
76 /* Search forwards through the history looking for a string which is typed
77    interactively.  Start with the current line. */
78 int
79 rl_forward_search_history (sign, key)
80      int sign, key;
81 {
82   return (rl_search_history (sign, key));
83 }
84 
85 /* Display the current state of the search in the echo-area.
86    SEARCH_STRING contains the string that is being searched for,
87    DIRECTION is zero for forward, or 1 for reverse,
88    WHERE is the history list number of the current line.  If it is
89    -1, then this line is the starting one. */
90 static void
91 rl_display_search (search_string, reverse_p, where)
92      char *search_string;
93      int reverse_p, where;
94 {
95   char *message;
96   int msglen, searchlen;
97 
98   searchlen = (search_string && *search_string) ? strlen (search_string) : 0;
99 
100   message = xmalloc (searchlen + 33);
101   msglen = 0;
102 
103 #if defined (NOTDEF)
104   if (where != -1)
105     {
106       sprintf (message, "[%d]", where + history_base);
107       msglen = strlen (message);
108     }
109 #endif /* NOTDEF */
110 
111   message[msglen++] = '(';
112 
113   if (reverse_p)
114     {
115       strcpy (message + msglen, "reverse-");
116       msglen += 8;
117     }
118 
119   strcpy (message + msglen, "i-search)`");
120   msglen += 10;
121 
122   if (search_string)
123     {
124       strcpy (message + msglen, search_string);
125       msglen += searchlen;
126     }
127 
128   strcpy (message + msglen, "': ");
129 
130   rl_message ("%s", message, 0);
131   free (message);
132   (*rl_redisplay_function) ();
133 }
134 
135 /* Search through the history looking for an interactively typed string.
136    This is analogous to i-search.  We start the search in the current line.
137    DIRECTION is which direction to search; >= 0 means forward, < 0 means
138    backwards. */
139 static int
140 rl_search_history (direction, invoking_key)
141      int direction, invoking_key;
142 {
143   /* The string that the user types in to search for. */
144   char *search_string;
145 
146   /* The current length of SEARCH_STRING. */
147   int search_string_index;
148 
149   /* The amount of space that SEARCH_STRING has allocated to it. */
150   int search_string_size;
151 
152   /* The list of lines to search through. */
153   char **lines, *allocated_line;
154 
155   /* The length of LINES. */
156   int hlen;
157 
158   /* Where we get LINES from. */
159   HIST_ENTRY **hlist;
160 
161   register int i;
162   int orig_point, orig_line, last_found_line;
163   int c, found, failed, sline_len;
164 
165   /* The line currently being searched. */
166   char *sline;
167 
168   /* Offset in that line. */
169   int line_index;
170 
171   /* Non-zero if we are doing a reverse search. */
172   int reverse;
173 
174   /* The list of characters which terminate the search, but are not
175      subsequently executed.  If the variable isearch-terminators has
176      been set, we use that value, otherwise we use ESC and C-J. */
177   unsigned char *isearch_terminators;
178 
179   orig_point = rl_point;
180   last_found_line = orig_line = where_history ();
181   reverse = direction < 0;
182   hlist = history_list ();
183   allocated_line = (char *)NULL;
184 
185   isearch_terminators = _rl_isearch_terminators ? _rl_isearch_terminators
186 						: (unsigned char *)"\033\012";
187 
188   /* Create an arrary of pointers to the lines that we want to search. */
189   maybe_replace_line ();
190   i = 0;
191   if (hlist)
192     for (i = 0; hlist[i]; i++);
193 
194   /* Allocate space for this many lines, +1 for the current input line,
195      and remember those lines. */
196   lines = (char **)xmalloc ((1 + (hlen = i)) * sizeof (char *));
197   for (i = 0; i < hlen; i++)
198     lines[i] = hlist[i]->line;
199 
200   if (saved_line_for_history)
201     lines[i] = saved_line_for_history->line;
202   else
203     {
204       /* Keep track of this so we can free it. */
205       allocated_line = xmalloc (1 + strlen (rl_line_buffer));
206       strcpy (allocated_line, &rl_line_buffer[0]);
207       lines[i] = allocated_line;
208     }
209 
210   hlen++;
211 
212   /* The line where we start the search. */
213   i = orig_line;
214 
215   rl_save_prompt ();
216 
217   /* Initialize search parameters. */
218   search_string = xmalloc (search_string_size = 128);
219   *search_string = '\0';
220   search_string_index = 0;
221   prev_line_found = (char *)0;		/* XXX */
222 
223   /* Normalize DIRECTION into 1 or -1. */
224   direction = (direction >= 0) ? 1 : -1;
225 
226   rl_display_search (search_string, reverse, -1);
227 
228   sline = rl_line_buffer;
229   sline_len = strlen (sline);
230   line_index = rl_point;
231 
232   found = failed = 0;
233   for (;;)
234     {
235       Function *f = (Function *)NULL;
236 
237       /* Read a key and decide how to proceed. */
238       c = rl_read_key ();
239 
240       if (_rl_keymap[c].type == ISFUNC)
241 	{
242 	  f = _rl_keymap[c].function;
243 
244 	  if (f == rl_reverse_search_history)
245 	    c = reverse ? -1 : -2;
246 	  else if (f == rl_forward_search_history)
247 	    c =  !reverse ? -1 : -2;
248 	}
249 
250 #if 0
251       /* Let NEWLINE (^J) terminate the search for people who don't like
252 	 using ESC.  ^M can still be used to terminate the search and
253 	 immediately execute the command. */
254       if (c == ESC || c == NEWLINE)
255 #else
256       /* The characters in isearch_terminators (set from the user-settable
257 	 variable isearch-terminators) are used to terminate the search but
258 	 not subsequently execute the character as a command.  The default
259 	 value is "\033\012" (ESC and C-J). */
260       if (strchr (isearch_terminators, c))
261 #endif
262 	{
263 	  /* ESC still terminates the search, but if there is pending
264 	     input or if input arrives within 0.1 seconds (on systems
265 	     with select(2)) it is used as a prefix character
266 	     with rl_execute_next.  WATCH OUT FOR THIS!  This is intended
267 	     to allow the arrow keys to be used like ^F and ^B are used
268 	     to terminate the search and execute the movement command. */
269 	  if (c == ESC && _rl_input_available ())	/* XXX */
270 	    rl_execute_next (ESC);
271 	  break;
272 	}
273 
274       if (c >= 0 && (CTRL_CHAR (c) || META_CHAR (c) || c == RUBOUT) && c != CTRL ('G'))
275 	{
276 	  rl_execute_next (c);
277 	  break;
278 	}
279 
280       switch (c)
281 	{
282 	case -1:
283 	  if (search_string_index == 0)
284 	    continue;
285 	  else if (reverse)
286 	    --line_index;
287 	  else if (line_index != sline_len)
288 	    ++line_index;
289 	  else
290 	    ding ();
291 	  break;
292 
293 	  /* switch directions */
294 	case -2:
295 	  direction = -direction;
296 	  reverse = direction < 0;
297 	  break;
298 
299 	case CTRL ('G'):
300 	  strcpy (rl_line_buffer, lines[orig_line]);
301 	  rl_point = orig_point;
302 	  rl_end = strlen (rl_line_buffer);
303 	  rl_restore_prompt();
304 	  rl_clear_message ();
305 	  if (allocated_line)
306 	    free (allocated_line);
307 	  free (lines);
308 	  return 0;
309 
310 #if 0
311 	/* delete character from search string. */
312 	case -3:
313 	  if (search_string_index == 0)
314 	    ding ();
315 	  else
316 	    {
317 	      search_string[--search_string_index] = '\0';
318 	      /* This is tricky.  To do this right, we need to keep a
319 		 stack of search positions for the current search, with
320 		 sentinels marking the beginning and end. */
321 	    }
322 	  break;
323 #endif
324 
325 	default:
326 	  /* Add character to search string and continue search. */
327 	  if (search_string_index + 2 >= search_string_size)
328 	    {
329 	      search_string_size += 128;
330 	      search_string = xrealloc (search_string, search_string_size);
331 	    }
332 	  search_string[search_string_index++] = c;
333 	  search_string[search_string_index] = '\0';
334 	  break;
335 	}
336 
337       for (found = failed = 0;;)
338 	{
339 	  int limit = sline_len - search_string_index + 1;
340 
341 	  /* Search the current line. */
342 	  while (reverse ? (line_index >= 0) : (line_index < limit))
343 	    {
344 	      if (STREQN (search_string, sline + line_index, search_string_index))
345 		{
346 		  found++;
347 		  break;
348 		}
349 	      else
350 		line_index += direction;
351 	    }
352 	  if (found)
353 	    break;
354 
355 	  /* Move to the next line, but skip new copies of the line
356 	     we just found and lines shorter than the string we're
357 	     searching for. */
358 	  do
359 	    {
360 	      /* Move to the next line. */
361 	      i += direction;
362 
363 	      /* At limit for direction? */
364 	      if (reverse ? (i < 0) : (i == hlen))
365 		{
366 		  failed++;
367 		  break;
368 		}
369 
370 	      /* We will need these later. */
371 	      sline = lines[i];
372 	      sline_len = strlen (sline);
373 	    }
374 	  while ((prev_line_found && STREQ (prev_line_found, lines[i])) ||
375 		 (search_string_index > sline_len));
376 
377 	  if (failed)
378 	    break;
379 
380 	  /* Now set up the line for searching... */
381 	  line_index = reverse ? sline_len - search_string_index : 0;
382 	}
383 
384       if (failed)
385 	{
386 	  /* We cannot find the search string.  Ding the bell. */
387 	  ding ();
388 	  i = last_found_line;
389 	  continue; 		/* XXX - was break */
390 	}
391 
392       /* We have found the search string.  Just display it.  But don't
393 	 actually move there in the history list until the user accepts
394 	 the location. */
395       if (found)
396 	{
397 	  int line_len;
398 
399 	  prev_line_found = lines[i];
400 	  line_len = strlen (lines[i]);
401 
402 	  if (line_len >= rl_line_buffer_len)
403 	    rl_extend_line_buffer (line_len);
404 
405 	  strcpy (rl_line_buffer, lines[i]);
406 	  rl_point = line_index;
407 	  rl_end = line_len;
408 	  last_found_line = i;
409 	  rl_display_search (search_string, reverse, (i == orig_line) ? -1 : i);
410 	}
411     }
412 
413   /* The searching is over.  The user may have found the string that she
414      was looking for, or else she may have exited a failing search.  If
415      LINE_INDEX is -1, then that shows that the string searched for was
416      not found.  We use this to determine where to place rl_point. */
417 
418   /* First put back the original state. */
419   strcpy (rl_line_buffer, lines[orig_line]);
420 
421   rl_restore_prompt ();
422 
423   /* Free the search string. */
424   free (search_string);
425 
426   if (last_found_line < orig_line)
427     rl_get_previous_history (orig_line - last_found_line, 0);
428   else
429     rl_get_next_history (last_found_line - orig_line, 0);
430 
431   /* If the string was not found, put point at the end of the line. */
432   if (line_index < 0)
433     line_index = strlen (rl_line_buffer);
434   rl_point = line_index;
435   rl_clear_message ();
436 
437   if (allocated_line)
438     free (allocated_line);
439   free (lines);
440 
441   return 0;
442 }
443