/* * help.c: handles the help stuff for irc * * Written by Michael Sandrof * Extensively modified by Troy Rollo * Re-modified by Matthew Green * * Copyright(c) 1992 * * See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT */ /* * This has been replaced almost entirely from the original by Michael * Sandrof in order to fit in with the multiple screen code. * * ugh, this wasn't easy to do, but I got there, after working out what * had been changed and why, by myself - phone, October 1992. * * And when I started getting /window create working, I discovered new * bugs, and there has been a few more major changes in here again. * It is invalid to call help from more than one screen, at the moment, * because there is to much to keep track of - phone, jan 1993. */ #if 0 static char rcsid[] = "@(#)$Id: help.c,v 1.1.1.1 2003/04/11 01:09:07 dan Exp $"; #endif #include "irc.h" static char cvsrevision[] = "$Id: help.c,v 1.1.1.1 2003/04/11 01:09:07 dan Exp $"; CVS_REVISION(help_c) #ifdef WANT_EPICHELP #include "struct.h" #include "help.h" #include "input.h" #include "ircaux.h" #include "hook.h" #include "output.h" #include "screen.h" #include "server.h" #include "ircterm.h" #include "vars.h" #include "window.h" #include #include "bsdglob.h" #define MAIN_SOURCE #include "modval.h" /* Forward declarations */ static void help_me (char *, char *); static void help_show_paused_topic (char *, char *); static void create_help_window (void); static void set_help_screen (Screen *); static void help_put_it (const char *topic, const char *format, ...); /* * A few variables here - A lot added to get help working with * non - recursive calls to irc_io, and also have it still * reading things from the server(s), so not to ping timeout. */ static int dont_pause_topic = 0; static int entry_size; static int finished_help_paging = 0; static FILE * help_fp; #define HELP_PAUSED_LINES_MAX 500 static int help_paused_lines = 0; static char * help_paused_topic[HELP_PAUSED_LINES_MAX]; /* Should be enough */ static Screen *help_screen = (Screen *) 0; static int help_show_directory = 0; static char help_topic_list[BIG_BUFFER_SIZE + 1]; static Window *help_window = (Window *) 0; static char no_help[] = "NOHELP"; static char paused_topic[128]; static char * this_arg; static int use_help_window = 0; /* * show_help: show's either a page of text from a help_fp, or the whole * thing, depending on the value of HELP_PAGER_VAR. If it gets to the end, * (in either case it will eventally), it closes the file, and returns 0 * to indicate this. */ static int show_help (Window *window, char *name) { Window *old_target_window = target_window; int rows = 0; char line[256]; target_window = window ? window : current_window; if (get_int_var(HELP_PAGER_VAR)) rows = window->display_size; while (rows) { if (!fgets(line, 255, help_fp)) { fclose(help_fp); help_fp = NULL; target_window = old_target_window; return 0; } if (*(line + strlen(line) - 1) == '\n') *(line + strlen(line) - 1) = (char) 0; /* * This is for compatability with ircII-4.4 */ if (*line == '!' || *line == '#') continue; help_put_it(name, "%s", line); rows--; } target_window = old_target_window; return (1); } /* * help_prompt: The main procedure called to display the help file * currently being accessed. Using add_wait_prompt(), it sets it * self up to be recalled when the next page is asked for. If * called when we have finished paging the help file, we exit, as * there is nothing left to show. If line is 'q' or 'Q', exit the * help pager, clean up, etc.. If all is cool for now, we call * show_help, and either if its finished, exit, or prompt for the * next page. From here, if we've finished the help page, and * doing help prompts, prompt for the help.. */ static void help_prompt (char *name, char *line) { if (finished_help_paging) { if (*paused_topic) help_show_paused_topic(paused_topic, empty_string); return; } if (line && toupper(*line) == 'Q') { finished_help_paging = 1; #if 0 help_paused_lines = 0; /* Thanks robo */ #endif fclose(help_fp); help_fp = NULL; set_help_screen((Screen *) 0); return; } if (show_help(help_window, name)) { if (dumb_mode) help_prompt(name, NULL); else add_wait_prompt("*** Hit any key for more, 'q' to quit ***", help_prompt, name, WAIT_PROMPT_KEY, 1); } else { finished_help_paging = 1; if (help_fp) fclose(help_fp); help_fp = NULL; if (help_show_directory) { if (get_int_var(HELP_PAGER_VAR)) { if (dumb_mode) help_show_paused_topic(name, empty_string); else add_wait_prompt("*** Hit any key to end ***", help_show_paused_topic, paused_topic, WAIT_PROMPT_KEY, 1); } else { help_show_paused_topic(paused_topic, empty_string); set_help_screen((Screen *) 0); } help_show_directory = 0; return; } } if (finished_help_paging) { if (get_int_var(HELP_PROMPT_VAR)) { char tmp[BIG_BUFFER_SIZE + 1]; sprintf(tmp, "%s%sHelp? ", help_topic_list, *help_topic_list ? space : empty_string); if (!dumb_mode) add_wait_prompt(tmp, help_me, help_topic_list, WAIT_PROMPT_LINE, 1); } else { if (*paused_topic) help_show_paused_topic(paused_topic, empty_string); set_help_screen((Screen *) 0); } } } /* * help_topic: Given a topic, we search the help directory, and try to * find the right file, if all is cool, and we can open it, or zcat it, * then we call help_prompt to get the actually displaying of the file * on the road. */ static void help_topic (char *path, char *name) { char *filename = NULL; if (!name) return; /* what is the base name? */ filename = m_sprintf("%s/%s", path, name); if (filename[strlen(filename)-1] == '/') chop(filename, 1); /* let uzfopen have all the fun */ if ((help_fp = uzfopen (&filename, path, 0))) { /* Isnt this a heck of a lot better then the kludge you were using? */ help_put_it(name, "*** Help on %s", name); help_prompt(name, NULL); } else help_put_it (name, "*** No help available on %s: Use ? for list of topics", name); new_free(&filename); return; } /* * help_pause_add_line: this procedure does a help_put_it() call, but * puts off the calling, until help_show_paused_topic() is called. * I do this because I need to create the list of help topics, but * not show them, until we've seen the whole file, so we called * help_show_paused_topic() when we've seen the file, if it is needed. */ static void help_pause_add_line (char *format, ...) { char buf[BIG_BUFFER_SIZE]; va_list args; va_start (args, format); vsnprintf(buf, BIG_BUFFER_SIZE - 1, format, args); va_end (args); if ((help_paused_lines + 1) >= HELP_PAUSED_LINES_MAX) ircpanic("help_pause_add_line: would overflow the buffer"); malloc_strcpy(&help_paused_topic[help_paused_lines++], buf); } /* * help_show_paused_topic: see above. Called when we've seen the * whole help file, and we have a list of topics to display. */ static void help_show_paused_topic (char *name, char *line) { static int i = 0; int j = 0; int rows; if (!help_paused_lines) return; if (toupper(*line) == 'Q') i = help_paused_lines + 1; /* just big enough */ rows = help_window->display_size; if (i < help_paused_lines) { for (j = 0; j < rows; j++) { help_put_it (name, "%s", help_paused_topic[i]); new_free(&help_paused_topic[i]); /* if we're done, the recurse to break loop */ if (++i >= help_paused_lines) break; } if (!dumb_mode) { if ((i < help_paused_lines) && get_int_var(HELP_PAGER_VAR)) add_wait_prompt("[MORE]", help_show_paused_topic, name, WAIT_PROMPT_KEY, 1); } else help_show_paused_topic(name, line); } /* * This cant be an else of the previous if because 'i' can * change in the previous if and we need to test it again */ if (i >= help_paused_lines) { if (get_int_var(HELP_PROMPT_VAR)) { char buf[BIG_BUFFER_SIZE]; sprintf(buf, "%s%sHelp? ", name, (name && *name) ? space : empty_string); if (!dumb_mode) add_wait_prompt(buf, help_me, name, WAIT_PROMPT_LINE, 1); } else set_help_screen((Screen *) 0); dont_pause_topic = 0; help_paused_lines = 0; /* Probably should reset this ;-) */ i = 0; } } /* * help_me: The big one. The help procedure that handles working out * what was actually requested, sets up the paused topic list if it is * needed, does pretty much all the hard work. */ static void help_me (char *topics, char *args) { char * ptr; glob_t g; int entries = 0, cnt, i, cols; struct stat stat_buf; char path[BIG_BUFFER_SIZE+1]; int help_paused_first_call = 0; char * help_paused_path = (char *) 0; char * help_paused_name = (char *) 0; char * temp; char tmp[BIG_BUFFER_SIZE+1]; char buffer[BIG_BUFFER_SIZE+1]; char * pattern = NULL; strcpy(help_topic_list, topics); ptr = get_string_var(HELP_PATH_VAR); sprintf(path, "%s/%s", ptr, topics); for (ptr = path; (ptr = strchr(ptr, ' '));) *ptr = '/'; /* * first we check access to the help dir, whinge if we can't, then * work out we need to ask them for more help, else we check the * args list, and do the stuff */ if (help_show_directory) { help_show_paused_topic(paused_topic, empty_string); help_show_directory = 0; } finished_help_paging = 0; if (access(path, R_OK|X_OK)) { help_put_it(no_help, "*** Cannot access help directory!"); set_help_screen((Screen *) 0); return; } this_arg = next_arg(args, &args); if (!this_arg && *help_topic_list && get_int_var(HELP_PROMPT_VAR)) { if ((temp = strrchr(help_topic_list, ' ')) != NULL) *temp = '\0'; else *help_topic_list = '\0'; sprintf(tmp, "%s%sHelp? ", help_topic_list, *help_topic_list ? space : empty_string); if (!dumb_mode) add_wait_prompt(tmp, help_me, help_topic_list, WAIT_PROMPT_LINE, 1); return; } if (!this_arg) { set_help_screen((Screen *) 0); return; } create_help_window(); /* * This is just a bogus while loop which is intended to allow * the user to do '/help alias expressions' without having to * include a slash inbetween the topic and subtopic. * * If all goes well, we 'break' at the bottom of the loop. */ while (this_arg) { entries = 0; reset_display_target(); if (!*this_arg) help_topic(path, NULL); if (strcmp(this_arg, "?") == 0) { this_arg = empty_string; if (!dont_pause_topic) dont_pause_topic = 1; } /* * entry_size is set to the width of the longest help topic * (adjusted for compression extensions, of course.) */ entry_size = 0; /* * Gather up the names of the files in the help directory. */ { #ifndef HAVE_FCHDIR char opath[MAXPATHLEN + 1]; getcwd(opath, MAXPATHLEN); #else int cwd = open(".", O_RDONLY); #endif chdir(path); pattern = alloca(strlen(path) + 2 + strlen(this_arg) + 3); strcpy(pattern, this_arg); strcat(pattern, "*"); #ifdef GLOB_INSENSITIVE bsd_glob(pattern, GLOB_INSENSITIVE /* GLOB_MARK */, NULL, &g); #else bsd_glob(pattern, 0 /* GLOB_MARK */, NULL, &g); #endif #ifndef HAVE_FCHDIR chdir(opath); #else fchdir(cwd); close(cwd); #endif } for (i = 0; i < g.gl_matchc; i++) { char *tmp = g.gl_pathv[i]; int len = strlen(tmp); if (!end_strcmp(tmp, ".gz", 3)) len -= 3; else if (!end_strcmp(tmp, ".bz2", 4)) len -= 4; entry_size = (len > entry_size) ? len : entry_size; } /* * Right here we need to check for an 'exact match'. * An 'exact match' would be sitting in gl_pathv[0], * and it is 'exact' if it is identical to what we are * looking for, or if it is the same except that it has * a compression extension on it */ if (g.gl_matchc > 1) { char *str1 = g.gl_pathv[0]; char *str2 = this_arg; int len1 = strlen(str1); int len2 = strlen(str2); if (len1 == len2 && !my_stricmp(str1, str2)) entries = 1; else if (len1 - 3 == len2 && !my_strnicmp(str1, str2, len2) && !end_strcmp(str1, ".gz", 3)) entries = 1; else if (len1 - 2 == len2 && !my_strnicmp(str1, str2, len2) && !end_strcmp(str1, ".Z", 2)) entries = 1; else if (len1 - 2 == len2 && !my_strnicmp(str1, str2, len2) && !end_strcmp(str1, ".z", 2)) entries = 1; } if (!*help_topic_list) dont_pause_topic = 1; /* reformatted */ /* * entries: -1 means something really died, 0 means there * was no help, 1, means it wasn't a directory, and so to * show the help file, and the default means to add the * stuff to the paused topic list.. */ if (!entries) entries = g.gl_matchc; switch (entries) { case -1: { help_put_it(no_help, "*** Error during help function: %s", strerror(errno)); set_help_screen(NULL); if (help_paused_first_call) { help_topic(help_paused_path, help_paused_name); help_paused_first_call = 0; new_free(&help_paused_path); new_free(&help_paused_name); } return; } case 0: { help_put_it(this_arg, "*** No help available on %s: Use ? for list of topics", this_arg); if (!get_int_var(HELP_PROMPT_VAR)) { set_help_screen(NULL); break; } sprintf(tmp, "%s%sHelp? ", help_topic_list, *help_topic_list ? space : empty_string); if (!dumb_mode) add_wait_prompt(tmp, help_me, help_topic_list, WAIT_PROMPT_LINE, 1); if (help_paused_first_call) { help_topic(help_paused_path, help_paused_name); help_paused_first_call = 0; new_free(&help_paused_path); new_free(&help_paused_name); } break; } case 1: { sprintf(tmp, "%s/%s", path, g.gl_pathv[0]); stat(tmp, &stat_buf); if (stat_buf.st_mode & S_IFDIR) { strcpy(path, tmp); if (*help_topic_list) strcat(help_topic_list, space); strcat(help_topic_list, g.gl_pathv[0]); if (!(this_arg = next_arg(args, &args))) { help_paused_first_call = 1; malloc_strcpy(&help_paused_path, path); malloc_strcpy(&help_paused_name, g.gl_pathv[0]); dont_pause_topic = -1; this_arg = "?"; } bsd_globfree(&g); continue; } else { help_topic(path, g.gl_pathv[0]); finished_help_paging = 0; break; } } default: { help_show_directory = 1; strcpy(paused_topic, help_topic_list); help_pause_add_line("*** %s choices:", help_topic_list); entry_size += 2; cols = (current_term->TI_cols - 10) / entry_size; strcpy(buffer, empty_string); cnt = 0; for (i = 0; i < entries; i++) { if (!end_strcmp(g.gl_pathv[i], ".gz", 3)) chop(g.gl_pathv[i], 3); else if (!end_strcmp(g.gl_pathv[i], ".bz2", 4)) chop(g.gl_pathv[i], 4); strcat(buffer, g.gl_pathv[i]); /* * Since we already know how many columns each * line will contain, we check to see if we have * accumulated that many entries. If we have, we * output the line to the screen. */ if (++cnt == cols) { help_pause_add_line("%s", buffer); strcpy(buffer, empty_string); cnt = 0; } /* * If we have not finished this line, then we have * to pad the name length out to the expected width. * 'entry_size' is the column width. We also have * do adjust for compression extension. */ else strextend(buffer, ' ', entry_size - strlen(g.gl_pathv[i])); } help_pause_add_line("%s", buffer); if (help_paused_first_call) { help_topic(help_paused_path, help_paused_name); help_paused_first_call = 0; new_free(&help_paused_path); new_free(&help_paused_name); } if (dont_pause_topic == 1) { help_show_paused_topic(paused_topic, empty_string); help_show_directory = 0; } break; } } /* end of reformatting */ bsd_globfree(&g); break; } /* * This one is for when there was never a topic and the prompt * never got a topic.. and help_screen was never reset.. * phone, jan 1993. */ if (!*help_topic_list && finished_help_paging) set_help_screen((Screen *) 0); } /* * help: the HELP command, gives help listings for any and all topics out * there */ BUILT_IN_COMMAND(epichelp) { char *help_path; finished_help_paging = 0; help_show_directory = 0; dont_pause_topic = 0; use_help_window = 0; /* * The idea here is to work out what sort of help we are using - * either the installed help files, or some help service, what * ever it maybe. Once we have worked this out, if we are using * a help window, set it up properly. */ help_path = get_string_var(HELP_PATH_VAR); if (!help_path || !*help_path || access(help_path, R_OK | X_OK)) { help_put_it(no_help, "*** HELP_PATH variable not set or set to an invalid path"); return; } /* Allow us to wait until help is finished */ if (!my_strnicmp(args, "-wait", 2)) { while (help_screen) io("help"); return; } if (help_path && help_screen && help_screen != current_window->screen) { say("You may not run help in two screens"); return; } help_screen = current_window->screen; help_window = (Window *) 0; help_me(empty_string, (args && *args) ? args : "?"); } static void create_help_window (void) { if (help_window) return; if (!dumb_mode && get_int_var(HELP_WINDOW_VAR)) { use_help_window = 1; help_window = new_window(current_window->screen); help_window->hold_mode = OFF; help_window->window_level = LOG_HELP; update_all_windows(); } else help_window = current_window; } static void set_help_screen (Screen *screen) { help_screen = screen; if (!help_screen && help_window) { if (use_help_window) { int display = window_display; window_display = 0; delete_window(help_window); window_display = display; } help_window = (Window *) 0; update_all_windows(); } } static void help_put_it (const char *topic, const char *format, ...) { char putbuf[BIG_BUFFER_SIZE * 3 + 1]; if (format) { va_list args; va_start (args, format); vsnprintf(putbuf, BIG_BUFFER_SIZE * 3, format, args); va_end(args); if (do_hook(HELP_LIST, "%s %s", topic, putbuf)) { int old_level = who_level; Window *old_target_window = target_window; /* * LOG_HELP is a completely bogus mode. We use * it only to make sure that the current level is * not LOG_CURRENT, so that the to_window will stick. */ who_level = LOG_HELP; if (help_window) target_window = help_window; add_to_screen(putbuf); target_window = old_target_window; who_level = old_level; } } } #endif