help.c (19976B)
1 /************************************************************************** 2 * help.c -- This file is part of GNU nano. * 3 * * 4 * Copyright (C) 2000-2011, 2013-2025 Free Software Foundation, Inc. * 5 * Copyright (C) 2017 Rishabh Dave * 6 * Copyright (C) 2014-2019 Benno Schulenberg * 7 * * 8 * GNU nano is free software: you can redistribute it and/or modify * 9 * it under the terms of the GNU General Public License as published * 10 * by the Free Software Foundation, either version 3 of the License, * 11 * or (at your option) any later version. * 12 * * 13 * GNU nano is distributed in the hope that it will be useful, * 14 * but WITHOUT ANY WARRANTY; without even the implied warranty * 15 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * 16 * See the GNU General Public License for more details. * 17 * * 18 * You should have received a copy of the GNU General Public License * 19 * along with this program. If not, see https://gnu.org/licenses/. * 20 * * 21 **************************************************************************/ 22 23 #include "prototypes.h" 24 25 #ifdef ENABLE_HELP 26 27 #include <errno.h> 28 #include <string.h> 29 30 static char *help_text = NULL; 31 /* The text displayed in the help window. */ 32 static const char *start_of_body = NULL; 33 /* The point in the help text just after the title. */ 34 static char *end_of_intro = NULL; 35 /* The point in the help text where the shortcut descriptions begin. */ 36 static size_t location; 37 /* The offset (in bytes) of the topleft of the shown help text. */ 38 39 /* Allocate space for the help text for the current menu, 40 * and concatenate the different pieces of text into it. */ 41 void help_init(void) 42 { 43 size_t allocsize = 0; 44 /* Space needed for help_text. */ 45 const char *htx[3]; 46 /* Untranslated help introduction. We break it up into three chunks 47 * in case the full string is too long for the compiler to handle. */ 48 const funcstruct *f; 49 const keystruct *s; 50 char *ptr; 51 52 /* First, set up the initial help text for the current function. */ 53 if (currmenu & (MWHEREIS|MREPLACE)) { 54 htx[0] = N_("Search Command Help Text\n\n " 55 "Enter the words or characters you would like to " 56 "search for, and then press Enter. If there is a " 57 "match for the text you entered, the screen will be " 58 "updated to the location of the nearest match for the " 59 "search string.\n\n The previous search string will be " 60 "shown in brackets after the search prompt. Hitting " 61 "Enter without entering any text will perform the " 62 "previous search. "); 63 htx[1] = N_("If you have selected text with the mark and then " 64 "search to replace, only matches in the selected text " 65 "will be replaced.\n\n The following function keys are " 66 "available in Search mode:\n\n"); 67 htx[2] = NULL; 68 } else if (currmenu == MREPLACEWITH) { 69 htx[0] = N_("=== Replacement ===\n\n " 70 "Type the characters that should replace what you " 71 "typed at the previous prompt, and press Enter.\n\n"); 72 htx[1] = N_(" The following function keys " 73 "are available at this prompt:\n\n"); 74 htx[2] = NULL; 75 } else if (currmenu == MGOTOLINE) { 76 htx[0] = N_("Go To Line Help Text\n\n " 77 "Enter the line number that you wish to go to and hit " 78 "Enter. If there are fewer lines of text than the " 79 "number you entered, you will be brought to the last " 80 "line of the file.\n\n The following function keys are " 81 "available in Go To Line mode:\n\n"); 82 htx[1] = NULL; 83 htx[2] = NULL; 84 } else if (currmenu == MINSERTFILE) { 85 htx[0] = N_("Insert File Help Text\n\n " 86 "Type in the name of a file to be inserted into the " 87 "current file buffer at the current cursor " 88 "location.\n\n If you have compiled nano with multiple " 89 "file buffer support, and enable multiple file buffers " 90 "with the -F or --multibuffer command line flags, the " 91 "Meta-F toggle, or a nanorc file, inserting a file " 92 "will cause it to be loaded into a separate buffer " 93 "(use Meta-< and > to switch between file buffers). "); 94 htx[1] = N_("If you need another blank buffer, do not enter " 95 "any filename, or type in a nonexistent filename at " 96 "the prompt and press Enter.\n\n The following " 97 "function keys are available in Insert File mode:\n\n"); 98 htx[2] = NULL; 99 } else if (currmenu == MWRITEFILE) { 100 htx[0] = N_("Write File Help Text\n\n " 101 "Type the name that you wish to save the current file " 102 "as and press Enter to save the file.\n\n If you have " 103 "selected text with the mark, you will be prompted to " 104 "save only the selected portion to a separate file. To " 105 "reduce the chance of overwriting the current file with " 106 "just a portion of it, the current filename is not the " 107 "default in this mode.\n\n The following function keys " 108 "are available in Write File mode:\n\n"); 109 htx[1] = NULL; 110 htx[2] = NULL; 111 } 112 #ifdef ENABLE_BROWSER 113 else if (currmenu == MBROWSER) { 114 htx[0] = N_("File Browser Help Text\n\n " 115 "The file browser is used to visually browse the " 116 "directory structure to select a file for reading " 117 "or writing. You may use the arrow keys or Page Up/" 118 "Down to browse through the files, and S or Enter to " 119 "choose the selected file or enter the selected " 120 "directory. To move up one level, select the " 121 "directory called \"..\" at the top of the file " 122 "list.\n\n The following function keys are available " 123 "in the file browser:\n\n"); 124 htx[1] = NULL; 125 htx[2] = NULL; 126 } else if (currmenu == MWHEREISFILE) { 127 htx[0] = N_("Browser Search Command Help Text\n\n " 128 "Enter the words or characters you would like to " 129 "search for, and then press Enter. If there is a " 130 "match for the text you entered, the screen will be " 131 "updated to the location of the nearest match for the " 132 "search string.\n\n The previous search string will be " 133 "shown in brackets after the search prompt. Hitting " 134 "Enter without entering any text will perform the " 135 "previous search.\n\n"); 136 htx[1] = N_(" The following function keys " 137 "are available at this prompt:\n\n"); 138 htx[2] = NULL; 139 } else if (currmenu == MGOTODIR) { 140 htx[0] = N_("Browser Go To Directory Help Text\n\n " 141 "Enter the name of the directory you would like to " 142 "browse to.\n\n If tab completion has not been " 143 "disabled, you can use the Tab key to (attempt to) " 144 "automatically complete the directory name.\n\n The " 145 "following function keys are available in Browser Go " 146 "To Directory mode:\n\n"); 147 htx[1] = NULL; 148 htx[2] = NULL; 149 } 150 #endif /* ENABLE_BROWSER */ 151 #ifdef ENABLE_SPELLER 152 else if (currmenu == MSPELL) { 153 htx[0] = N_("Spell Check Help Text\n\n " 154 "The spell checker checks the spelling of all text in " 155 "the current file. When an unknown word is " 156 "encountered, it is highlighted and a replacement can " 157 "be edited. It will then prompt to replace every " 158 "instance of the given misspelled word in the current " 159 "file, or, if you have selected text with the mark, in " 160 "the selected text.\n\n The following function keys " 161 "are available in Spell Check mode:\n\n"); 162 htx[1] = NULL; 163 htx[2] = NULL; 164 } 165 #endif /* ENABLE_SPELLER */ 166 #ifndef NANO_TINY 167 else if (currmenu == MEXECUTE) { 168 htx[0] = N_("Execute Command Help Text\n\n " 169 "This mode allows you to insert the output of a " 170 "command run by the shell into the current buffer (or " 171 "into a new buffer). If the command is preceded by '|' " 172 "(the pipe symbol), the current contents of the buffer " 173 "(or marked region) will be piped to the command. "); 174 htx[1] = N_("If you just need another blank buffer, do not enter any " 175 "command.\n\n You can also pick one of four tools, or cut a " 176 "large piece of the buffer, or put the editor to sleep.\n\n"); 177 htx[2] = N_(" The following function keys " 178 "are available at this prompt:\n\n"); 179 } else if (currmenu == MLINTER) { 180 htx[0] = N_("=== Linter ===\n\n " 181 "In this mode, the status bar shows an error message or " 182 "warning, and the cursor is put at the corresponding " 183 "position in the file. With PageUp and PageDown you " 184 "can switch to earlier and later messages.\n\n"); 185 htx[1] = N_(" The following function keys are " 186 "available in Linter mode:\n\n"); 187 htx[2] = NULL; 188 } 189 #endif /* !NANO_TINY */ 190 else { 191 /* Default to the main help list. */ 192 htx[0] = N_("Main nano help text\n\n " 193 "The nano editor is designed to emulate the " 194 "functionality and ease-of-use of the UW Pico text " 195 "editor. There are four main sections of the editor. " 196 "The top line shows the program version, the current " 197 "filename being edited, and whether or not the file " 198 "has been modified. Next is the main editor window " 199 "showing the file being edited. The status line is " 200 "the third line from the bottom and shows important " 201 "messages. "); 202 htx[1] = N_("The bottom two lines show the most commonly used " 203 "shortcuts in the editor.\n\n Shortcuts are written as " 204 "follows: Control-key sequences are notated with a '^' " 205 "and can be entered either by using the Ctrl key or " 206 "pressing the Esc key twice. Meta-key sequences are " 207 "notated with 'M-' and can be entered using either the " 208 "Alt, Cmd, or Esc key, depending on your keyboard setup. "); 209 htx[2] = N_("Also, pressing Esc twice and then typing a " 210 "three-digit decimal number from 000 to 255 will enter " 211 "the character with the corresponding value. The " 212 "following keystrokes are available in the main editor " 213 "window. Alternative keys are shown in " 214 "parentheses:\n\n"); 215 } 216 217 htx[0] = _(htx[0]); 218 if (htx[1] != NULL) 219 htx[1] = _(htx[1]); 220 if (htx[2] != NULL) 221 htx[2] = _(htx[2]); 222 223 allocsize += strlen(htx[0]); 224 if (htx[1] != NULL) 225 allocsize += strlen(htx[1]); 226 if (htx[2] != NULL) 227 allocsize += strlen(htx[2]); 228 229 /* Calculate the length of the descriptions of the shortcuts. 230 * Each entry has one or two keystrokes, which fill 17 cells, 231 * plus translated text, plus one or two \n's. */ 232 for (f = allfuncs; f != NULL; f = f->next) 233 if (f->menus & currmenu) 234 allocsize += strlen(_(f->phrase)) + 21; 235 236 #ifndef NANO_TINY 237 /* If we're on the main list, we also count the toggle help text. 238 * Each entry has "M-%c\t\t ", six chars which fill 17 cells, plus 239 * two translated texts, plus a space, plus one or two '\n's. */ 240 if (currmenu == MMAIN) { 241 size_t onoff_len = strlen(_("enable/disable")); 242 243 for (s = sclist; s != NULL; s = s->next) 244 if (s->func == do_toggle) 245 allocsize += strlen(_(epithet_of_flag(s->toggle))) + onoff_len + 9; 246 } 247 #endif 248 249 /* Allocate memory for the help text. */ 250 help_text = nmalloc(allocsize + 1); 251 252 /* Now add the text we want. */ 253 strcpy(help_text, htx[0]); 254 if (htx[1] != NULL) 255 strcat(help_text, htx[1]); 256 if (htx[2] != NULL) 257 strcat(help_text, htx[2]); 258 259 /* Remember this end-of-introduction, start-of-shortcuts. */ 260 end_of_intro = help_text + strlen(help_text); 261 ptr = end_of_intro; 262 263 /* Now add the shortcuts and their descriptions. */ 264 for (f = allfuncs; f != NULL; f = f->next) { 265 int tally = 0; 266 267 if ((f->menus & currmenu) == 0) 268 continue; 269 270 /* Show the first two shortcuts (if any) for each function. */ 271 for (s = sclist; s != NULL; s = s->next) { 272 if ((s->menus & currmenu) && s->func == f->func && s->keystr[0]) { 273 /* Make the first column 7 cells wide and the second 10. */ 274 if (++tally == 1) { 275 sprintf(ptr, "%s ", s->keystr); 276 /* Unicode arrows take three bytes instead of one. */ 277 ptr += (strstr(s->keystr, "\xE2") != NULL ? 9 : 7); 278 } else { 279 sprintf(ptr, "(%s) ", s->keystr); 280 ptr += (strstr(s->keystr, "\xE2") != NULL ? 12 : 10); 281 break; 282 } 283 } 284 } 285 286 if (tally == 0) 287 ptr += sprintf(ptr, "\t\t "); 288 else if (tally == 1) 289 ptr += 10; 290 291 /* The shortcut's description. */ 292 ptr += sprintf(ptr, "%s\n", _(f->phrase)); 293 294 if (f->blank_after) 295 ptr += sprintf(ptr, "\n"); 296 } 297 298 #ifndef NANO_TINY 299 /* And the toggles... */ 300 if (currmenu == MMAIN) { 301 int maximum = 0, counter = 0; 302 303 /* First see how many toggles there are. */ 304 for (s = sclist; s != NULL; s = s->next) 305 maximum = (s->toggle && s->ordinal > maximum) ? s->ordinal : maximum; 306 307 /* Now show them in the original order. */ 308 while (counter < maximum) { 309 counter++; 310 for (s = sclist; s != NULL; s = s->next) 311 if (s->toggle && s->ordinal == counter) { 312 ptr += sprintf(ptr, "%s\t\t %s %s\n", (s->menus & MMAIN ? s->keystr : ""), 313 _(epithet_of_flag(s->toggle)), _("enable/disable")); 314 /* Add a blank like between two groups. */ 315 if (s->toggle == NO_SYNTAX) 316 ptr += sprintf(ptr, "\n"); 317 break; 318 } 319 } 320 } 321 #endif 322 } 323 324 /* Hard-wrap the concatenated help text, and write it into a new buffer. */ 325 void wrap_help_text_into_buffer(void) 326 { 327 /* Avoid overtight and overwide paragraphs in the introductory text. */ 328 size_t wrapping_point = ((COLS < 40) ? 40 : (COLS > 74) ? 74 : COLS) - sidebar; 329 const char *ptr = start_of_body; 330 size_t sum = 0; 331 332 make_new_buffer(); 333 334 /* Ensure there is a blank line at the top of the text, for esthetics. */ 335 if ((ISSET(MINIBAR) || !ISSET(EMPTY_LINE)) && LINES > 6) { 336 openfile->current->data = mallocstrcpy(openfile->current->data, " "); 337 openfile->current->next = make_new_node(openfile->current); 338 openfile->current = openfile->current->next; 339 } 340 341 /* Copy the help text into the just-created new buffer. */ 342 while (*ptr != '\0') { 343 int length, shim; 344 char *oneline; 345 346 if (ptr == end_of_intro) 347 wrapping_point = ((COLS < 40) ? 40 : COLS) - sidebar; 348 349 if (ptr < end_of_intro || *(ptr - 1) == '\n') { 350 length = break_line(ptr, wrapping_point, TRUE); 351 oneline = nmalloc(length + 1); 352 shim = (*(ptr + length - 1) == ' ') ? 0 : 1; 353 snprintf(oneline, length + shim, "%s", ptr); 354 } else { 355 length = break_line(ptr, ((COLS < 40) ? 22 : COLS - 18) - sidebar, TRUE); 356 oneline = nmalloc(length + 5); 357 snprintf(oneline, length + 5, "\t\t %s", ptr); 358 } 359 360 free(openfile->current->data); 361 openfile->current->data = oneline; 362 363 ptr += length; 364 if (*ptr != '\n') 365 ptr--; 366 367 /* Create a new line, and then one more for each extra \n. */ 368 do { 369 openfile->current->next = make_new_node(openfile->current); 370 openfile->current = openfile->current->next; 371 openfile->current->data = copy_of(""); 372 } while (*(++ptr) == '\n'); 373 } 374 375 openfile->filebot = openfile->current; 376 openfile->current = openfile->filetop; 377 378 remove_magicline(); 379 #ifdef ENABLE_COLOR 380 find_and_prime_applicable_syntax(); 381 #endif 382 prepare_for_display(); 383 384 /* Move to the position in the file where we were before. */ 385 while (TRUE) { 386 sum += strlen(openfile->current->data); 387 if (sum > location) 388 break; 389 openfile->current = openfile->current->next; 390 } 391 392 openfile->edittop = openfile->current; 393 } 394 395 /* Assemble a help text, display it, and allow scrolling through it. */ 396 void show_help(void) 397 { 398 int kbinput = ERR; 399 functionptrtype function; 400 /* The function of the key the user typed in. */ 401 int oldmenu = currmenu; 402 /* The menu we were called from. */ 403 #ifdef ENABLE_LINENUMBERS 404 int was_margin = margin; 405 #endif 406 ssize_t was_tabsize = tabsize; 407 #ifdef ENABLE_COLOR 408 char *was_syntax = syntaxstr; 409 #endif 410 char *saved_answer = (answer != NULL) ? copy_of(answer) : NULL; 411 /* The current answer when the user invokes help at the prompt. */ 412 unsigned stash[sizeof(flags) / sizeof(flags[0])]; 413 /* A storage place for the current flag settings. */ 414 linestruct *line; 415 int length; 416 417 /* Save the settings of all flags. */ 418 memcpy(stash, flags, sizeof(flags)); 419 420 /* Ensure that the help screen's shortcut list can be displayed. */ 421 if (ISSET(NO_HELP) || ISSET(ZERO)) { 422 UNSET(NO_HELP); 423 UNSET(ZERO); 424 window_init(); 425 } else 426 blank_statusbar(); 427 428 /* When searching, do it forward, case insensitive, and without regexes. */ 429 UNSET(BACKWARDS_SEARCH); 430 UNSET(CASE_SENSITIVE); 431 UNSET(USE_REGEXP); 432 433 UNSET(WHITESPACE_DISPLAY); 434 435 #ifdef ENABLE_LINENUMBERS 436 editwincols = COLS - sidebar; 437 margin = 0; 438 #endif 439 tabsize = 8; 440 #ifdef ENABLE_COLOR 441 syntaxstr = "nanohelp"; 442 #endif 443 curs_set(0); 444 445 /* Compose the help text from all the relevant pieces. */ 446 help_init(); 447 448 inhelp = TRUE; 449 location = 0; 450 didfind = 0; 451 452 bottombars(MHELP); 453 454 /* Extract the title from the head of the help text. */ 455 length = break_line(help_text, HIGHEST_POSITIVE, TRUE); 456 title = measured_copy(help_text, length); 457 458 titlebar(title); 459 460 /* Skip over the title to point at the start of the body text. */ 461 start_of_body = help_text + length; 462 while (*start_of_body == '\n') 463 start_of_body++; 464 465 wrap_help_text_into_buffer(); 466 edit_refresh(); 467 468 while (TRUE) { 469 lastmessage = VACUUM; 470 focusing = TRUE; 471 472 /* Show the cursor when we searched and found something. */ 473 kbinput = get_kbinput(midwin, didfind == 1 || ISSET(SHOW_CURSOR)); 474 475 didfind = 0; 476 477 #ifndef NANO_TINY 478 spotlighted = FALSE; 479 #endif 480 function = interpret(kbinput); 481 482 if (ISSET(SHOW_CURSOR) && (function == do_left || function == do_right || 483 function == do_up || function == do_down)) { 484 function(); 485 } else if (function == do_up || function == do_scroll_up) { 486 do_scroll_up(); 487 } else if (function == do_down || function == do_scroll_down) { 488 if (openfile->edittop->lineno + editwinrows - 1 < openfile->filebot->lineno) 489 do_scroll_down(); 490 } else if (function == do_page_up || function == do_page_down || 491 function == to_first_line || function == to_last_line) { 492 function(); 493 } else if (function == do_search_backward || function == do_search_forward || 494 function == do_findprevious || function == do_findnext) { 495 function(); 496 bottombars(MHELP); 497 #ifdef ENABLE_NANORC 498 } else if (function == (functionptrtype)implant) { 499 implant(first_sc_for(MHELP, function)->expansion); 500 #endif 501 #ifdef ENABLE_MOUSE 502 } else if (kbinput == KEY_MOUSE) { 503 int dummy_row, dummy_col; 504 get_mouseinput(&dummy_row, &dummy_col, TRUE); 505 #endif 506 #ifndef NANO_TINY 507 } else if (kbinput == START_OF_PASTE) { 508 while (get_kbinput(midwin, BLIND) != END_OF_PASTE) 509 ; 510 statusline(AHEM, _("Paste is ignored")); 511 } else if (kbinput == THE_WINDOW_RESIZED) { 512 ; /* Nothing to do. */ 513 #endif 514 } else if (function == full_refresh) { 515 full_refresh(); 516 } else if (function == do_exit) { 517 break; 518 } else 519 unbound_key(kbinput); 520 521 edit_refresh(); 522 523 location = 0; 524 line = openfile->filetop; 525 526 /* Count how far (in bytes) edittop is into the file. */ 527 while (line != openfile->edittop) { 528 location += strlen(line->data); 529 line = line->next; 530 } 531 } 532 533 /* Discard the help-text buffer. */ 534 close_buffer(); 535 536 /* Restore the settings of all flags. */ 537 memcpy(flags, stash, sizeof(flags)); 538 539 #ifdef ENABLE_LINENUMBERS 540 margin = was_margin; 541 editwincols = COLS - margin - sidebar; 542 #endif 543 tabsize = was_tabsize; 544 #ifdef ENABLE_COLOR 545 syntaxstr = was_syntax; 546 have_palette = FALSE; 547 #endif 548 549 free(title); 550 title = NULL; 551 free(answer); 552 answer = saved_answer; 553 free(help_text); 554 inhelp = FALSE; 555 556 curs_set(0); 557 558 if (ISSET(NO_HELP) || ISSET(ZERO)) 559 window_init(); 560 else 561 blank_statusbar(); 562 563 bottombars(oldmenu); 564 565 #ifdef ENABLE_BROWSER 566 if (oldmenu & (MBROWSER|MWHEREISFILE|MGOTODIR)) 567 browser_refresh(); 568 else 569 #endif 570 { 571 titlebar(NULL); 572 edit_refresh(); 573 } 574 } 575 576 #endif /* ENABLE_HELP */ 577 578 /* Start the help viewer, or indicate that there is no help. */ 579 void do_help(void) 580 { 581 #ifdef ENABLE_HELP 582 show_help(); 583 #else 584 if (currmenu & (MMAIN|MBROWSER)) 585 statusbar(_("^W = Ctrl+W M-W = Alt+W")); 586 else 587 beep(); 588 #endif 589 }