1/* $Id: help.c,v 1.58.2.1 2007/04/19 03:15:04 dolorous Exp $ */
2/**************************************************************************
3 *   help.c                                                               *
4 *                                                                        *
5 *   Copyright (C) 2000, 2001, 2002, 2003, 2004 Chris Allegretta          *
6 *   Copyright (C) 2005, 2006, 2007 David Lawrence Ramsey                 *
7 *   This program is free software; you can redistribute it and/or modify *
8 *   it under the terms of the GNU General Public License as published by *
9 *   the Free Software Foundation; either version 2, or (at your option)  *
10 *   any later version.                                                   *
11 *                                                                        *
12 *   This program is distributed in the hope that it will be useful, but  *
13 *   WITHOUT ANY WARRANTY; without even the implied warranty of           *
14 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    *
15 *   General Public License for more details.                             *
16 *                                                                        *
17 *   You should have received a copy of the GNU General Public License    *
18 *   along with this program; if not, write to the Free Software          *
19 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA            *
20 *   02110-1301, USA.                                                     *
21 *                                                                        *
22 **************************************************************************/
23
24#include "proto.h"
25
26#include <stdio.h>
27#include <string.h>
28#include <ctype.h>
29
30#ifndef DISABLE_HELP
31
32static char *help_text = NULL;
33	/* The text displayed in the help window. */
34
35/* Our main help browser function.  refresh_func is the function we will
36 * call to refresh the edit window. */
37void do_help(void (*refresh_func)(void))
38{
39    int kbinput = ERR;
40    bool meta_key, func_key, old_no_help = ISSET(NO_HELP);
41    bool abort = FALSE;
42	/* Whether we should abort the help browser. */
43    size_t line = 0;
44	/* The line number in help_text of the first displayed help
45	 * line.  This variable is zero-based. */
46    size_t last_line = 0;
47	/* The line number in help_text of the last help line.  This
48	 * variable is zero-based. */
49#ifndef DISABLE_MOUSE
50    const shortcut *oldshortcut = currshortcut;
51	/* The current shortcut list. */
52#endif
53    const char *ptr;
54	/* The current line of the help text. */
55    size_t old_line = (size_t)-1;
56	/* The line we were on before the current line. */
57
58    curs_set(0);
59    blank_edit();
60    wattroff(bottomwin, reverse_attr);
61    blank_statusbar();
62
63    /* Set help_text as the string to display. */
64    help_init();
65
66    assert(help_text != NULL);
67
68#ifndef DISABLE_MOUSE
69    /* Set currshortcut to allow clicking on the help screen's shortcut
70     * list, after help_init() is called. */
71    currshortcut = help_list;
72#endif
73
74    if (ISSET(NO_HELP)) {
75	/* Make sure that the help screen's shortcut list will actually
76	 * be displayed. */
77	UNSET(NO_HELP);
78	window_init();
79    }
80
81    bottombars(help_list);
82    wnoutrefresh(bottomwin);
83
84    /* Get the last line of the help text. */
85    ptr = help_text;
86
87    for (; *ptr != '\0'; last_line++) {
88	ptr += help_line_len(ptr);
89	if (*ptr == '\n')
90	    ptr++;
91    }
92    if (last_line > 0)
93	last_line--;
94
95    while (!abort) {
96	size_t i;
97
98	/* Display the help text if we don't have a key, or if the help
99	 * text has moved. */
100	if (kbinput == ERR || line != old_line) {
101	    blank_edit();
102
103	    ptr = help_text;
104
105	    /* Calculate where in the text we should be, based on the
106	     * page. */
107	    for (i = 0; i < line; i++) {
108		ptr += help_line_len(ptr);
109		if (*ptr == '\n')
110		    ptr++;
111	    }
112
113	    for (i = 0; i < editwinrows && *ptr != '\0'; i++) {
114		size_t j = help_line_len(ptr);
115
116		mvwaddnstr(edit, i, 0, ptr, j);
117		ptr += j;
118		if (*ptr == '\n')
119		    ptr++;
120	    }
121	}
122
123	wnoutrefresh(edit);
124
125	old_line = line;
126
127	kbinput = get_kbinput(edit, &meta_key, &func_key);
128	parse_help_input(&kbinput, &meta_key, &func_key);
129
130	switch (kbinput) {
131#ifndef DISABLE_MOUSE
132	    case KEY_MOUSE:
133		{
134		    int mouse_x, mouse_y;
135
136		    get_mouseinput(&mouse_x, &mouse_y, TRUE);
137		}
138		break;
139#endif
140	    /* Redraw the screen. */
141	    case NANO_REFRESH_KEY:
142		total_redraw();
143		break;
144	    case NANO_PREVPAGE_KEY:
145		if (line > editwinrows - 2)
146		    line -= editwinrows - 2;
147		else
148		    line = 0;
149		break;
150	    case NANO_NEXTPAGE_KEY:
151		if (line + (editwinrows - 1) < last_line)
152		    line += editwinrows - 2;
153		break;
154	    case NANO_PREVLINE_KEY:
155		if (line > 0)
156		    line--;
157		break;
158	    case NANO_NEXTLINE_KEY:
159		if (line + (editwinrows - 1) < last_line)
160		    line++;
161		break;
162	    case NANO_FIRSTLINE_METAKEY:
163		if (meta_key)
164		    line = 0;
165		break;
166	    case NANO_LASTLINE_METAKEY:
167		if (meta_key) {
168		    if (line + (editwinrows - 1) < last_line)
169			line = last_line - (editwinrows - 1);
170		}
171		break;
172	    /* Abort the help browser. */
173	    case NANO_EXIT_KEY:
174		abort = TRUE;
175		break;
176	}
177    }
178
179#ifndef DISABLE_MOUSE
180    currshortcut = oldshortcut;
181#endif
182
183    if (old_no_help) {
184	blank_bottombars();
185	wnoutrefresh(bottomwin);
186	SET(NO_HELP);
187	window_init();
188    } else
189	bottombars(currshortcut);
190
191    curs_set(1);
192    refresh_func();
193
194    /* The help_init() at the beginning allocated help_text.  Since
195     * help_text has now been written to the screen, we don't need it
196     * anymore. */
197    free(help_text);
198    help_text = NULL;
199}
200
201/* Start the help browser for the edit window. */
202void do_help_void(void)
203{
204    do_help(&edit_refresh);
205}
206
207#ifndef DISABLE_BROWSER
208/* Start the help browser for the file browser. */
209void do_browser_help(void)
210{
211    do_help(&browser_refresh);
212}
213#endif
214
215/* This function allocates help_text, and stores the help string in it.
216 * help_text should be NULL initially. */
217void help_init(void)
218{
219    size_t allocsize = 0;	/* Space needed for help_text. */
220    const char *htx[3];		/* Untranslated help message.  We break
221				 * it up into three chunks in case the
222				 * full string is too long for the
223				 * compiler to handle. */
224    char *ptr;
225    const shortcut *s;
226#ifndef NANO_TINY
227    const toggle *t;
228#ifdef ENABLE_NANORC
229    bool old_whitespace = ISSET(WHITESPACE_DISPLAY);
230
231    UNSET(WHITESPACE_DISPLAY);
232#endif
233#endif
234
235    /* First, set up the initial help text for the current function. */
236    if (currshortcut == whereis_list || currshortcut == replace_list ||
237	currshortcut == replace_list_2) {
238	htx[0] = N_("Search Command Help Text\n\n "
239		"Enter the words or characters you would like to "
240		"search for, and then press Enter.  If there is a "
241		"match for the text you entered, the screen will be "
242		"updated to the location of the nearest match for the "
243		"search string.\n\n The previous search string will be "
244		"shown in brackets after the search prompt.  Hitting "
245		"Enter without entering any text will perform the "
246		"previous search.  ");
247	htx[1] = N_("If you have selected text with the mark and then "
248		"search to replace, only matches in the selected text "
249		"will be replaced.\n\n The following function keys are "
250		"available in Search mode:\n\n");
251	htx[2] = NULL;
252    } else if (currshortcut == gotoline_list) {
253	htx[0] = N_("Go To Line Help Text\n\n "
254		"Enter the line number that you wish to go to and hit "
255		"Enter.  If there are fewer lines of text than the "
256		"number you entered, you will be brought to the last "
257		"line of the file.\n\n The following function keys are "
258		"available in Go To Line mode:\n\n");
259	htx[1] = NULL;
260	htx[2] = NULL;
261    } else if (currshortcut == insertfile_list) {
262	htx[0] = N_("Insert File Help Text\n\n "
263		"Type in the name of a file to be inserted into the "
264		"current file buffer at the current cursor "
265		"location.\n\n If you have compiled nano with multiple "
266		"file buffer support, and enable multiple file buffers "
267		"with the -F or --multibuffer command line flags, the "
268		"Meta-F toggle, or a nanorc file, inserting a file "
269		"will cause it to be loaded into a separate buffer "
270		"(use Meta-< and > to switch between file buffers).  ");
271	htx[1] = N_("If you need another blank buffer, do not enter "
272		"any filename, or type in a nonexistent filename at "
273		"the prompt and press Enter.\n\n The following "
274		"function keys are available in Insert File mode:\n\n");
275	htx[2] = NULL;
276    } else if (currshortcut == writefile_list) {
277	htx[0] = N_("Write File Help Text\n\n "
278		"Type the name that you wish to save the current file "
279		"as and press Enter to save the file.\n\n If you have "
280		"selected text with the mark, you will be prompted to "
281		"save only the selected portion to a separate file.  To "
282		"reduce the chance of overwriting the current file with "
283		"just a portion of it, the current filename is not the "
284		"default in this mode.\n\n The following function keys "
285		"are available in Write File mode:\n\n");
286	htx[1] = NULL;
287	htx[2] = NULL;
288    }
289#ifndef DISABLE_BROWSER
290    else if (currshortcut == browser_list) {
291	htx[0] = N_("File Browser Help Text\n\n "
292		"The file browser is used to visually browse the "
293		"directory structure to select a file for reading "
294		"or writing.  You may use the arrow keys or Page Up/"
295		"Down to browse through the files, and S or Enter to "
296		"choose the selected file or enter the selected "
297		"directory.  To move up one level, select the "
298		"directory called \"..\" at the top of the file "
299		"list.\n\n The following function keys are available "
300		"in the file browser:\n\n");
301	htx[1] = NULL;
302	htx[2] = NULL;
303    } else if (currshortcut == whereis_file_list) {
304	htx[0] = N_("Browser Search Command Help Text\n\n "
305		"Enter the words or characters you would like to "
306		"search for, and then press Enter.  If there is a "
307		"match for the text you entered, the screen will be "
308		"updated to the location of the nearest match for the "
309		"search string.\n\n The previous search string will be "
310		"shown in brackets after the search prompt.  Hitting "
311		"Enter without entering any text will perform the "
312		"previous search.\n\n");
313	htx[1] = N_(" The following function keys are available in "
314		"Browser Search mode:\n\n");
315	htx[2] = NULL;
316    } else if (currshortcut == gotodir_list) {
317	htx[0] = N_("Browser Go To Directory Help Text\n\n "
318		"Enter the name of the directory you would like to "
319		"browse to.\n\n If tab completion has not been "
320		"disabled, you can use the Tab key to (attempt to) "
321		"automatically complete the directory name.\n\n The "
322		"following function keys are available in Browser Go "
323		"To Directory mode:\n\n");
324	htx[1] = NULL;
325	htx[2] = NULL;
326    }
327#endif /* !DISABLE_BROWSER */
328#ifndef DISABLE_SPELLER
329    else if (currshortcut == spell_list) {
330	htx[0] = N_("Spell Check Help Text\n\n "
331		"The spell checker checks the spelling of all text in "
332		"the current file.  When an unknown word is "
333		"encountered, it is highlighted and a replacement can "
334		"be edited.  It will then prompt to replace every "
335		"instance of the given misspelled word in the current "
336		"file, or, if you have selected text with the mark, in "
337		"the selected text.\n\n The following function keys "
338		"are available in Spell Check mode:\n\n");
339	htx[1] = NULL;
340	htx[2] = NULL;
341    }
342#endif /* !DISABLE_SPELLER */
343#ifndef NANO_TINY
344    else if (currshortcut == extcmd_list) {
345	htx[0] = N_("Execute Command Help Text\n\n "
346		"This mode allows you to insert the output of a "
347		"command run by the shell into the current buffer (or "
348		"a new buffer in multiple file buffer mode). If you "
349		"need another blank buffer, do not enter any "
350		"command.\n\n The following function keys are "
351		"available in Execute Command mode:\n\n");
352	htx[1] = NULL;
353	htx[2] = NULL;
354    }
355#endif /* !NANO_TINY */
356    else {
357	/* Default to the main help list. */
358	htx[0] = N_("Main nano help text\n\n "
359		"The nano editor is designed to emulate the "
360		"functionality and ease-of-use of the UW Pico text "
361		"editor.  There are four main sections of the editor.  "
362		"The top line shows the program version, the current "
363		"filename being edited, and whether or not the file "
364		"has been modified.  Next is the main editor window "
365		"showing the file being edited.  The status line is "
366		"the third line from the bottom and shows important "
367		"messages.  ");
368	htx[1] = N_("The bottom two lines show the most commonly used "
369		"shortcuts in the editor.\n\n The notation for "
370		"shortcuts is as follows: Control-key sequences are "
371		"notated with a caret (^) symbol and can be entered "
372		"either by using the Control (Ctrl) key or pressing "
373		"the Escape (Esc) key twice.  Escape-key sequences are "
374		"notated with the Meta (M-) symbol and can be entered "
375		"using either the Esc, Alt, or Meta key depending on "
376		"your keyboard setup.  ");
377	htx[2] = N_("Also, pressing Esc twice and then typing a "
378		"three-digit decimal number from 000 to 255 will enter "
379		"the character with the corresponding value.  The "
380		"following keystrokes are available in the main editor "
381		"window.  Alternative keys are shown in "
382		"parentheses:\n\n");
383    }
384
385    htx[0] = _(htx[0]);
386    if (htx[1] != NULL)
387	htx[1] = _(htx[1]);
388    if (htx[2] != NULL)
389	htx[2] = _(htx[2]);
390
391    allocsize += strlen(htx[0]);
392    if (htx[1] != NULL)
393	allocsize += strlen(htx[1]);
394    if (htx[2] != NULL)
395	allocsize += strlen(htx[2]);
396
397    /* Count the shortcut help text.  Each entry has up to three keys,
398     * which fill 24 columns, plus translated text, plus one or two
399     * \n's. */
400	for (s = currshortcut; s != NULL; s = s->next)
401	    allocsize += (24 * mb_cur_max()) + strlen(s->help) + 2;
402
403#ifndef NANO_TINY
404    /* If we're on the main list, we also count the toggle help text.
405     * Each entry has "M-%c\t\t\t", which fills 24 columns, plus a
406     * space, plus translated text, plus one or two '\n's. */
407    if (currshortcut == main_list) {
408	size_t endis_len = strlen(_("enable/disable"));
409
410	for (t = toggles; t != NULL; t = t->next)
411	    allocsize += strlen(t->desc) + endis_len + 9;
412    }
413#endif
414
415    /* help_text has been freed and set to NULL unless the user resized
416     * while in the help screen. */
417    if (help_text != NULL)
418	free(help_text);
419
420    /* Allocate space for the help text. */
421    help_text = charalloc(allocsize + 1);
422
423    /* Now add the text we want. */
424    strcpy(help_text, htx[0]);
425    if (htx[1] != NULL)
426	strcat(help_text, htx[1]);
427    if (htx[2] != NULL)
428	strcat(help_text, htx[2]);
429
430    ptr = help_text + strlen(help_text);
431
432    /* Now add our shortcut info.  Assume that each shortcut has, at the
433     * very least, an equivalent control key, an equivalent primary meta
434     * key sequence, or both.  Also assume that the meta key values are
435     * not control characters.  We can display a maximum of three
436     * shortcut entries. */
437    for (s = currshortcut; s != NULL; s = s->next) {
438	int entries = 0;
439
440	/* Control key. */
441	if (s->ctrlval != NANO_NO_KEY) {
442	    entries++;
443	    /* Yucky sentinel values that we can't handle a better
444	     * way. */
445	    if (s->ctrlval == NANO_CONTROL_SPACE) {
446		char *space_ptr = display_string(_("Space"), 0, 14,
447			FALSE);
448
449		if (s->funcval == NANO_NO_KEY && (s->metaval ==
450			NANO_NO_KEY || s->miscval == NANO_NO_KEY)) {
451		    /* If we're here, we have at least two entries worth
452		     * of blank space.  If this entry takes up more than
453		     * one entry's worth of space, use two to display
454		     * it. */
455		    if (mbstrlen(space_ptr) > 6)
456			entries++;
457		} else
458		    /* Otherwise, truncate it so that it takes up only
459		     * one entry's worth of space. */
460		    space_ptr[6] = '\0';
461
462		ptr += sprintf(ptr, "^%s", space_ptr);
463
464		free(space_ptr);
465	    } else if (s->ctrlval == NANO_CONTROL_8)
466		ptr += sprintf(ptr, "^?");
467	    /* Normal values. */
468	    else
469		ptr += sprintf(ptr, "^%c", s->ctrlval + 64);
470	    *(ptr++) = '\t';
471	}
472
473	/* Function key. */
474	if (s->funcval != NANO_NO_KEY) {
475	    entries++;
476	    /* If this is the first entry, put it in the middle. */
477	    if (entries == 1) {
478		entries++;
479		*(ptr++) = '\t';
480	    }
481	    ptr += sprintf(ptr, "(F%d)", s->funcval - KEY_F0);
482	    *(ptr++) = '\t';
483	}
484
485	/* Primary meta key sequence.  If it's the first entry, don't
486	 * put parentheses around it. */
487	if (s->metaval != NANO_NO_KEY) {
488	    entries++;
489	    /* If this is the last entry, put it at the end. */
490	    if (entries == 2 && s->miscval == NANO_NO_KEY) {
491		entries++;
492		*(ptr++) = '\t';
493	    }
494	    /* Yucky sentinel values that we can't handle a better
495	     * way. */
496	    if (s->metaval == NANO_META_SPACE && entries == 1) {
497		char *space_ptr = display_string(_("Space"), 0, 13,
498			FALSE);
499
500		/* If we're here, we have at least two entries worth of
501		 * blank space.  If this entry takes up more than one
502		 * entry's worth of space, use two to display it. */
503		if (mbstrlen(space_ptr) > 5)
504		    entries++;
505
506		ptr += sprintf(ptr, "M-%s", space_ptr);
507
508		free(space_ptr);
509	    } else
510		/* Normal values. */
511		ptr += sprintf(ptr, (entries == 1) ? "M-%c" : "(M-%c)",
512			toupper(s->metaval));
513	    *(ptr++) = '\t';
514	}
515
516	/* Miscellaneous meta key sequence. */
517	if (entries < 3 && s->miscval != NANO_NO_KEY) {
518	    entries++;
519	    /* If this is the last entry, put it at the end. */
520	    if (entries == 2) {
521		entries++;
522		*(ptr++) = '\t';
523	    }
524	    ptr += sprintf(ptr, "(M-%c)", toupper(s->miscval));
525	    *(ptr++) = '\t';
526	}
527
528	/* If this entry isn't blank, make sure all the help text starts
529	 * at the same place. */
530	if (s->ctrlval != NANO_NO_KEY || s->funcval != NANO_NO_KEY ||
531		s->metaval != NANO_NO_KEY || s->miscval !=
532		NANO_NO_KEY) {
533	    while (entries < 3) {
534		entries++;
535		*(ptr++) = '\t';
536	    }
537	}
538
539	/* The shortcut's help text. */
540	ptr += sprintf(ptr, "%s\n", s->help);
541
542	if (s->blank_after)
543	    ptr += sprintf(ptr, "\n");
544    }
545
546#ifndef NANO_TINY
547    /* And the toggles... */
548    if (currshortcut == main_list) {
549	for (t = toggles; t != NULL; t = t->next) {
550	    ptr += sprintf(ptr, "M-%c\t\t\t%s %s\n",
551		toupper(t->val), t->desc, _("enable/disable"));
552
553	    if (t->blank_after)
554		ptr += sprintf(ptr, "\n");
555	}
556    }
557
558#ifdef ENABLE_NANORC
559    if (old_whitespace)
560	SET(WHITESPACE_DISPLAY);
561#endif
562#endif
563
564    /* If all went well, we didn't overwrite the allocated space for
565     * help_text. */
566    assert(strlen(help_text) <= allocsize + 1);
567}
568
569/* Determine the shortcut key corresponding to the values of kbinput
570 * (the key itself), meta_key (whether the key is a meta sequence), and
571 * func_key (whether the key is a function key), if any.  In the
572 * process, convert certain non-shortcut keys into their corresponding
573 * shortcut keys. */
574void parse_help_input(int *kbinput, bool *meta_key, bool *func_key)
575{
576    get_shortcut(help_list, kbinput, meta_key, func_key);
577
578    if (!*meta_key) {
579	switch (*kbinput) {
580	    /* For consistency with the file browser. */
581	    case ' ':
582		*kbinput = NANO_NEXTPAGE_KEY;
583		break;
584	    case '-':
585		*kbinput = NANO_PREVPAGE_KEY;
586		break;
587	    /* Cancel is equivalent to Exit here. */
588	    case NANO_CANCEL_KEY:
589	    case 'E':
590	    case 'e':
591		*kbinput = NANO_EXIT_KEY;
592		break;
593	}
594    }
595}
596
597/* Calculate the next line of help_text, starting at ptr. */
598size_t help_line_len(const char *ptr)
599{
600    int help_cols = (COLS > 24) ? COLS - 1 : 24;
601
602    /* Try to break the line at (COLS - 1) columns if we have more than
603     * 24 columns, and at 24 columns otherwise. */
604    ssize_t wrap_loc = break_line(ptr, help_cols, TRUE);
605    size_t retval = (wrap_loc < 0) ? 0 : wrap_loc;
606    size_t retval_save = retval;
607
608    /* Get the length of the entire line up to a null or a newline. */
609    while (*(ptr + retval) != '\0' && *(ptr + retval) != '\n')
610	retval += move_mbright(ptr + retval, 0);
611
612    /* If the entire line doesn't go more than one column beyond where
613     * we tried to break it, we should display it as-is.  Otherwise, we
614     * should display it only up to the break. */
615    if (strnlenpt(ptr, retval) > help_cols + 1)
616	retval = retval_save;
617
618    return retval;
619}
620
621#endif /* !DISABLE_HELP */
622