search.c revision 1abf7346
1/* $XConsortium: search.c,v 1.21 94/04/17 20:43:58 rws Exp $ */
2/*
3
4Copyright (c) 1987, 1988  X Consortium
5
6Permission is hereby granted, free of charge, to any person obtaining
7a copy of this software and associated documentation files (the
8"Software"), to deal in the Software without restriction, including
9without limitation the rights to use, copy, modify, merge, publish,
10distribute, sublicense, and/or sell copies of the Software, and to
11permit persons to whom the Software is furnished to do so, subject to
12the following conditions:
13
14The above copyright notice and this permission notice shall be included
15in all copies or substantial portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR
21OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
22ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23OTHER DEALINGS IN THE SOFTWARE.
24
25Except as contained in this notice, the name of the X Consortium shall
26not be used in advertising or otherwise to promote the sale, use or
27other dealings in this Software without prior written authorization
28from the X Consortium.
29
30*/
31/* $XFree86: xc/programs/xman/search.c,v 1.5 2001/01/27 17:24:27 herrb Exp $ */
32
33
34#include "globals.h"
35#include "vendor.h"
36
37/* Map <CR> and control-M to goto begining of file. */
38
39#define SEARCHARGS 10
40
41static FILE * DoManualSearch(ManpageGlobals *man_globals, char * string);
42static int BEntrySearch(char * string, char ** first, int number);
43
44/*	Function Name: MakeSearchWidget
45 *	Description: This Function Creates the Search Widget.
46 *	Arguments: man_globals - the pseudo globas for this manpage.
47 *                 w - the widgets parent
48 *	Returns: the search widget.
49 */
50
51void
52MakeSearchWidget(ManpageGlobals * man_globals, Widget parent)
53{
54  Widget dialog, command, text, cancel;
55  Arg arglist[2];
56  Cardinal num_args = 0;
57
58  XtSetArg(arglist[0], XtNtransientFor, parent);
59  man_globals->search_widget = XtCreatePopupShell(SEARCHNAME,
60						  transientShellWidgetClass,
61						  parent,
62						  arglist, 1);
63
64  if (resources.clear_search_string) {
65    XtSetArg(arglist[0], XtNvalue, ""); num_args++;
66  }
67
68  dialog = XtCreateManagedWidget(DIALOG, dialogWidgetClass,
69				 man_globals->search_widget,
70				 arglist, num_args);
71
72  if ( (text = XtNameToWidget(dialog, "value")) == (Widget) NULL)
73    PopupWarning(NULL, "Could not find text widget in MakeSearchWidget.");
74  else
75    XtSetKeyboardFocus(dialog, text);
76
77  XawDialogAddButton(dialog, MANUALSEARCH, NULL, NULL);
78  XawDialogAddButton(dialog, APROPOSSEARCH, NULL, NULL);
79  XawDialogAddButton(dialog, CANCEL, NULL, NULL);
80
81/*
82 * This is a bit gross, but it get the cancel button underneath the
83 * others, and forms them up to the right size..
84 */
85
86  if ( ((command = XtNameToWidget(dialog, MANUALSEARCH)) == (Widget) NULL) ||
87       ((cancel = XtNameToWidget(dialog, CANCEL)) == (Widget) NULL) )
88    PopupWarning(NULL,
89		 "Could not find manual search widget in MakeSearchWidget.");
90  else {
91    static char * half_size[] = {
92      MANUALSEARCH, APROPOSSEARCH, NULL
93    };
94    static char * full_size[] = {
95      "label", "value", CANCEL, NULL
96    };
97
98    num_args = 0;
99    XtSetArg(arglist[num_args], XtNfromVert, command); num_args++;
100    XtSetArg(arglist[num_args], XtNfromHoriz, NULL); num_args++;
101    XtSetValues(cancel, arglist, num_args);
102    FormUpWidgets(dialog, full_size, half_size);
103  }
104
105}
106
107/*      Function Name: SearchString
108 *      Description: Returns the search string.
109 *      Arguments: man_globals - the globals.
110 *      Returns: the search string.
111 */
112
113static char *
114SearchString(
115ManpageGlobals * man_globals)
116{
117  Widget dialog;
118
119  dialog = XtNameToWidget(man_globals->search_widget, DIALOG);
120  if (dialog != NULL)
121    return(XawDialogGetValueString(dialog));
122
123  PopupWarning(man_globals,
124	      "Could not get the search string, no search will be preformed.");
125  return(NULL);
126}
127
128
129/*	Function Name: DoSearch
130 *	Description: This function performs a search for a man page or apropos
131 *                   search upon search string.
132 *	Arguments: man_globals - the pseudo globas for this manpage.
133 *                 type - the type of search.
134 *	Returns: none.
135 */
136
137#define LOOKLINES 6
138
139/*
140 * Manual searches look through the list of manual pages for the right one
141 * with a binary search.
142 *
143 * Apropos searches still exec man -k.
144 *
145 * If nothing is found then I send a warning message to the user, and do
146 * nothing.
147 */
148
149FILE *
150DoSearch(ManpageGlobals * man_globals, int type)
151{
152  char cmdbuf[BUFSIZ],*mantmp, *manpath;
153  char tmp[BUFSIZ],path[BUFSIZ];
154  char string_buf[BUFSIZ], cmp_str[BUFSIZ], error_buf[BUFSIZ];
155  char * search_string = SearchString(man_globals);
156  FILE * file;
157#ifdef HAS_MKSTEMP
158  int fd;
159#endif
160  int count;
161  Boolean flag;
162
163  if (search_string == NULL) return(NULL);
164
165  /* If the string is empty or starts with a space then do not search */
166
167  if ( streq(search_string,"") ) {
168    PopupWarning(man_globals, "Search string is empty.");
169    return(NULL);
170  }
171
172  if (strlen(search_string) >= BUFSIZ) {
173    PopupWarning(man_globals, "Search string too long.");
174    return(NULL);
175  }
176  if (search_string[0] == ' ') {
177    PopupWarning(man_globals, "First character cannot be a space.");
178    return(NULL);
179  }
180
181  if (type == APROPOS) {
182    char label[BUFSIZ];
183
184    strcpy(tmp, MANTEMP);		/* get a temp file. */
185#ifdef HAS_MKSTEMP
186    fd = mkstemp(tmp);
187    if (fd < 0) {
188      PopupWarning(man_globals, "Cant create temp file");
189      return NULL;
190    }
191#else
192    (void)mktemp(tmp);
193#endif
194    mantmp = tmp;
195
196    manpath=getenv("MANPATH");
197    if (manpath == NULL || streq(manpath,"") ) {
198#ifdef MANCONF
199      if (!ReadManConfig(path))
200#endif
201      {
202	strcpy(path,SYSMANPATH);
203#ifdef LOCALMANPATH
204	strcat(path,":");
205	strcat(path,LOCALMANPATH);
206#endif
207      }
208    } else {
209      strcpy(path,manpath);
210    }
211
212    snprintf(label, sizeof(label),
213	"Results of apropos search on: %s", search_string);
214
215#ifdef NO_MANPATH_SUPPORT	/* not quite correct, but the best I can do. */
216    snprintf(cmdbuf, sizeof(cmdbuf), APROPOS_FORMAT, search_string, mantmp);
217#else
218    snprintf(cmdbuf, sizeof(cmdbuf), APROPOS_FORMAT, path, search_string, mantmp);
219#endif
220
221    if(system(cmdbuf) != 0) {	/* execute search. */
222	snprintf(error_buf, sizeof(error_buf), "Something went wrong trying to run %s\n",cmdbuf);
223      PopupWarning(man_globals, error_buf);
224    }
225
226#ifdef HAS_MKSTEMP
227    if ((file = fdopen(fd, "r")) == NULL)
228#else
229    if((file = fopen(mantmp,"r")) == NULL)
230#endif
231      PrintError("lost temp file? out of temp space?");
232
233/*
234 * Since we keep the FD open we can unlink the file safely, this
235 * will keep extra files out of /tmp.
236 */
237
238    unlink(mantmp);
239
240    snprintf(string_buf, sizeof(string_buf), "%s: nothing appropriate", search_string);
241
242    /*
243     * Check first LOOKLINES lines for "nothing appropriate".
244     */
245
246    count = 0;
247    flag = FALSE;
248    while ( (fgets(cmp_str, BUFSIZ, file) != NULL) && (count < LOOKLINES) ) {
249      if ( cmp_str[strlen(cmp_str) - 1] == '\n') /* strip off the '\n' */
250	  cmp_str[strlen(cmp_str) - 1] = '\0';
251
252      if (streq(cmp_str, string_buf)) {
253	flag = TRUE;
254	break;
255      }
256      count++;
257    }
258
259    /*
260     * If the file is less than this number of lines then assume that there is
261     * nothing apropriate found. This does not confuse the apropos filter.
262     */
263
264    if (flag) {
265      fclose(file);
266      file = NULL;
267      ChangeLabel(man_globals->label,string_buf);
268      return(NULL);
269    }
270
271    snprintf(man_globals->manpage_title, sizeof(man_globals->manpage_title),
272	     "%s", label);
273    ChangeLabel(man_globals->label,label);
274    fseek(file, 0L, SEEK_SET);		/* reset file to point at top. */
275  }
276  else {			/* MANUAL SEACH */
277    file = DoManualSearch(man_globals, search_string);
278    if (file == NULL) {
279      snprintf(string_buf, sizeof(string_buf), "No manual entry for %s.", search_string);
280      ChangeLabel(man_globals->label, string_buf);
281      if (man_globals->label == NULL)
282	PopupWarning(man_globals, string_buf);
283      return(NULL);
284    }
285  }
286
287  if (resources.clear_search_string) {
288    Arg arglist[1];
289    Widget dialog;
290
291    dialog = XtNameToWidget(man_globals->search_widget, DIALOG);
292    if (dialog == NULL)
293      PopupWarning(man_globals, "Could not clear the search string.");
294
295    XtSetArg(arglist[0], XtNvalue, "");
296    XtSetValues(dialog, arglist, (Cardinal) 1);
297  }
298
299  return(file);
300}
301
302/*	Function Name: DoManualSearch
303 *	Description: performs a manual search.
304 *	Arguments: man_globals - the manual page specific globals.
305 *	Returns: the filename of the man page.
306 */
307
308#define NO_ENTRY -100
309
310static FILE *
311DoManualSearch(ManpageGlobals *man_globals, char * string)
312{
313  int e_num = NO_ENTRY;
314  int i;
315
316/* search current section first. */
317
318  i = man_globals->current_directory;
319  e_num = BEntrySearch(string, manual[i].entries, manual[i].nentries);
320
321/* search other sections. */
322
323  if (e_num == NO_ENTRY) {
324    i = 0;			/* At the exit of the loop i needs to
325				   be the one we used. */
326    while ( TRUE ) {
327      if (i == man_globals->current_directory)
328	if (++i >= sections) return(NULL);
329      e_num = BEntrySearch(string, manual[i].entries, manual[i].nentries);
330      if (e_num != NO_ENTRY) break;
331      if (++i >= sections) return(NULL);
332    }
333
334/*
335 * Manual page found in some other section, unhighlight the current one.
336 */
337    if ( man_globals->manpagewidgets.box != NULL)
338      XawListUnhighlight(
339	man_globals->manpagewidgets.box[man_globals->current_directory]);
340  }
341  else {
342    /*
343     * Highlight the element we are searching for if it is in the directory
344     * listing currently being shown.
345     */
346    if ( man_globals->manpagewidgets.box != NULL)
347      XawListHighlight(man_globals->manpagewidgets.box[i], e_num);
348  }
349  return(FindManualFile(man_globals, i, e_num));
350}
351
352/*	Function Name: BEntrySearch
353 *	Description: binary search through entries.
354 *	Arguments: string - the string to match.
355 *                 first - the first entry in the list.
356 *                 number - the number of entries.
357 *	Returns: a pointer to the entry found.
358 */
359
360static int
361BEntrySearch(
362char * string,
363char ** first,
364int number)
365{
366  int check, cmp, len_cmp, global_number;
367  char *head, *tail;
368
369  global_number = 0;
370  while (TRUE) {
371
372    if (number == 0) {
373      return(NO_ENTRY);		/* didn't find it. */
374    }
375
376    check = number/2;
377
378    head = rindex(first[ global_number + check ], '/');
379    if (head == NULL)
380      PrintError("index failure in BEntrySearch");
381    head++;
382
383    tail = rindex(head, '.');
384    if (tail == NULL)
385      /* not an error, some systems (e.g. sgi) have only a .z suffix */
386      tail = head + strlen(head);
387
388    cmp = strncmp(string, head, (tail - head));
389    len_cmp = strlen(string) - (int) (tail - head);
390
391    if ( cmp == 0 && len_cmp == 0) {
392      return(global_number + check);
393    }
394    else if ( cmp < 0 || ((cmp == 0) && (len_cmp < 0)) )
395      number = check;
396    else /* cmp > 0 || ((cmp == 0) && (len_cmp > 0)) */ {
397      global_number += (check + 1);
398      number -= ( check + 1 );
399    }
400  }
401}
402