1 /*
2 * help.c: handles the help stuff for irc
3 *
4 * Written by Michael Sandrof
5 * Extensively modified by Troy Rollo
6 * Re-modified by Matthew Green
7 *
8 * Copyright(c) 1992
9 *
10 * See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT
11 */
12
13 /*
14 * This has been replaced almost entirely from the original by Michael
15 * Sandrof in order to fit in with the multiple screen code.
16 *
17 * ugh, this wasn't easy to do, but I got there, after working out what
18 * had been changed and why, by myself - phone, October 1992.
19 *
20 * And when I started getting /window create working, I discovered new
21 * bugs, and there has been a few more major changes in here again.
22 * It is invalid to call help from more than one screen, at the moment,
23 * because there is to much to keep track of - phone, jan 1993.
24 */
25
26 #if 0
27 static char rcsid[] = "@(#)$Id: help.c 432 2013-11-07 03:00:24Z tcava $";
28 #endif
29
30 #include "irc.h"
31 static char cvsrevision[] = "$Id: help.c 432 2013-11-07 03:00:24Z tcava $";
32 CVS_REVISION(help_c)
33
34 #ifdef WANT_EPICHELP
35 #include "struct.h"
36 #include "help.h"
37 #include "input.h"
38 #include "ircaux.h"
39 #include "hook.h"
40 #include "output.h"
41 #include "screen.h"
42 #include "server.h"
43 #include "ircterm.h"
44 #include "vars.h"
45 #include "window.h"
46 #include <sys/stat.h>
47 #include "bsdglob.h"
48 #define MAIN_SOURCE
49 #include "modval.h"
50
51 /* Forward declarations */
52
53 static void help_me (char *, char *);
54 static void help_show_paused_topic (char *, char *);
55 static void create_help_window (void);
56 static void set_help_screen (Screen *);
57 static void help_put_it (const char *topic, const char *format, ...);
58
59 /*
60 * A few variables here - A lot added to get help working with
61 * non - recursive calls to irc_io, and also have it still
62 * reading things from the server(s), so not to ping timeout.
63 */
64 static int dont_pause_topic = 0;
65 static int entry_size;
66 static int finished_help_paging = 0;
67 static FILE * help_fp;
68 #define HELP_PAUSED_LINES_MAX 500
69 static int help_paused_lines = 0;
70 static char * help_paused_topic[HELP_PAUSED_LINES_MAX]; /* Should be enough */
71 static Screen *help_screen = (Screen *) 0;
72 static int help_show_directory = 0;
73 static char help_topic_list[BIG_BUFFER_SIZE + 1];
74 static Window *help_window = (Window *) 0;
75 static char no_help[] = "NOHELP";
76 static char paused_topic[BIG_BUFFER_SIZE + 1];
77 static char * this_arg;
78 static int use_help_window = 0;
79
80
81 /*
82 * show_help: show's either a page of text from a help_fp, or the whole
83 * thing, depending on the value of HELP_PAGER_VAR. If it gets to the end,
84 * (in either case it will eventally), it closes the file, and returns 0
85 * to indicate this.
86 */
show_help(Window * window,char * name)87 static int show_help (Window *window, char *name)
88 {
89 Window *old_target_window = target_window;
90 int rows = 0;
91 char line[256];
92
93 target_window = window ? window : current_window;
94
95 if (get_int_var(HELP_PAGER_VAR))
96 rows = window->display_size;
97
98 while (rows)
99 {
100 if (!fgets(line, 255, help_fp))
101 {
102 fclose(help_fp);
103 help_fp = NULL;
104 target_window = old_target_window;
105 return 0;
106 }
107
108 if (*(line + strlen(line) - 1) == '\n')
109 *(line + strlen(line) - 1) = (char) 0;
110
111 /*
112 * This is for compatability with ircII-4.4
113 */
114 if (*line == '!' || *line == '#')
115 continue;
116
117 help_put_it(name, "%s", line);
118 rows--;
119 }
120
121 target_window = old_target_window;
122 return (1);
123 }
124
125 /*
126 * help_prompt: The main procedure called to display the help file
127 * currently being accessed. Using add_wait_prompt(), it sets it
128 * self up to be recalled when the next page is asked for. If
129 * called when we have finished paging the help file, we exit, as
130 * there is nothing left to show. If line is 'q' or 'Q', exit the
131 * help pager, clean up, etc.. If all is cool for now, we call
132 * show_help, and either if its finished, exit, or prompt for the
133 * next page. From here, if we've finished the help page, and
134 * doing help prompts, prompt for the help..
135 */
help_prompt(char * name,char * line)136 static void help_prompt (char *name, char *line)
137 {
138 if (finished_help_paging)
139 {
140 if (*paused_topic)
141 help_show_paused_topic(paused_topic, empty_string);
142 return;
143 }
144
145 if (line && toupper(*line) == 'Q')
146 {
147 finished_help_paging = 1;
148 #if 0
149 help_paused_lines = 0; /* Thanks robo */
150 #endif
151 fclose(help_fp);
152 help_fp = NULL;
153 set_help_screen((Screen *) 0);
154 return;
155 }
156
157 if (show_help(help_window, name))
158 {
159 if (dumb_mode)
160 help_prompt(name, NULL);
161 else
162 add_wait_prompt("*** Hit any key for more, 'q' to quit ***",
163 help_prompt, name, WAIT_PROMPT_KEY, 1);
164 }
165 else
166 {
167 finished_help_paging = 1;
168 if (help_fp)
169 fclose(help_fp);
170 help_fp = NULL;
171
172 if (help_show_directory)
173 {
174 if (get_int_var(HELP_PAGER_VAR))
175 {
176 if (dumb_mode)
177 help_show_paused_topic(name, empty_string);
178 else
179 add_wait_prompt("*** Hit any key to end ***",
180 help_show_paused_topic, paused_topic,
181 WAIT_PROMPT_KEY, 1);
182 }
183 else
184 {
185 help_show_paused_topic(paused_topic, empty_string);
186 set_help_screen((Screen *) 0);
187 }
188 help_show_directory = 0;
189 return;
190 }
191 }
192
193 if (finished_help_paging)
194 {
195 if (get_int_var(HELP_PROMPT_VAR))
196 {
197 char tmp[BIG_BUFFER_SIZE + 1];
198
199 sprintf(tmp, "%s%sHelp? ", help_topic_list,
200 *help_topic_list ? space : empty_string);
201 if (!dumb_mode)
202 add_wait_prompt(tmp, help_me, help_topic_list,
203 WAIT_PROMPT_LINE, 1);
204 }
205 else
206 {
207 if (*paused_topic)
208 help_show_paused_topic(paused_topic, empty_string);
209 set_help_screen((Screen *) 0);
210 }
211 }
212 }
213
214 /*
215 * help_topic: Given a topic, we search the help directory, and try to
216 * find the right file, if all is cool, and we can open it, or zcat it,
217 * then we call help_prompt to get the actually displaying of the file
218 * on the road.
219 */
help_topic(char * path,char * name)220 static void help_topic (char *path, char *name)
221 {
222 char *filename = NULL;
223
224 if (!name)
225 return;
226
227 /* what is the base name? */
228 filename = m_sprintf("%s/%s", path, name);
229 if (filename[strlen(filename)-1] == '/')
230 chop(filename, 1);
231
232 /* let uzfopen have all the fun */
233 if ((help_fp = uzfopen (&filename, path, 0)))
234 {
235 /* Isnt this a heck of a lot better then the kludge you were using? */
236 help_put_it(name, "*** Help on %s", name);
237 help_prompt(name, NULL);
238 }
239 else
240 help_put_it (name, "*** No help available on %s: Use ? for list of topics", name);
241
242 new_free(&filename);
243 return;
244 }
245
246 /*
247 * help_pause_add_line: this procedure does a help_put_it() call, but
248 * puts off the calling, until help_show_paused_topic() is called.
249 * I do this because I need to create the list of help topics, but
250 * not show them, until we've seen the whole file, so we called
251 * help_show_paused_topic() when we've seen the file, if it is needed.
252 */
help_pause_add_line(char * format,...)253 static void help_pause_add_line (char *format, ...)
254 {
255 char buf[BIG_BUFFER_SIZE];
256 va_list args;
257
258 va_start (args, format);
259 vsnprintf(buf, BIG_BUFFER_SIZE - 1, format, args);
260 va_end (args);
261 if ((help_paused_lines + 1) >= HELP_PAUSED_LINES_MAX)
262 ircpanic("help_pause_add_line: would overflow the buffer");
263 malloc_strcpy(&help_paused_topic[help_paused_lines++], buf);
264 }
265
266 /*
267 * help_show_paused_topic: see above. Called when we've seen the
268 * whole help file, and we have a list of topics to display.
269 */
help_show_paused_topic(char * name,char * line)270 static void help_show_paused_topic (char *name, char *line)
271 {
272 static int i = 0;
273 int j = 0;
274 int rows;
275
276 if (!help_paused_lines)
277 return;
278
279 if (toupper(*line) == 'Q')
280 i = help_paused_lines + 1; /* just big enough */
281
282 rows = help_window->display_size;
283 if (i < help_paused_lines)
284 {
285 for (j = 0; j < rows; j++)
286 {
287 help_put_it (name, "%s", help_paused_topic[i]);
288 new_free(&help_paused_topic[i]);
289
290 /* if we're done, the recurse to break loop */
291 if (++i >= help_paused_lines)
292 break;
293 }
294 if (!dumb_mode)
295 {
296 if ((i < help_paused_lines) && get_int_var(HELP_PAGER_VAR))
297 add_wait_prompt("[MORE]", help_show_paused_topic, name, WAIT_PROMPT_KEY, 1);
298 }
299 else
300 help_show_paused_topic(name, line);
301 }
302
303 /*
304 * This cant be an else of the previous if because 'i' can
305 * change in the previous if and we need to test it again
306 */
307 if (i >= help_paused_lines)
308 {
309 if (get_int_var(HELP_PROMPT_VAR))
310 {
311 char buf[BIG_BUFFER_SIZE];
312
313 sprintf(buf, "%s%sHelp? ", name, (name && *name) ? space : empty_string);
314 if (!dumb_mode)
315 add_wait_prompt(buf, help_me, name, WAIT_PROMPT_LINE, 1);
316 }
317 else
318 set_help_screen((Screen *) 0);
319
320 dont_pause_topic = 0;
321 help_paused_lines = 0; /* Probably should reset this ;-) */
322 i = 0;
323 }
324 }
325
326 /*
327 * help_me: The big one. The help procedure that handles working out
328 * what was actually requested, sets up the paused topic list if it is
329 * needed, does pretty much all the hard work.
330 */
help_me(char * topics,char * args)331 static void help_me (char *topics, char *args)
332 {
333 char * ptr;
334 glob_t g;
335 int entries = 0,
336 cnt,
337 i,
338 cols;
339 struct stat stat_buf;
340 char path[BIG_BUFFER_SIZE+1];
341 int help_paused_first_call = 0;
342 char * help_paused_path = (char *) 0;
343 char * help_paused_name = (char *) 0;
344 char * temp;
345 char tmp[BIG_BUFFER_SIZE+1];
346 char buffer[BIG_BUFFER_SIZE+1];
347 char * pattern = NULL;
348
349 strcpy(help_topic_list, topics);
350 ptr = get_string_var(HELP_PATH_VAR);
351
352 sprintf(path, "%s/%s", ptr, topics);
353 for (ptr = path; (ptr = strchr(ptr, ' '));)
354 *ptr = '/';
355
356 /*
357 * first we check access to the help dir, whinge if we can't, then
358 * work out we need to ask them for more help, else we check the
359 * args list, and do the stuff
360 */
361 if (help_show_directory)
362 {
363 help_show_paused_topic(paused_topic, empty_string);
364 help_show_directory = 0;
365 }
366
367 finished_help_paging = 0;
368 if (access(path, R_OK|X_OK))
369 {
370 help_put_it(no_help, "*** Cannot access help directory!");
371 set_help_screen((Screen *) 0);
372 return;
373 }
374
375 this_arg = next_arg(args, &args);
376 if (!this_arg && *help_topic_list && get_int_var(HELP_PROMPT_VAR))
377 {
378 if ((temp = strrchr(help_topic_list, ' ')) != NULL)
379 *temp = '\0';
380 else
381 *help_topic_list = '\0';
382
383 sprintf(tmp, "%s%sHelp? ", help_topic_list, *help_topic_list ? space : empty_string);
384
385 if (!dumb_mode)
386 add_wait_prompt(tmp, help_me, help_topic_list, WAIT_PROMPT_LINE, 1);
387 return;
388 }
389
390 if (!this_arg)
391 {
392 set_help_screen((Screen *) 0);
393 return;
394 }
395
396 create_help_window();
397
398 /*
399 * This is just a bogus while loop which is intended to allow
400 * the user to do '/help alias expressions' without having to
401 * include a slash inbetween the topic and subtopic.
402 *
403 * If all goes well, we 'break' at the bottom of the loop.
404 */
405 while (this_arg)
406 {
407 entries = 0;
408 reset_display_target();
409
410 if (!*this_arg)
411 help_topic(path, NULL);
412
413 if (strcmp(this_arg, "?") == 0)
414 {
415 this_arg = empty_string;
416 if (!dont_pause_topic)
417 dont_pause_topic = 1;
418 }
419
420 /*
421 * entry_size is set to the width of the longest help topic
422 * (adjusted for compression extensions, of course.)
423 */
424 entry_size = 0;
425
426 /*
427 * Gather up the names of the files in the help directory.
428 */
429 {
430 #ifndef HAVE_FCHDIR
431 char opath[MAXPATHLEN + 1];
432 getcwd(opath, MAXPATHLEN);
433 #else
434 int cwd = open(".", O_RDONLY);
435 #endif
436
437 chdir(path);
438 pattern = alloca(strlen(path) + 2 +
439 strlen(this_arg) + 3);
440 strcpy(pattern, this_arg);
441 strcat(pattern, "*");
442 #ifdef GLOB_INSENSITIVE
443 bsd_glob(pattern, GLOB_INSENSITIVE /* GLOB_MARK */, NULL, &g);
444 #else
445 bsd_glob(pattern, 0 /* GLOB_MARK */, NULL, &g);
446 #endif
447 #ifndef HAVE_FCHDIR
448 chdir(opath);
449 #else
450 fchdir(cwd);
451 close(cwd);
452 #endif
453 }
454
455 for (i = 0; i < g.gl_matchc; i++)
456 {
457 char *tmp = g.gl_pathv[i];
458 int len = strlen(tmp);
459
460 if (!end_strcmp(tmp, ".gz", 3))
461 len -= 3;
462 else if (!end_strcmp(tmp, ".bz2", 4))
463 len -= 4;
464 entry_size = (len > entry_size) ? len : entry_size;
465 }
466
467 /*
468 * Right here we need to check for an 'exact match'.
469 * An 'exact match' would be sitting in gl_pathv[0],
470 * and it is 'exact' if it is identical to what we are
471 * looking for, or if it is the same except that it has
472 * a compression extension on it
473 */
474 if (g.gl_matchc > 1)
475 {
476 char *str1 = g.gl_pathv[0];
477 char *str2 = this_arg;
478 int len1 = strlen(str1);
479 int len2 = strlen(str2);
480
481
482 if (len1 == len2 && !my_stricmp(str1, str2))
483 entries = 1;
484 else if (len1 - 3 == len2 && !my_strnicmp(str1, str2, len2) && !end_strcmp(str1, ".gz", 3))
485 entries = 1;
486 else if (len1 - 2 == len2 && !my_strnicmp(str1, str2, len2) && !end_strcmp(str1, ".Z", 2))
487 entries = 1;
488 else if (len1 - 2 == len2 && !my_strnicmp(str1, str2, len2) && !end_strcmp(str1, ".z", 2))
489 entries = 1;
490 }
491
492 if (!*help_topic_list)
493 dont_pause_topic = 1;
494
495 /* reformatted */
496 /*
497 * entries: -1 means something really died, 0 means there
498 * was no help, 1, means it wasn't a directory, and so to
499 * show the help file, and the default means to add the
500 * stuff to the paused topic list..
501 */
502 if (!entries)
503 entries = g.gl_matchc;
504
505 switch (entries)
506 {
507 case -1:
508 {
509 help_put_it(no_help, "*** Error during help function: %s", strerror(errno));
510 set_help_screen(NULL);
511 if (help_paused_first_call)
512 {
513 help_topic(help_paused_path, help_paused_name);
514 help_paused_first_call = 0;
515 new_free(&help_paused_path);
516 new_free(&help_paused_name);
517 }
518 return;
519 }
520 case 0:
521 {
522 help_put_it(this_arg, "*** No help available on %s: Use ? for list of topics", this_arg);
523 if (!get_int_var(HELP_PROMPT_VAR))
524 {
525 set_help_screen(NULL);
526 break;
527 }
528 sprintf(tmp, "%s%sHelp? ", help_topic_list, *help_topic_list ? space : empty_string);
529 if (!dumb_mode)
530 add_wait_prompt(tmp, help_me, help_topic_list, WAIT_PROMPT_LINE, 1);
531
532 if (help_paused_first_call)
533 {
534 help_topic(help_paused_path, help_paused_name);
535 help_paused_first_call = 0;
536 new_free(&help_paused_path);
537 new_free(&help_paused_name);
538 }
539
540 break;
541 }
542 case 1:
543 {
544 sprintf(tmp, "%s/%s", path, g.gl_pathv[0]);
545 stat(tmp, &stat_buf);
546 if (stat_buf.st_mode & S_IFDIR)
547 {
548 strcpy(path, tmp);
549 if (*help_topic_list)
550 strcat(help_topic_list, space);
551
552 strcat(help_topic_list, g.gl_pathv[0]);
553
554 if (!(this_arg = next_arg(args, &args)))
555 {
556 help_paused_first_call = 1;
557 malloc_strcpy(&help_paused_path, path);
558 malloc_strcpy(&help_paused_name, g.gl_pathv[0]);
559 dont_pause_topic = -1;
560 this_arg = "?";
561 }
562 bsd_globfree(&g);
563 continue;
564 }
565 else
566 {
567 help_topic(path, g.gl_pathv[0]);
568 finished_help_paging = 0;
569 break;
570 }
571 }
572 default:
573 {
574 help_show_directory = 1;
575 strcpy(paused_topic, help_topic_list);
576 help_pause_add_line("*** %s choices:", help_topic_list);
577 entry_size += 2;
578 cols = (current_term->TI_cols - 10) / entry_size;
579
580 strcpy(buffer, empty_string);
581 cnt = 0;
582
583 for (i = 0; i < entries; i++)
584 {
585 if (!end_strcmp(g.gl_pathv[i], ".gz", 3))
586 chop(g.gl_pathv[i], 3);
587 else if (!end_strcmp(g.gl_pathv[i], ".bz2", 4))
588 chop(g.gl_pathv[i], 4);
589 strcat(buffer, g.gl_pathv[i]);
590
591 /*
592 * Since we already know how many columns each
593 * line will contain, we check to see if we have
594 * accumulated that many entries. If we have, we
595 * output the line to the screen.
596 */
597 if (++cnt == cols)
598 {
599 help_pause_add_line("%s", buffer);
600 strcpy(buffer, empty_string);
601 cnt = 0;
602 }
603
604 /*
605 * If we have not finished this line, then we have
606 * to pad the name length out to the expected width.
607 * 'entry_size' is the column width. We also have
608 * do adjust for compression extension.
609 */
610 else
611 strextend(buffer, ' ', entry_size - strlen(g.gl_pathv[i]));
612 }
613
614 help_pause_add_line("%s", buffer);
615 if (help_paused_first_call)
616 {
617 help_topic(help_paused_path, help_paused_name);
618 help_paused_first_call = 0;
619 new_free(&help_paused_path);
620 new_free(&help_paused_name);
621 }
622 if (dont_pause_topic == 1)
623 {
624 help_show_paused_topic(paused_topic, empty_string);
625 help_show_directory = 0;
626 }
627 break;
628 }
629 }
630 /* end of reformatting */
631
632
633 bsd_globfree(&g);
634 break;
635 }
636
637 /*
638 * This one is for when there was never a topic and the prompt
639 * never got a topic.. and help_screen was never reset..
640 * phone, jan 1993.
641 */
642 if (!*help_topic_list && finished_help_paging)
643 set_help_screen((Screen *) 0);
644 }
645
646 /*
647 * help: the HELP command, gives help listings for any and all topics out
648 * there
649 */
BUILT_IN_COMMAND(epichelp)650 BUILT_IN_COMMAND(epichelp)
651 {
652 char *help_path;
653
654 finished_help_paging = 0;
655 help_show_directory = 0;
656 dont_pause_topic = 0;
657 use_help_window = 0;
658
659 /*
660 * The idea here is to work out what sort of help we are using -
661 * either the installed help files, or some help service, what
662 * ever it maybe. Once we have worked this out, if we are using
663 * a help window, set it up properly.
664 */
665 help_path = get_string_var(HELP_PATH_VAR);
666
667 if (!help_path || !*help_path || access(help_path, R_OK | X_OK))
668 {
669 help_put_it(no_help, "*** HELP_PATH variable not set or set to an invalid path");
670 return;
671 }
672
673 /* Allow us to wait until help is finished */
674 if (!my_strnicmp(args, "-wait", 2))
675 {
676 while (help_screen)
677 io("help");
678 return;
679 }
680
681 if (help_path && help_screen && help_screen != current_window->screen)
682 {
683 say("You may not run help in two screens");
684 return;
685 }
686
687 help_screen = current_window->screen;
688 help_window = (Window *) 0;
689 help_me(empty_string, (args && *args) ? args : "?");
690 }
691
692
693
694
create_help_window(void)695 static void create_help_window (void)
696 {
697 if (help_window)
698 return;
699
700 if (!dumb_mode && get_int_var(HELP_WINDOW_VAR))
701 {
702 use_help_window = 1;
703 help_window = new_window(current_window->screen);
704 help_window->hold_mode = OFF;
705 help_window->window_level = LOG_HELP;
706 update_all_windows();
707 }
708 else
709 help_window = current_window;
710 }
711
712
713
set_help_screen(Screen * screen)714 static void set_help_screen (Screen *screen)
715 {
716 help_screen = screen;
717 if (!help_screen && help_window)
718 {
719 if (use_help_window)
720 {
721 int display = window_display;
722
723 window_display = 0;
724 delete_window(help_window);
725 window_display = display;
726 }
727 help_window = (Window *) 0;
728 update_all_windows();
729 }
730 }
731
help_put_it(const char * topic,const char * format,...)732 static void help_put_it (const char *topic, const char *format, ...)
733 {
734 char putbuf[BIG_BUFFER_SIZE * 3 + 1];
735
736 if (format)
737 {
738 va_list args;
739 va_start (args, format);
740 vsnprintf(putbuf, BIG_BUFFER_SIZE * 3, format, args);
741 va_end(args);
742
743 if (do_hook(HELP_LIST, "%s %s", topic, putbuf))
744 {
745 int old_level = who_level;
746 Window *old_target_window = target_window;
747
748 /*
749 * LOG_HELP is a completely bogus mode. We use
750 * it only to make sure that the current level is
751 * not LOG_CURRENT, so that the to_window will stick.
752 */
753 who_level = LOG_HELP;
754 if (help_window)
755 target_window = help_window;
756 add_to_screen(putbuf);
757 target_window = old_target_window;
758 who_level = old_level;
759 }
760 }
761 }
762 #endif
763
764