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