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