/* $Id$ */ /************************************************************************** * nano.c * * * * Copyright (C) 1999-2005 Chris Allegretta * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2, or (at your option) * * any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * * **************************************************************************/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "proto.h" #include "nano.h" #ifdef HAVE_TERMIOS_H #include #endif #ifdef HAVE_GETOPT_H #include #endif #ifndef NANO_SMALL #include #endif #ifndef DISABLE_WRAPJUSTIFY static ssize_t fill = 0; /* Fill - where to wrap lines, basically */ #endif #ifndef DISABLE_WRAPPING static bool same_line_wrap = FALSE; /* Whether wrapped text should be prepended to the next line */ #endif static struct termios oldterm; /* The user's original term settings */ static struct sigaction act; /* For all our fun signal handlers */ #ifndef NANO_SMALL static sigjmp_buf jmpbuf; /* Used to return to mainloop after SIGWINCH */ static int pid; /* The PID of the newly forked process * in open_pipe(). It must be global * because the signal handler needs * it. */ #endif #ifndef DISABLE_JUSTIFY static filestruct *jusbottom = NULL; /* Pointer to end of justify buffer. */ #endif void print_view_warning(void) { statusbar(_("Key illegal in VIEW mode")); } /* What we do when we're all set to exit. */ void finish(void) { if (!ISSET(NO_HELP)) blank_bottombars(); else blank_statusbar(); wrefresh(bottomwin); endwin(); /* Restore the old terminal settings. */ tcsetattr(0, TCSANOW, &oldterm); #if !defined(NANO_SMALL) && defined(ENABLE_NANORC) if (!ISSET(NO_RCFILE) && ISSET(HISTORYLOG)) save_history(); #endif #ifdef DEBUG thanks_for_all_the_fish(); #endif exit(0); } /* Die (gracefully?). */ void die(const char *msg, ...) { va_list ap; endwin(); curses_ended = TRUE; /* Restore the old terminal settings. */ tcsetattr(0, TCSANOW, &oldterm); va_start(ap, msg); vfprintf(stderr, msg, ap); va_end(ap); /* Save the current file buffer if it's been modified. */ if (ISSET(MODIFIED)) die_save_file(filename); #ifdef ENABLE_MULTIBUFFER /* Save all of the other modified file buffers, if any. */ if (open_files != NULL) { openfilestruct *tmp = open_files; while (tmp != open_files->next) { open_files = open_files->next; /* Save the current file buffer if it's been modified. */ if (open_files->flags & MODIFIED) { /* Set fileage and filebot to match the current file * buffer, and then write it to disk. */ fileage = open_files->fileage; filebot = open_files->filebot; die_save_file(open_files->filename); } } } #endif /* Get out. */ exit(1); } void die_save_file(const char *die_filename) { char *ret; bool failed = TRUE; /* If we're using restricted mode, don't write any emergency backup * files, since that would allow reading from or writing to files * not specified on the command line. */ if (ISSET(RESTRICTED)) return; /* If we can't save, we have REAL bad problems, but we might as well TRY. */ if (die_filename[0] == '\0') die_filename = "nano"; ret = get_next_filename(die_filename); if (ret[0] != '\0') failed = (write_file(ret, TRUE, FALSE, TRUE) == -1); if (!failed) fprintf(stderr, _("\nBuffer written to %s\n"), ret); else fprintf(stderr, _("\nBuffer not written to %s (too many backup files?)\n"), ret); free(ret); } /* Die with an error message that the screen was too small if, well, the * screen is too small. */ void check_die_too_small(void) { editwinrows = LINES - 5 + no_more_space() + no_help(); if (editwinrows < MIN_EDITOR_ROWS) die(_("Window size is too small for nano...\n")); } /* Reassign variables that depend on the window size. That is, fill and * hblank. */ void resize_variables(void) { #ifndef DISABLE_WRAPJUSTIFY fill = wrap_at; if (fill <= 0) fill += COLS; if (fill < 0) fill = 0; #endif hblank = charealloc(hblank, COLS + 1); memset(hblank, ' ', COLS); hblank[COLS] = '\0'; } /* Initialize global variables -- no better way for now. If * save_cutbuffer is TRUE, don't set cutbuffer to NULL. */ void global_init(bool save_cutbuffer) { check_die_too_small(); resize_variables(); fileage = NULL; edittop = NULL; current = NULL; if (!save_cutbuffer) cutbuffer = NULL; current_x = 0; placewewant = 0; current_y = 0; totlines = 0; totsize = 0; } void window_init(void) { check_die_too_small(); if (topwin != NULL) delwin(topwin); if (edit != NULL) delwin(edit); if (bottomwin != NULL) delwin(bottomwin); /* Set up the windows. */ topwin = newwin(2 - no_more_space(), COLS, 0, 0); edit = newwin(editwinrows, COLS, 2 - no_more_space(), 0); bottomwin = newwin(3 - no_help(), COLS, editwinrows + (2 - no_more_space()), 0); /* Turn the keypad back on. */ keypad(edit, TRUE); keypad(bottomwin, TRUE); } #ifndef DISABLE_MOUSE void mouse_init(void) { if (ISSET(USE_MOUSE)) { mousemask(BUTTON1_RELEASED, NULL); mouseinterval(50); } else mousemask(0, NULL); } #endif #ifndef DISABLE_HELP /* This function allocates help_text, and stores the help string in it. * help_text should be NULL initially. */ void help_init(void) { size_t allocsize = 1; /* Space needed for help_text. */ const char *htx; /* Untranslated help message. */ char *ptr; const shortcut *s; #ifndef NANO_SMALL const toggle *t; #ifdef ENABLE_NANORC bool old_whitespace = ISSET(WHITESPACE_DISPLAY); UNSET(WHITESPACE_DISPLAY); #endif #endif /* First, set up the initial help text for the current function. */ if (currshortcut == whereis_list || currshortcut == replace_list || currshortcut == replace_list_2) htx = N_("Search Command Help Text\n\n " "Enter the words or characters you would like to search " "for, then hit Enter. If there is a match for the text you " "entered, the screen will be updated to the location of the " "nearest match for the search string.\n\n The previous " "search string will be shown in brackets after the search " "prompt. Hitting Enter without entering any text will " "perform the previous search. If you have selected text " "with the mark and then search to replace, only matches in " "the selected text will be replaced.\n\n The following " "function keys are available in Search mode:\n\n"); else if (currshortcut == gotoline_list) htx = N_("Go To Line Help Text\n\n " "Enter the line number that you wish to go to and hit " "Enter. If there are fewer lines of text than the " "number you entered, you will be brought to the last line " "of the file.\n\n The following function keys are " "available in Go To Line mode:\n\n"); else if (currshortcut == insertfile_list) htx = N_("Insert File Help Text\n\n " "Type in the name of a file to be inserted into the current " "file buffer at the current cursor location.\n\n " "If you have compiled nano with multiple file buffer " "support, and enable multiple buffers with the -F " "or --multibuffer command line flags, the Meta-F toggle, or " "a nanorc file, inserting a file will cause it to be " "loaded into a separate buffer (use Meta-< and > to switch " "between file buffers). If you need another blank buffer, " "do not enter any filename, or type in a nonexistent " "filename at the prompt and press Enter.\n\n The following " "function keys are available in Insert File mode:\n\n"); else if (currshortcut == writefile_list) htx = N_("Write File Help Text\n\n " "Type the name that you wish to save the current file " "as and hit Enter to save the file.\n\n If you have " "selected text with the mark, you will be prompted to " "save only the selected portion to a separate file. To " "reduce the chance of overwriting the current file with " "just a portion of it, the current filename is not the " "default in this mode.\n\n The following function keys " "are available in Write File mode:\n\n"); #ifndef DISABLE_BROWSER else if (currshortcut == browser_list) htx = N_("File Browser Help Text\n\n " "The file browser is used to visually browse the " "directory structure to select a file for reading " "or writing. You may use the arrow keys or Page Up/" "Down to browse through the files, and S or Enter to " "choose the selected file or enter the selected " "directory. To move up one level, select the directory " "called \"..\" at the top of the file list.\n\n The " "following function keys are available in the file " "browser:\n\n"); else if (currshortcut == gotodir_list) htx = N_("Browser Go To Directory Help Text\n\n " "Enter the name of the directory you would like to " "browse to.\n\n If tab completion has not been disabled, " "you can use the Tab key to (attempt to) automatically " "complete the directory name.\n\n The following function " "keys are available in Browser Go To Directory mode:\n\n"); #endif #ifndef DISABLE_SPELLER else if (currshortcut == spell_list) htx = N_("Spell Check Help Text\n\n " "The spell checker checks the spelling of all text " "in the current file. When an unknown word is " "encountered, it is highlighted and a replacement can " "be edited. It will then prompt to replace every " "instance of the given misspelled word in the " "current file, or, if you have selected text with the " "mark, in the selected text.\n\n The following other " "functions are available in Spell Check mode:\n\n"); #endif #ifndef NANO_SMALL else if (currshortcut == extcmd_list) htx = N_("External Command Help Text\n\n " "This menu allows you to insert the output of a command " "run by the shell into the current buffer (or a new " "buffer in multibuffer mode). If you need another blank " "buffer, do not enter any command.\n\n The following keys " "are available in this mode:\n\n"); #endif else /* Default to the main help list. */ htx = N_(" nano help text\n\n " "The nano editor is designed to emulate the functionality and " "ease-of-use of the UW Pico text editor. There are four main " "sections of the editor: The top line shows the program " "version, the current filename being edited, and whether " "or not the file has been modified. Next is the main editor " "window showing the file being edited. The status line is " "the third line from the bottom and shows important messages. " "The bottom two lines show the most commonly used shortcuts " "in the editor.\n\n " "The notation for shortcuts is as follows: Control-key " "sequences are notated with a caret (^) symbol and can be " "entered either by using the Control (Ctrl) key or pressing the " "Esc key twice. Escape-key sequences are notated with the Meta " "(M) symbol and can be entered using either the Esc, Alt or " "Meta key depending on your keyboard setup. Also, pressing Esc " "twice and then typing a three-digit decimal number from 000 to " " 255 will enter the character with the corresponding value. " "The following keystrokes are available in the main editor " " window. Alternative keys are shown in parentheses:\n\n"); htx = _(htx); allocsize += strlen(htx); /* The space needed for the shortcut lists, at most COLS characters, * plus '\n'. */ allocsize += (COLS < 21 ? 21 : COLS + 1) * length_of_list(currshortcut); #ifndef NANO_SMALL /* If we're on the main list, we also count the toggle help text. * Each line has "M-%c\t\t\t", which fills 24 columns, plus a space, * plus translated text, plus '\n'. */ if (currshortcut == main_list) { size_t endis_len = strlen(_("enable/disable")); for (t = toggles; t != NULL; t = t->next) allocsize += 8 + strlen(t->desc) + endis_len; } #endif /* help_text has been freed and set to NULL unless the user resized * while in the help screen. */ free(help_text); /* Allocate space for the help text. */ help_text = charalloc(allocsize); /* Now add the text we want. */ strcpy(help_text, htx); ptr = help_text + strlen(help_text); /* Now add our shortcut info. Assume that each shortcut has, at the * very least, an equivalent control key, an equivalent primary meta * key sequence, or both. Also assume that the meta key values are * not control characters. We can display a maximum of 3 shortcut * entries. */ for (s = currshortcut; s != NULL; s = s->next) { int entries = 0; /* Control key. */ if (s->ctrlval != NANO_NO_KEY) { entries++; #ifndef NANO_SMALL if (s->ctrlval == NANO_HISTORY_KEY) { char *up_ptr = display_string(_("Up"), 0, 7, FALSE); ptr += sprintf(ptr, "%s", up_ptr); free(up_ptr); } else #endif if (s->ctrlval == NANO_CONTROL_SPACE) { char *space_ptr = display_string(_("Space"), 0, 6, FALSE); ptr += sprintf(ptr, "^%s", space_ptr); free(space_ptr); } else if (s->ctrlval == NANO_CONTROL_8) ptr += sprintf(ptr, "^?"); else ptr += sprintf(ptr, "^%c", s->ctrlval + 64); *(ptr++) = '\t'; } /* Function key. */ if (s->funcval != NANO_NO_KEY) { entries++; /* If this is the first entry, put it in the middle. */ if (entries == 1) { entries++; *(ptr++) = '\t'; } ptr += sprintf(ptr, "(F%d)", s->funcval - KEY_F0); *(ptr++) = '\t'; } /* Primary meta key sequence. */ if (s->metaval != NANO_NO_KEY) { entries++; /* If this is the last entry, put it at the end. */ if (entries == 2 && s->miscval == NANO_NO_KEY) { entries++; *(ptr++) = '\t'; } /* If the primary meta key sequence is the first entry, * don't put parentheses around it. */ if (entries == 1 && s->metaval == NANO_ALT_SPACE) { char *space_ptr = display_string(_("Space"), 0, 5, FALSE); ptr += sprintf(ptr, "M-%s", space_ptr); free(space_ptr); } else ptr += sprintf(ptr, entries == 1 ? "M-%c" : "(M-%c)", toupper(s->metaval)); *(ptr++) = '\t'; } /* Miscellaneous meta key sequence. */ if (entries < 3 && s->miscval != NANO_NO_KEY) { entries++; /* If this is the last entry, put it at the end. */ if (entries == 2) { entries++; *(ptr++) = '\t'; } ptr += sprintf(ptr, "(M-%c)", toupper(s->miscval)); *(ptr++) = '\t'; } /* Make sure all the help text starts at the same place. */ while (entries < 3) { entries++; *(ptr++) = '\t'; } assert(s->help != NULL); if (COLS > 24) { char *help_ptr = display_string(s->help, 0, COLS - 24, FALSE); ptr += sprintf(ptr, help_ptr); free(help_ptr); } ptr += sprintf(ptr, "\n"); } #ifndef NANO_SMALL /* And the toggles... */ if (currshortcut == main_list) { for (t = toggles; t != NULL; t = t->next) { assert(t->desc != NULL); ptr += sprintf(ptr, "M-%c\t\t\t%s %s\n", toupper(t->val), t->desc, _("enable/disable")); } } #ifdef ENABLE_NANORC if (old_whitespace) SET(WHITESPACE_DISPLAY); #endif #endif /* If all went well, we didn't overwrite the allocated space for * help_text. */ assert(strlen(help_text) < allocsize); } #endif /* Create a new filestruct node. Note that we specifically do not set * prevnode->next equal to the new line. */ filestruct *make_new_node(filestruct *prevnode) { filestruct *newnode = (filestruct *)nmalloc(sizeof(filestruct)); newnode->data = NULL; newnode->prev = prevnode; newnode->next = NULL; newnode->lineno = (prevnode != NULL) ? prevnode->lineno + 1 : 1; return newnode; } /* Make a copy of a filestruct node. */ filestruct *copy_node(const filestruct *src) { filestruct *dst; assert(src != NULL); dst = (filestruct *)nmalloc(sizeof(filestruct)); dst->data = mallocstrcpy(NULL, src->data); dst->next = src->next; dst->prev = src->prev; dst->lineno = src->lineno; return dst; } /* Splice a node into an existing filestruct. */ void splice_node(filestruct *begin, filestruct *newnode, filestruct *end) { assert(newnode != NULL && begin != NULL); newnode->next = end; newnode->prev = begin; begin->next = newnode; if (end != NULL) end->prev = newnode; } /* Unlink a node from the rest of the filestruct. */ void unlink_node(const filestruct *fileptr) { assert(fileptr != NULL); if (fileptr->prev != NULL) fileptr->prev->next = fileptr->next; if (fileptr->next != NULL) fileptr->next->prev = fileptr->prev; } /* Delete a node from the filestruct. */ void delete_node(filestruct *fileptr) { assert(fileptr != NULL && fileptr->data != NULL); if (fileptr->data != NULL) free(fileptr->data); free(fileptr); } /* Okay, now let's duplicate a whole struct! */ filestruct *copy_filestruct(const filestruct *src) { filestruct *head; /* copy of src, top of the copied list */ filestruct *prev; /* temp that traverses the list */ assert(src != NULL); prev = copy_node(src); prev->prev = NULL; head = prev; src = src->next; while (src != NULL) { prev->next = copy_node(src); prev->next->prev = prev; prev = prev->next; src = src->next; } prev->next = NULL; return head; } /* Frees a filestruct. */ void free_filestruct(filestruct *src) { assert(src != NULL); while (src->next != NULL) { src = src->next; delete_node(src->prev); } delete_node(src); } /* Partition a filestruct so it begins at (top, top_x) and ends at (bot, * bot_x). */ partition *partition_filestruct(filestruct *top, size_t top_x, filestruct *bot, size_t bot_x) { partition *p; assert(top != NULL && bot != NULL && fileage != NULL && filebot != NULL); /* Initialize the partition. */ p = (partition *)nmalloc(sizeof(partition)); /* If the top and bottom of the partition are different from the top * and bottom of the filestruct, save the latter and then set them * to top and bot. */ if (top != fileage) { p->fileage = fileage; fileage = top; } else p->fileage = NULL; if (bot != filebot) { p->filebot = filebot; filebot = bot; } else p->filebot = NULL; /* Save the line above the top of the partition, detach the top of * the partition from it, and save the text before top_x in * top_data. */ p->top_prev = top->prev; top->prev = NULL; p->top_data = mallocstrncpy(NULL, top->data, top_x + 1); p->top_data[top_x] = '\0'; /* Save the line below the bottom of the partition, detach the * bottom of the partition from it, and save the text after bot_x in * bot_data. */ p->bot_next = bot->next; bot->next = NULL; p->bot_data = mallocstrcpy(NULL, bot->data + bot_x); /* Remove all text after bot_x at the bottom of the partition. */ null_at(&bot->data, bot_x); /* Remove all text before top_x at the top of the partition. */ charmove(top->data, top->data + top_x, strlen(top->data) - top_x + 1); align(&top->data); /* Return the partition. */ return p; } /* Unpartition a filestruct so it begins at (fileage, 0) and ends at * (filebot, strlen(filebot)) again. */ void unpartition_filestruct(partition **p) { char *tmp; assert(p != NULL && fileage != NULL && filebot != NULL); /* Reattach the line above the top of the partition, and restore the * text before top_x from top_data. Free top_data when we're done * with it. */ tmp = mallocstrcpy(NULL, fileage->data); fileage->prev = (*p)->top_prev; if (fileage->prev != NULL) fileage->prev->next = fileage; fileage->data = charealloc(fileage->data, strlen((*p)->top_data) + strlen(fileage->data) + 1); strcpy(fileage->data, (*p)->top_data); free((*p)->top_data); strcat(fileage->data, tmp); free(tmp); /* Reattach the line below the bottom of the partition, and restore * the text after bot_x from bot_data. Free bot_data when we're * done with it. */ filebot->next = (*p)->bot_next; if (filebot->next != NULL) filebot->next->prev = filebot; filebot->data = charealloc(filebot->data, strlen(filebot->data) + strlen((*p)->bot_data) + 1); strcat(filebot->data, (*p)->bot_data); free((*p)->bot_data); /* Restore the top and bottom of the filestruct, if they were * different from the top and bottom of the partition. */ if ((*p)->fileage != NULL) fileage = (*p)->fileage; if ((*p)->filebot != NULL) filebot = (*p)->filebot; /* Uninitialize the partition. */ free(*p); *p = NULL; } /* Move all the text between (top, top_x) and (bot, bot_x) in the * current filestruct to a filestruct beginning with file_top and ending * with file_bot. If no text is between (top, top_x) and (bot, bot_x), * don't do anything. */ void move_to_filestruct(filestruct **file_top, filestruct **file_bot, filestruct *top, size_t top_x, filestruct *bot, size_t bot_x) { filestruct *top_save; size_t part_totsize; bool at_edittop; #ifndef NANO_SMALL bool mark_inside = FALSE; #endif assert(file_top != NULL && file_bot != NULL && top != NULL && bot != NULL); /* If (top, top_x)-(bot, bot_x) doesn't cover any text, get out. */ if (top == bot && top_x == bot_x) return; /* Partition the filestruct so that it contains only the text from * (top, top_x) to (bot, bot_x), keep track of whether the top of * the partition is the top of the edit window, and keep track of * whether the mark begins inside the partition. */ filepart = partition_filestruct(top, top_x, bot, bot_x); at_edittop = (fileage == edittop); #ifndef NANO_SMALL if (ISSET(MARK_ISSET)) mark_inside = (mark_beginbuf->lineno >= fileage->lineno && mark_beginbuf->lineno <= filebot->lineno && (mark_beginbuf != fileage || mark_beginx >= top_x) && (mark_beginbuf != filebot || mark_beginx <= bot_x)); #endif /* Get the number of characters in the text, and subtract it from * totsize. */ get_totals(top, bot, NULL, &part_totsize); totsize -= part_totsize; if (*file_top == NULL) { /* If file_top is empty, just move all the text directly into * it. This is equivalent to tacking the text in top onto the * (lack of) text at the end of file_top. */ *file_top = fileage; *file_bot = filebot; } else { /* Otherwise, tack the text in top onto the text at the end of * file_bot. */ (*file_bot)->data = charealloc((*file_bot)->data, strlen((*file_bot)->data) + strlen(fileage->data) + 1); strcat((*file_bot)->data, fileage->data); /* Attach the line after top to the line after file_bot. Then, * if there's more than one line after top, move file_bot down * to bot. */ (*file_bot)->next = fileage->next; if ((*file_bot)->next != NULL) { (*file_bot)->next->prev = *file_bot; *file_bot = filebot; } } /* Since the text has now been saved, remove it from the filestruct. * If the top of the partition was the top of the edit window, set * edittop to where the text used to start. If the mark began * inside the partition, set the beginning of the mark to where the * text used to start. */ fileage = (filestruct *)nmalloc(sizeof(filestruct)); fileage->data = mallocstrcpy(NULL, ""); filebot = fileage; if (at_edittop) edittop = fileage; #ifndef NANO_SMALL if (mark_inside) { mark_beginbuf = fileage; mark_beginx = top_x; } #endif /* Restore the current line and cursor position. */ current = fileage; current_x = top_x; top_save = fileage; /* Unpartition the filestruct so that it contains all the text * again, minus the saved text. */ unpartition_filestruct(&filepart); /* Renumber starting with the beginning line of the old * partition. */ renumber(top_save); if (filebot->data[0] != '\0') new_magicline(); /* Set totlines to the new number of lines in the file. */ totlines = filebot->lineno; } /* Copy all the text from the filestruct beginning with file_top and * ending with file_bot to the current filestruct at the current cursor * position. */ void copy_from_filestruct(filestruct *file_top, filestruct *file_bot) { filestruct *top_save; int part_totlines; size_t part_totsize; bool at_edittop; assert(file_top != NULL && file_bot != NULL); /* Partition the filestruct so that it contains no text, and keep * track of whether the top of the partition is the top of the edit * window. */ filepart = partition_filestruct(current, current_x, current, current_x); at_edittop = (fileage == edittop); /* Put the top and bottom of the filestruct at copies of file_top * and file_bot. */ fileage = copy_filestruct(file_top); filebot = fileage; while (filebot->next != NULL) filebot = filebot->next; /* Restore the current line and cursor position. */ current = filebot; current_x = strlen(filebot->data); if (fileage == filebot) current_x += strlen(filepart->top_data); /* Get the number of lines and the number of characters in the saved * text, and add the latter to totsize. */ get_totals(fileage, filebot, &part_totlines, &part_totsize); totsize += part_totsize; /* If the top of the partition was the top of the edit window, set * edittop to where the saved text now starts, and update the * current y-coordinate to account for the number of lines it * has, less one since the first line will be tacked onto the * current line. */ if (at_edittop) edittop = fileage; current_y += part_totlines - 1; top_save = fileage; /* Unpartition the filestruct so that it contains all the text * again, minus the saved text. */ unpartition_filestruct(&filepart); /* Renumber starting with the beginning line of the old * partition. */ renumber(top_save); if (filebot->data[0] != '\0') new_magicline(); /* Set totlines to the new number of lines in the file. */ totlines = filebot->lineno; } void renumber_all(void) { filestruct *temp; int i = 1; assert(fileage == NULL || fileage != fileage->next); for (temp = fileage; temp != NULL; temp = temp->next) temp->lineno = i++; } void renumber(filestruct *fileptr) { if (fileptr == NULL || fileptr->prev == NULL || fileptr == fileage) renumber_all(); else { int lineno = fileptr->prev->lineno; assert(fileptr != fileptr->next); for (; fileptr != NULL; fileptr = fileptr->next) fileptr->lineno = ++lineno; } } /* Print one usage string to the screen. This cuts down on duplicate * strings to translate and leaves out the parts that shouldn't be * translatable (the flag names). */ void print1opt(const char *shortflag, const char *longflag, const char *desc) { printf(" %s\t", shortflag); if (strlen(shortflag) < 8) printf("\t"); #ifdef HAVE_GETOPT_LONG printf("%s\t", longflag); if (strlen(longflag) < 8) printf("\t\t"); else if (strlen(longflag) < 16) printf("\t"); #endif printf("%s\n", _(desc)); } void usage(void) { #ifdef HAVE_GETOPT_LONG printf(_("Usage: nano [+LINE] [GNU long option] [option] [file]\n\n")); printf(_("Option\t\tLong option\t\tMeaning\n")); #else printf(_("Usage: nano [+LINE] [option] [file]\n\n")); printf(_("Option\t\tMeaning\n")); #endif print1opt("-h, -?", "--help", N_("Show this message")); print1opt(_("+LINE"), "", N_("Start at line number LINE")); #ifndef NANO_SMALL print1opt("-A", "--smarthome", N_("Enable smart home key")); print1opt("-B", "--backup", N_("Backup existing files on save")); print1opt(_("-E [dir]"), _("--backupdir=[dir]"), N_("Directory for writing backup files")); #endif #ifdef ENABLE_MULTIBUFFER print1opt("-F", "--multibuffer", N_("Enable multiple file buffers")); #endif #ifdef ENABLE_NANORC #ifndef NANO_SMALL print1opt("-H", "--historylog", N_("Log & read search/replace string history")); #endif print1opt("-I", "--ignorercfiles", N_("Don't look at nanorc files")); #endif #ifndef NANO_SMALL print1opt("-N", "--noconvert", N_("Don't convert files from DOS/Mac format")); #endif print1opt("-O", "--morespace", N_("Use more space for editing")); #ifndef DISABLE_JUSTIFY print1opt(_("-Q [str]"), _("--quotestr=[str]"), N_("Quoting string, default \"> \"")); #endif #ifdef HAVE_REGEX_H print1opt("-R", "--regexp", N_("Do regular expression searches")); #endif #ifndef NANO_SMALL print1opt("-S", "--smooth", N_("Smooth scrolling")); #endif print1opt(_("-T [#cols]"), _("--tabsize=[#cols]"), N_("Set width of a tab in cols to #cols")); print1opt("-V", "--version", N_("Print version information and exit")); #ifdef ENABLE_COLOR print1opt(_("-Y [str]"), _("--syntax=[str]"), N_("Syntax definition to use")); #endif print1opt("-Z", "--restricted", N_("Restricted mode")); print1opt("-c", "--const", N_("Constantly show cursor position")); #ifndef NANO_SMALL print1opt("-d", "--rebinddelete", N_("Fix Backspace/Delete confusion problem")); print1opt("-i", "--autoindent", N_("Automatically indent new lines")); print1opt("-k", "--cut", N_("Cut from cursor to end of line")); #endif print1opt("-l", "--nofollow", N_("Don't follow symbolic links, overwrite")); #ifndef DISABLE_MOUSE print1opt("-m", "--mouse", N_("Enable mouse")); #endif #ifndef DISABLE_OPERATINGDIR print1opt(_("-o [dir]"), _("--operatingdir=[dir]"), N_("Set operating directory")); #endif print1opt("-p", "--preserve", N_("Preserve XON (^Q) and XOFF (^S) keys")); #ifndef DISABLE_WRAPJUSTIFY print1opt(_("-r [#cols]"), _("--fill=[#cols]"), N_("Set fill cols to (wrap lines at) #cols")); #endif #ifndef DISABLE_SPELLER print1opt(_("-s [prog]"), _("--speller=[prog]"), N_("Enable alternate speller")); #endif print1opt("-t", "--tempfile", N_("Auto save on exit, don't prompt")); print1opt("-v", "--view", N_("View (read only) mode")); #ifndef DISABLE_WRAPPING print1opt("-w", "--nowrap", N_("Don't wrap long lines")); #endif print1opt("-x", "--nohelp", N_("Don't show help window")); print1opt("-z", "--suspend", N_("Enable suspend")); /* This is a special case. */ printf(" %s\t\t\t%s\n","-a, -b, -e, -f, -g, -j", _("(ignored, for Pico compatibility)")); exit(0); } void version(void) { printf(_(" GNU nano version %s (compiled %s, %s)\n"), VERSION, __TIME__, __DATE__); printf( _(" Email: nano@nano-editor.org Web: http://www.nano-editor.org/")); printf(_("\n Compiled options:")); #ifndef ENABLE_NLS printf(" --disable-nls"); #endif #ifdef DEBUG printf(" --enable-debug"); #endif #ifdef NANO_EXTRA printf(" --enable-extra"); #endif #ifdef NANO_SMALL printf(" --enable-tiny"); #else #ifdef DISABLE_BROWSER printf(" --disable-browser"); #endif #ifdef DISABLE_HELP printf(" --disable-help"); #endif #ifdef DISABLE_JUSTIFY printf(" --disable-justify"); #endif #ifdef DISABLE_MOUSE printf(" --disable-mouse"); #endif #ifdef DISABLE_OPERATINGDIR printf(" --disable-operatingdir"); #endif #ifdef DISABLE_SPELLER printf(" --disable-speller"); #endif #ifdef DISABLE_TABCOMP printf(" --disable-tabcomp"); #endif #endif /* NANO_SMALL */ #ifdef DISABLE_WRAPPING printf(" --disable-wrapping"); #endif #ifdef DISABLE_ROOTWRAP printf(" --disable-wrapping-as-root"); #endif #ifdef ENABLE_COLOR printf(" --enable-color"); #endif #ifdef ENABLE_MULTIBUFFER printf(" --enable-multibuffer"); #endif #ifdef ENABLE_NANORC printf(" --enable-nanorc"); #endif #ifdef USE_SLANG printf(" --with-slang"); #endif printf("\n"); } int no_more_space(void) { return ISSET(MORE_SPACE) ? 1 : 0; } int no_help(void) { return ISSET(NO_HELP) ? 2 : 0; } void nano_disabled_msg(void) { statusbar(_("Sorry, support for this function has been disabled")); } #ifndef NANO_SMALL void cancel_fork(int signal) { if (kill(pid, SIGKILL) == -1) nperror("kill"); } /* Return TRUE on success. */ bool open_pipe(const char *command) { int fd[2]; FILE *f; struct sigaction oldaction, newaction; /* Original and temporary handlers for * SIGINT. */ bool sig_failed = FALSE; /* sig_failed means that sigaction() failed without changing the * signal handlers. * * We use this variable since it is important to put things back * when we finish, even if we get errors. */ /* Make our pipes. */ if (pipe(fd) == -1) { statusbar(_("Could not pipe")); return FALSE; } /* Fork a child. */ if ((pid = fork()) == 0) { close(fd[0]); dup2(fd[1], fileno(stdout)); dup2(fd[1], fileno(stderr)); /* If execl() returns at all, there was an error. */ execl("/bin/sh", "sh", "-c", command, NULL); exit(0); } /* Else continue as parent. */ close(fd[1]); if (pid == -1) { close(fd[0]); statusbar(_("Could not fork")); return FALSE; } /* Before we start reading the forked command's output, we set * things up so that ^C will cancel the new process. */ /* Enable interpretation of the special control keys so that we get * SIGINT when Ctrl-C is pressed. */ enable_signals(); if (sigaction(SIGINT, NULL, &newaction) == -1) { sig_failed = TRUE; nperror("sigaction"); } else { newaction.sa_handler = cancel_fork; if (sigaction(SIGINT, &newaction, &oldaction) == -1) { sig_failed = TRUE; nperror("sigaction"); } } /* Note that now oldaction is the previous SIGINT signal handler, * to be restored later. */ f = fdopen(fd[0], "rb"); if (f == NULL) nperror("fdopen"); read_file(f, "stdin"); /* If multibuffer mode is on, we could be here in view mode. If so, * don't set the modification flag. */ if (!ISSET(VIEW_MODE)) set_modified(); if (wait(NULL) == -1) nperror("wait"); if (!sig_failed && sigaction(SIGINT, &oldaction, NULL) == -1) nperror("sigaction"); /* Disable interpretation of the special control keys so that we can * use Ctrl-C for other things. */ disable_signals(); return TRUE; } #endif /* !NANO_SMALL */ void do_verbatim_input(void) { int *kbinput; size_t kbinput_len, i; char *output; statusbar(_("Verbatim input")); /* Read in all the verbatim characters. */ kbinput = get_verbatim_kbinput(edit, &kbinput_len); /* Display all the verbatim characters at once. */ output = charalloc(kbinput_len + 1); for (i = 0; i < kbinput_len; i++) output[i] = (char)kbinput[i]; output[i] = '\0'; do_output(output, kbinput_len); free(output); } void do_backspace(void) { if (current != fileage || current_x > 0) { do_left(FALSE); do_delete(); } } void do_delete(void) { bool do_refresh = FALSE; /* Do we have to call edit_refresh(), or can we get away with * update_line()? */ assert(current != NULL && current->data != NULL && current_x <= strlen(current->data)); placewewant = xplustabs(); if (current->data[current_x] != '\0') { int char_buf_len = parse_mbchar(current->data + current_x, NULL, NULL, NULL); size_t line_len = strlen(current->data + current_x); assert(current_x < strlen(current->data)); /* Let's get dangerous. */ charmove(¤t->data[current_x], ¤t->data[current_x + char_buf_len], line_len - char_buf_len + 1); null_at(¤t->data, current_x + line_len - char_buf_len); #ifndef NANO_SMALL if (current_x < mark_beginx && mark_beginbuf == current) mark_beginx -= char_buf_len; #endif totsize--; } else if (current != filebot && (current->next != filebot || current->data[0] == '\0')) { /* We can delete the line before filebot only if it is blank: it * becomes the new magicline then. */ filestruct *foo = current->next; assert(current_x == strlen(current->data)); /* If we're deleting at the end of a line, we need to call * edit_refresh(). */ if (current->data[current_x] == '\0') do_refresh = TRUE; current->data = charealloc(current->data, current_x + strlen(foo->data) + 1); strcpy(current->data + current_x, foo->data); #ifndef NANO_SMALL if (mark_beginbuf == current->next) { mark_beginx += current_x; mark_beginbuf = current; } #endif if (filebot == foo) filebot = current; unlink_node(foo); delete_node(foo); renumber(current); totlines--; totsize--; #ifndef DISABLE_WRAPPING wrap_reset(); #endif } else return; set_modified(); #ifdef ENABLE_COLOR /* If color syntaxes are turned on, we need to call * edit_refresh(). */ if (!ISSET(NO_COLOR_SYNTAX)) do_refresh = TRUE; #endif if (do_refresh) edit_refresh(); else update_line(current, current_x); } void do_tab(void) { char *kbinput = "\t"; do_output(kbinput, 1); } /* Someone hits return *gasp!* */ void do_enter(void) { filestruct *newnode = make_new_node(current); size_t extra = 0; assert(current != NULL && current->data != NULL); #ifndef NANO_SMALL /* Do auto-indenting, like the neolithic Turbo Pascal editor. */ if (ISSET(AUTOINDENT)) { /* If we are breaking the line in the indentation, the new * indentation should have only current_x characters, and * current_x should not change. */ extra = indent_length(current->data); if (extra > current_x) extra = current_x; } #endif newnode->data = charalloc(strlen(current->data + current_x) + extra + 1); strcpy(&newnode->data[extra], current->data + current_x); #ifndef NANO_SMALL if (ISSET(AUTOINDENT)) { strncpy(newnode->data, current->data, extra); totsize += mbstrlen(newnode->data); } #endif null_at(¤t->data, current_x); #ifndef NANO_SMALL if (current == mark_beginbuf && current_x < mark_beginx) { mark_beginbuf = newnode; mark_beginx += extra - current_x; } #endif current_x = extra; if (current == filebot) filebot = newnode; splice_node(current, newnode, current->next); renumber(current); current = newnode; edit_refresh(); totlines++; totsize++; set_modified(); placewewant = xplustabs(); } #ifndef NANO_SMALL /* Move to the next word. */ void do_next_word(void) { size_t pww_save = placewewant; const filestruct *current_save = current; char *char_mb; int char_mb_len; assert(current != NULL && current->data != NULL); char_mb = charalloc(mb_cur_max()); /* Move forward until we find the character after the last letter of * the current word. */ while (current->data[current_x] != '\0') { char_mb_len = parse_mbchar(current->data + current_x, char_mb, NULL, NULL); /* If we've found it, stop moving forward through the current * line. */ if (!is_alnum_mbchar(char_mb)) break; current_x += char_mb_len; } /* Move forward until we find the first letter of the next word. */ if (current->data[current_x] != '\0') current_x += char_mb_len; for (; current != NULL; current = current->next) { while (current->data[current_x] != '\0') { char_mb_len = parse_mbchar(current->data + current_x, char_mb, NULL, NULL); /* If we've found it, stop moving forward through the * current line. */ if (is_alnum_mbchar(char_mb)) break; current_x += char_mb_len; } /* If we've found it, stop moving forward to the beginnings of * subsequent lines. */ if (current->data[current_x] != '\0') break; current_x = 0; } free(char_mb); /* If we haven't found it, leave the cursor at the end of the * file. */ if (current == NULL) current = filebot; placewewant = xplustabs(); /* Update the screen. */ edit_redraw(current_save, pww_save); } /* Move to the previous word. */ void do_prev_word(void) { size_t pww_save = placewewant; const filestruct *current_save = current; char *char_mb; int char_mb_len; bool begin_line = FALSE; assert(current != NULL && current->data != NULL); char_mb = charalloc(mb_cur_max()); /* Move backward until we find the character before the first letter * of the current word. */ while (!begin_line) { char_mb_len = parse_mbchar(current->data + current_x, char_mb, NULL, NULL); /* If we've found it, stop moving backward through the current * line. */ if (!is_alnum_mbchar(char_mb)) break; if (current_x == 0) begin_line = TRUE; else current_x = move_mbleft(current->data, current_x); } /* Move backward until we find the last letter of the previous * word. */ if (current_x == 0) begin_line = TRUE; else current_x = move_mbleft(current->data, current_x); for (; current != NULL; current = current->prev) { while (!begin_line) { char_mb_len = parse_mbchar(current->data + current_x, char_mb, NULL, NULL); /* If we've found it, stop moving backward through the * current line. */ if (is_alnum_mbchar(char_mb)) break; if (current_x == 0) begin_line = TRUE; else current_x = move_mbleft(current->data, current_x); } /* If we've found it, stop moving backward to the ends of * previous lines. */ if (!begin_line) break; if (current->prev != NULL) { begin_line = FALSE; current_x = strlen(current->prev->data); } } /* If we haven't found it, leave the cursor at the beginning of the * file. */ if (current == NULL) { current = fileage; current_x = 0; /* If we've found it, move backward until we find the character * before the first letter of the previous word. */ } else if (!begin_line) { if (current_x == 0) begin_line = TRUE; else current_x = move_mbleft(current->data, current_x); while (!begin_line) { char_mb_len = parse_mbchar(current->data + current_x, char_mb, NULL, NULL); /* If we've found it, stop moving backward through the * current line. */ if (!is_alnum_mbchar(char_mb)) break; if (current_x == 0) begin_line = TRUE; else current_x = move_mbleft(current->data, current_x); } /* If we've found it, move forward to the first letter of the * previous word. */ if (!begin_line) current_x += char_mb_len; } free(char_mb); placewewant = xplustabs(); /* Update the screen. */ edit_redraw(current_save, pww_save); } void do_mark(void) { TOGGLE(MARK_ISSET); if (ISSET(MARK_ISSET)) { statusbar(_("Mark Set")); mark_beginbuf = current; mark_beginx = current_x; } else { statusbar(_("Mark UNset")); edit_refresh(); } } #endif /* !NANO_SMALL */ #ifndef DISABLE_WRAPPING void wrap_reset(void) { same_line_wrap = FALSE; } #endif #ifndef DISABLE_WRAPPING /* We wrap the given line. Precondition: we assume the cursor has been * moved forward since the last typed character. Return value: whether * we wrapped. */ bool do_wrap(filestruct *inptr) { size_t len = strlen(inptr->data); /* Length of the line we wrap. */ size_t i = 0; /* Generic loop variable. */ ssize_t wrap_loc = -1; /* Index of inptr->data where we wrap. */ ssize_t word_back = -1; #ifndef NANO_SMALL const char *indentation = NULL; /* Indentation to prepend to the new line. */ size_t indent_len = 0; /* strlen(indentation) */ #endif const char *after_break; /* Text after the wrap point. */ size_t after_break_len; /* strlen(after_break) */ bool wrapping = FALSE; /* Do we prepend to the next line? */ const char *wrap_line = NULL; /* The next line, minus indentation. */ size_t wrap_line_len = 0; /* strlen(wrap_line) */ char *newline = NULL; /* The line we create. */ size_t new_line_len = 0; /* Eventual length of newline. */ /* There are three steps. First, we decide where to wrap. Then, we * create the new wrap line. Finally, we clean up. */ /* Step 1, finding where to wrap. We are going to add a new line * after a whitespace character. In this step, we set wrap_loc as the * location of this replacement. * * Where should we break the line? We need the last legal wrap point * such that the last word before it ended at or before fill. If there * is no such point, we settle for the first legal wrap point. * * A legal wrap point is a whitespace character that is not followed by * whitespace. * * If there is no legal wrap point or we found the last character of the * line, we should return without wrapping. * * Note that the initial indentation does not count as a legal wrap * point if we are going to auto-indent! * * Note that the code below could be optimized, by not calling * strnlenpt() so often. */ #ifndef NANO_SMALL if (ISSET(AUTOINDENT)) i = indent_length(inptr->data); #endif wrap_line = inptr->data + i; for (; i < len; i++, wrap_line++) { /* Record where the last word ended. */ if (!is_blank_char(*wrap_line)) word_back = i; /* If we have found a legal wrap point and the current word * extends too far, then we stop. */ if (wrap_loc != -1 && strnlenpt(inptr->data, word_back + 1) > fill) break; /* We record the latest legal wrap point. */ if (word_back != i && !is_blank_char(wrap_line[1])) wrap_loc = i; } if (i == len) return FALSE; /* Step 2, making the new wrap line. It will consist of indentation * + after_break + " " + wrap_line (although indentation and * wrap_line are conditional on flags and #defines). */ /* after_break is the text that will be moved to the next line. */ after_break = inptr->data + wrap_loc + 1; after_break_len = len - wrap_loc - 1; assert(after_break_len == strlen(after_break)); /* new_line_len will later be increased by the lengths of indentation * and wrap_line. */ new_line_len = after_break_len; /* We prepend the wrapped text to the next line, if the flag is set, * and there is a next line, and prepending would not make the line * too long. */ if (same_line_wrap && inptr->next) { wrap_line = inptr->next->data; wrap_line_len = strlen(wrap_line); /* +1 for the space between after_break and wrap_line. */ if ((new_line_len + 1 + wrap_line_len) <= fill) { wrapping = TRUE; new_line_len += 1 + wrap_line_len; } } #ifndef NANO_SMALL if (ISSET(AUTOINDENT)) { /* Indentation comes from the next line if wrapping, else from * this line. */ indentation = wrapping ? wrap_line : inptr->data; indent_len = indent_length(indentation); if (wrapping) /* The wrap_line text should not duplicate indentation. * Note in this case we need not increase new_line_len. */ wrap_line += indent_len; else new_line_len += indent_len; } #endif /* Now we allocate the new line and copy into it. */ newline = charalloc(new_line_len + 1); /* +1 for \0 */ new_line_len = 0; *newline = '\0'; #ifndef NANO_SMALL if (ISSET(AUTOINDENT)) { strncpy(newline, indentation, indent_len); newline[indent_len] = '\0'; totsize += mbstrlen(newline); new_line_len = indent_len; } #endif strcat(newline, after_break); new_line_len += after_break_len; /* We end the old line after wrap_loc. Note that this does not eat * the space. */ null_at(&inptr->data, wrap_loc + 1); totsize++; if (wrapping) { /* In this case, totsize increases by 1 since we add a space * between after_break and wrap_line. If the line already ends * in a tab or a space, we don't add a space and decrement * totsize to account for that. */ if (!is_blank_char(newline[new_line_len - 1])) strcat(newline, " "); else totsize--; strcat(newline, wrap_line); free(inptr->next->data); inptr->next->data = newline; } else { filestruct *temp = (filestruct *)nmalloc(sizeof(filestruct)); totlines++; temp->data = newline; temp->prev = inptr; temp->next = inptr->next; temp->prev->next = temp; /* If temp->next is NULL, then temp is the last line of the * file, so we must set filebot. */ if (temp->next != NULL) temp->next->prev = temp; else filebot = temp; } /* Step 3, clean up. Here we reposition the cursor and mark, and do * some other sundry things. */ /* Later wraps of this line will be prepended to the next line. */ same_line_wrap = TRUE; /* Each line knows its line number. We recalculate these if we * inserted a new line. */ if (!wrapping) renumber(inptr); /* If the cursor was after the break point, we must move it. */ if (current_x > wrap_loc) { current = current->next; current_x -= #ifndef NANO_SMALL -indent_len + #endif wrap_loc + 1; wrap_reset(); placewewant = xplustabs(); } #ifndef NANO_SMALL /* If the mark was on this line after the wrap point, we move it * down. If it was on the next line and we wrapped, we move it * right. */ if (mark_beginbuf == inptr && mark_beginx > wrap_loc) { mark_beginbuf = inptr->next; mark_beginx -= wrap_loc - indent_len + 1; } else if (wrapping && mark_beginbuf == inptr->next) mark_beginx += after_break_len; #endif /* !NANO_SMALL */ return TRUE; } #endif /* !DISABLE_WRAPPING */ #ifndef DISABLE_SPELLER /* A word is misspelled in the file. Let the user replace it. We * return FALSE if the user cancels. */ bool do_int_spell_fix(const char *word) { char *save_search, *save_replace; size_t current_x_save = current_x, pww_save = placewewant; filestruct *edittop_save = edittop, *current_save = current; /* Save where we are. */ bool canceled = FALSE; /* The return value. */ bool case_sens_set = ISSET(CASE_SENSITIVE); #ifndef NANO_SMALL bool reverse_search_set = ISSET(REVERSE_SEARCH); #endif #ifdef HAVE_REGEX_H bool regexp_set = ISSET(USE_REGEXP); #endif #ifndef NANO_SMALL bool old_mark_set = ISSET(MARK_ISSET); bool added_magicline = FALSE; /* Whether we added a magicline after filebot. */ bool right_side_up = FALSE; /* TRUE if (mark_beginbuf, mark_beginx) is the top of the mark, * FALSE if (current, current_x) is. */ filestruct *top, *bot; size_t top_x, bot_x; #endif /* Make sure spell-check is case sensitive. */ SET(CASE_SENSITIVE); #ifndef NANO_SMALL /* Make sure spell-check goes forward only. */ UNSET(REVERSE_SEARCH); #endif #ifdef HAVE_REGEX_H /* Make sure spell-check doesn't use regular expressions. */ UNSET(USE_REGEXP); #endif /* Save the current search/replace strings. */ search_init_globals(); save_search = last_search; save_replace = last_replace; /* Set the search/replace strings to the misspelled word. */ last_search = mallocstrcpy(NULL, word); last_replace = mallocstrcpy(NULL, word); #ifndef NANO_SMALL if (old_mark_set) { /* If the mark is on, partition the filestruct so that it * contains only the marked text, keep track of whether the text * will have a magicline added when we're done correcting * misspelled words, and turn the mark off. */ mark_order((const filestruct **)&top, &top_x, (const filestruct **)&bot, &bot_x, &right_side_up); filepart = partition_filestruct(top, top_x, bot, bot_x); added_magicline = (filebot->data[0] != '\0'); UNSET(MARK_ISSET); } #endif /* Start from the top of the file. */ edittop = fileage; current = fileage; current_x = (size_t)-1; placewewant = 0; /* Find the first whole-word occurrence of word. */ findnextstr_wrap_reset(); while (findnextstr(TRUE, TRUE, FALSE, fileage, 0, word, NULL)) { if (is_whole_word(current_x, current->data, word)) { edit_refresh(); do_replace_highlight(TRUE, word); /* Allow all instances of the word to be corrected. */ canceled = (statusq(FALSE, spell_list, word, #ifndef NANO_SMALL NULL, #endif _("Edit a replacement")) == -1); do_replace_highlight(FALSE, word); if (!canceled && strcmp(word, answer) != 0) { current_x--; do_replace_loop(word, current, ¤t_x, TRUE, &canceled); } break; } } #ifndef NANO_SMALL if (old_mark_set) { /* If the mark was on and we added a magicline, remove it * now. */ if (added_magicline) remove_magicline(); /* Put the beginning and the end of the mark at the beginning * and the end of the spell-checked text. */ if (fileage == filebot) bot_x += top_x; if (right_side_up) { mark_beginx = top_x; current_x_save = bot_x; } else { current_x_save = top_x; mark_beginx = bot_x; } /* Unpartition the filestruct so that it contains all the text * again, and turn the mark back on. */ unpartition_filestruct(&filepart); SET(MARK_ISSET); } #endif /* Restore the search/replace strings. */ free(last_search); last_search = save_search; free(last_replace); last_replace = save_replace; /* Restore where we were. */ edittop = edittop_save; current = current_save; current_x = current_x_save; placewewant = pww_save; /* Restore case sensitivity setting. */ if (!case_sens_set) UNSET(CASE_SENSITIVE); #ifndef NANO_SMALL /* Restore search/replace direction. */ if (reverse_search_set) SET(REVERSE_SEARCH); #endif #ifdef HAVE_REGEX_H /* Restore regular expression usage setting. */ if (regexp_set) SET(USE_REGEXP); #endif return !canceled; } /* Integrated spell checking using 'spell' program. Return value: NULL * for normal termination, otherwise the error string. */ const char *do_int_speller(const char *tempfile_name) { char *read_buff, *read_buff_ptr, *read_buff_word; size_t pipe_buff_size, read_buff_size, read_buff_read, bytesread; int spell_fd[2], sort_fd[2], uniq_fd[2], tempfile_fd = -1; pid_t pid_spell, pid_sort, pid_uniq; int spell_status, sort_status, uniq_status; /* Create all three pipes up front. */ if (pipe(spell_fd) == -1 || pipe(sort_fd) == -1 || pipe(uniq_fd) == -1) return _("Could not create pipe"); statusbar(_("Creating misspelled word list, please wait...")); /* A new process to run spell in. */ if ((pid_spell = fork()) == 0) { /* Child continues (i.e, future spell process). */ close(spell_fd[0]); /* Replace the standard input with the temp file. */ if ((tempfile_fd = open(tempfile_name, O_RDONLY)) == -1) goto close_pipes_and_exit; if (dup2(tempfile_fd, STDIN_FILENO) != STDIN_FILENO) goto close_pipes_and_exit; close(tempfile_fd); /* Send spell's standard output to the pipe. */ if (dup2(spell_fd[1], STDOUT_FILENO) != STDOUT_FILENO) goto close_pipes_and_exit; close(spell_fd[1]); /* Start spell program; we are using PATH. */ execlp("spell", "spell", NULL); /* Should not be reached, if spell is found. */ exit(1); } /* Parent continues here. */ close(spell_fd[1]); /* A new process to run sort in. */ if ((pid_sort = fork()) == 0) { /* Child continues (i.e, future spell process). Replace the * standard input with the standard output of the old pipe. */ if (dup2(spell_fd[0], STDIN_FILENO) != STDIN_FILENO) goto close_pipes_and_exit; close(spell_fd[0]); /* Send sort's standard output to the new pipe. */ if (dup2(sort_fd[1], STDOUT_FILENO) != STDOUT_FILENO) goto close_pipes_and_exit; close(sort_fd[1]); /* Start sort program. Use -f to remove mixed case without * having to have ANOTHER pipe for tr. If this isn't portable, * let me know. */ execlp("sort", "sort", "-f", NULL); /* Should not be reached, if sort is found. */ exit(1); } close(spell_fd[0]); close(sort_fd[1]); /* A new process to run uniq in. */ if ((pid_uniq = fork()) == 0) { /* Child continues (i.e, future uniq process). Replace the * standard input with the standard output of the old pipe. */ if (dup2(sort_fd[0], STDIN_FILENO) != STDIN_FILENO) goto close_pipes_and_exit; close(sort_fd[0]); /* Send uniq's standard output to the new pipe. */ if (dup2(uniq_fd[1], STDOUT_FILENO) != STDOUT_FILENO) goto close_pipes_and_exit; close(uniq_fd[1]); /* Start uniq program; we are using PATH. */ execlp("uniq", "uniq", NULL); /* Should not be reached, if uniq is found. */ exit(1); } close(sort_fd[0]); close(uniq_fd[1]); /* Child process was not forked successfully. */ if (pid_spell < 0 || pid_sort < 0 || pid_uniq < 0) { close(uniq_fd[0]); return _("Could not fork"); } /* Get system pipe buffer size. */ if ((pipe_buff_size = fpathconf(uniq_fd[0], _PC_PIPE_BUF)) < 1) { close(uniq_fd[0]); return _("Could not get size of pipe buffer"); } /* Read in the returned spelling errors. */ read_buff_read = 0; read_buff_size = pipe_buff_size + 1; read_buff = read_buff_ptr = charalloc(read_buff_size); while ((bytesread = read(uniq_fd[0], read_buff_ptr, pipe_buff_size)) > 0) { read_buff_read += bytesread; read_buff_size += pipe_buff_size; read_buff = read_buff_ptr = charealloc(read_buff, read_buff_size); read_buff_ptr += read_buff_read; } *read_buff_ptr = (char)NULL; close(uniq_fd[0]); /* Process the spelling errors. */ read_buff_word = read_buff_ptr = read_buff; while (*read_buff_ptr != '\0') { if ((*read_buff_ptr == '\n') || (*read_buff_ptr == '\r')) { *read_buff_ptr = (char)NULL; if (read_buff_word != read_buff_ptr) { if (!do_int_spell_fix(read_buff_word)) { read_buff_word = read_buff_ptr; break; } } read_buff_word = read_buff_ptr + 1; } read_buff_ptr++; } /* Special case where last word doesn't end with \n or \r. */ if (read_buff_word != read_buff_ptr) do_int_spell_fix(read_buff_word); free(read_buff); replace_abort(); edit_refresh(); /* Process end of spell process. */ waitpid(pid_spell, &spell_status, 0); waitpid(pid_sort, &sort_status, 0); waitpid(pid_uniq, &uniq_status, 0); if (WIFEXITED(spell_status) == 0 || WEXITSTATUS(spell_status)) return _("Error invoking \"spell\""); if (WIFEXITED(sort_status) == 0 || WEXITSTATUS(sort_status)) return _("Error invoking \"sort -f\""); if (WIFEXITED(uniq_status) == 0 || WEXITSTATUS(uniq_status)) return _("Error invoking \"uniq\""); /* Otherwise... */ return NULL; close_pipes_and_exit: /* Don't leak any handles. */ close(tempfile_fd); close(spell_fd[0]); close(spell_fd[1]); close(sort_fd[0]); close(sort_fd[1]); close(uniq_fd[0]); close(uniq_fd[1]); exit(1); } /* External spell checking. Return value: NULL for normal termination, * otherwise the error string. */ const char *do_alt_speller(char *tempfile_name) { int alt_spell_status, lineno_save = current->lineno; size_t current_x_save = current_x, pww_save = placewewant; int current_y_save = current_y; pid_t pid_spell; char *ptr; static int arglen = 3; static char **spellargs = NULL; FILE *f; #ifndef NANO_SMALL bool old_mark_set = ISSET(MARK_ISSET); bool added_magicline = FALSE; /* Whether we added a magicline after filebot. */ bool right_side_up = FALSE; /* TRUE if (mark_beginbuf, mark_beginx) is the top of the mark, * FALSE if (current, current_x) is. */ filestruct *top, *bot; size_t top_x, bot_x; int mbb_lineno_save = 0; /* We're going to close the current file, and open the output of * the alternate spell command. The line that mark_beginbuf * points to will be freed, so we save the line number and * restore afterwards. */ size_t totsize_save = totsize; /* Our saved value of totsize, used when we spell-check a marked * selection. */ if (old_mark_set) { /* If the mark is on, save the number of the line it starts on, * and then turn the mark off. */ mbb_lineno_save = mark_beginbuf->lineno; UNSET(MARK_ISSET); } #endif endwin(); /* Set up an argument list to pass execvp(). */ if (spellargs == NULL) { spellargs = (char **)nmalloc(arglen * sizeof(char *)); spellargs[0] = strtok(alt_speller, " "); while ((ptr = strtok(NULL, " ")) != NULL) { arglen++; spellargs = (char **)nrealloc(spellargs, arglen * sizeof(char *)); spellargs[arglen - 3] = ptr; } spellargs[arglen - 1] = NULL; } spellargs[arglen - 2] = tempfile_name; /* Start a new process for the alternate speller. */ if ((pid_spell = fork()) == 0) { /* Start alternate spell program; we are using PATH. */ execvp(spellargs[0], spellargs); /* Should not be reached, if alternate speller is found!!! */ exit(1); } /* Could not fork?? */ if (pid_spell < 0) return _("Could not fork"); /* Wait for alternate speller to complete. */ wait(&alt_spell_status); if (!WIFEXITED(alt_spell_status) || WEXITSTATUS(alt_spell_status) != 0) { char *altspell_error = NULL; char *invoke_error = _("Could not invoke \"%s\""); int msglen = strlen(invoke_error) + strlen(alt_speller) + 2; altspell_error = charalloc(msglen); snprintf(altspell_error, msglen, invoke_error, alt_speller); return altspell_error; } refresh(); /* Restore the terminal to its previous state. */ terminal_init(); #ifndef NANO_SMALL if (old_mark_set) { size_t part_totsize; /* If the mark was on, partition the filestruct so that it * contains only the marked text, and keep track of whether the * temp file (which should contain the spell-checked marked * text) will have a magicline added when it's reloaded. */ mark_order((const filestruct **)&top, &top_x, (const filestruct **)&bot, &bot_x, &right_side_up); filepart = partition_filestruct(top, top_x, bot, bot_x); added_magicline = (filebot->data[0] != '\0'); /* Get the number of characters in the marked text, and subtract * it from the saved value of totsize. Note that we don't need * to save totlines. */ get_totals(top, bot, NULL, &part_totsize); totsize_save -= part_totsize; } #endif /* Reinitialize the filestruct. */ free_filestruct(fileage); global_init(TRUE); /* Reload the temp file. Do what load_buffer() would do, except for * making a new buffer for the temp file if multibuffer support is * available. */ open_file(tempfile_name, FALSE, &f); read_file(f, tempfile_name); current = fileage; #ifndef NANO_SMALL if (old_mark_set) { filestruct *top_save = fileage; /* If the mark was on and we added a magicline, remove it * now. */ if (added_magicline) remove_magicline(); /* Put the beginning and the end of the mark at the beginning * and the end of the spell-checked text. */ if (fileage == filebot) bot_x += top_x; if (right_side_up) { mark_beginx = top_x; current_x_save = bot_x; } else { current_x_save = top_x; mark_beginx = bot_x; } /* Unpartition the filestruct so that it contains all the text * again. Note that we've replaced the marked text originally * in the partition with the spell-checked marked text in the * temp file. */ unpartition_filestruct(&filepart); /* Renumber starting with the beginning line of the old * partition. Also set totlines to the new number of lines in * the file, add the number of characters in the spell-checked * marked text to the saved value of totsize, and then make that * saved value the actual value. */ renumber(top_save); totlines = filebot->lineno; totsize_save += totsize; totsize = totsize_save; /* Assign mark_beginbuf to the line where the mark began * before. */ do_gotopos(mbb_lineno_save, mark_beginx, current_y_save, 0); mark_beginbuf = current; /* Assign mark_beginx to the location in mark_beginbuf where the * mark began before, adjusted for any shortening of the * line. */ mark_beginx = current_x; /* Turn the mark back on. */ SET(MARK_ISSET); } #endif /* Go back to the old position, mark the file as modified, and make * sure that the titlebar is refreshed. */ do_gotopos(lineno_save, current_x_save, current_y_save, pww_save); set_modified(); clearok(topwin, FALSE); titlebar(NULL); return NULL; } void do_spell(void) { int i; char *temp = safe_tempnam(); const char *spell_msg; if (temp == NULL) { statusbar(_("Could not create temp file: %s"), strerror(errno)); return; } #ifndef NANO_SMALL if (ISSET(MARK_ISSET)) i = write_marked(temp, TRUE, FALSE); else #endif i = write_file(temp, TRUE, FALSE, FALSE); if (i == -1) { statusbar(_("Error writing temp file: %s"), strerror(errno)); free(temp); return; } #ifdef ENABLE_MULTIBUFFER /* Update the current open_files entry before spell-checking, in * case any problems occur. */ add_open_file(TRUE); #endif spell_msg = alt_speller != NULL ? do_alt_speller(temp) : do_int_speller(temp); unlink(temp); free(temp); if (spell_msg != NULL) statusbar(_("Spell checking failed: %s: %s"), spell_msg, strerror(errno)); else statusbar(_("Finished checking spelling")); } #endif /* !DISABLE_SPELLER */ #ifndef NANO_SMALL /* The "indentation" of a line is the whitespace between the quote part * and the non-whitespace of the line. */ size_t indent_length(const char *line) { size_t len = 0; char *blank_mb; int blank_mb_len; assert(line != NULL); blank_mb = charalloc(mb_cur_max()); while (*line != '\0') { blank_mb_len = parse_mbchar(line, blank_mb, NULL, NULL); if (!is_blank_mbchar(blank_mb)) break; line += blank_mb_len; len += blank_mb_len; } free(blank_mb); return len; } #endif /* !NANO_SMALL */ #ifndef DISABLE_JUSTIFY /* justify_format() replaces tabs with spaces and multiple spaces by 1 * (except it maintains 2 after a non-repeated character in punct * followed by a character in brackets, and removes all at the end of * the line). * * justify_format() might make line->data shorter, and change the actual * pointer with null_at(). * * justify_format() will not look at the first skip characters of line. * skip should be at most strlen(line->data). The character at * line[skip + 1] must not be whitespace. */ void justify_format(filestruct *paragraph, size_t skip) { char *end, *new_end, *new_paragraph_data; size_t shift = 0; #ifndef NANO_SMALL size_t mark_shift = 0; #endif /* These four asserts are assumptions about the input data. */ assert(paragraph != NULL); assert(paragraph->data != NULL); assert(skip < strlen(paragraph->data)); assert(!is_blank_char(paragraph->data[skip])); end = paragraph->data + skip; new_paragraph_data = charalloc(strlen(paragraph->data) + 1); charcpy(new_paragraph_data, paragraph->data, skip); new_end = new_paragraph_data + skip; while (*end != '\0') { /* If this character is blank, make sure that it's a space with * no blanks after it. */ if (is_blank_char(*end)) { *new_end = ' '; new_end++; end++; while (*end != '\0' && is_blank_char(*end)) { end++; shift++; #ifndef NANO_SMALL if (mark_beginbuf == paragraph && mark_beginx >= end - paragraph->data) mark_shift++; #endif } /* If this character is punctuation, there are two ways we can * handle it. */ } else if (strchr(punct, *end) != NULL) { *new_end = *end; new_end++; end++; /* If this character is punctuation followed by itself and * optionally followed by a bracket, make sure there is no * more than one blank after it, and make sure that the * blank is a space. */ if (*end != '\0' && *end == *(end - 1)) { *new_end = *end; new_end++; end++; if (*end != '\0' && strchr(brackets, *end) != NULL) { *new_end = *end; new_end++; end++; } if (*end != '\0' && is_blank_char(*end)) { *new_end = ' '; new_end++; end++; } while (*end != '\0' && is_blank_char(*end)) { end++; shift++; #ifndef NANO_SMALL if (mark_beginbuf == paragraph && mark_beginx >= end - paragraph->data) mark_shift++; #endif } /* If this character is punctuation optionally followed by a * bracket and then followed by spaces, make sure there are * no more than two blanks after it, and make sure that the * blanks are spaces. */ } else { if (*end != '\0' && strchr(brackets, *end) != NULL) { *new_end = *end; new_end++; end++; } if (*end != '\0' && is_blank_char(*end)) { *new_end = ' '; new_end++; end++; } if (*end != '\0' && is_blank_char(*end)) { *new_end = ' '; new_end++; end++; } while (*end != '\0' && is_blank_char(*end)) { end++; shift++; #ifndef NANO_SMALL if (mark_beginbuf == paragraph && mark_beginx >= end - paragraph->data) mark_shift++; #endif } } } else { *new_end = *end; new_end++; end++; } } assert(*end == '\0'); *new_end = *end; while (new_end > new_paragraph_data + skip && *(new_end - 1) == ' ') { new_end--; shift++; } if (shift > 0) { totsize -= shift; null_at(&new_paragraph_data, new_end - new_paragraph_data); free(paragraph->data); paragraph->data = new_paragraph_data; #ifndef NANO_SMALL /* Adjust the mark coordinates to compensate for the change in * the current line. */ if (mark_beginbuf == paragraph) { mark_beginx -= mark_shift; if (mark_beginx > new_end - new_paragraph_data) mark_beginx = new_end - new_paragraph_data; } #endif } else free(new_paragraph_data); } /* The "quote part" of a line is the largest initial substring matching * the quote string. This function returns the length of the quote part * of the given line. * * Note that if !HAVE_REGEX_H then we match concatenated copies of * quotestr. */ size_t quote_length(const char *line) { #ifdef HAVE_REGEX_H regmatch_t matches; int rc = regexec("ereg, line, 1, &matches, 0); if (rc == REG_NOMATCH || matches.rm_so == (regoff_t)-1) return 0; /* matches.rm_so should be 0, since the quote string should start * with the caret ^. */ return matches.rm_eo; #else /* !HAVE_REGEX_H */ size_t qdepth = 0; /* Compute quote depth level. */ while (strncmp(line + qdepth, quotestr, quotelen) == 0) qdepth += quotelen; return qdepth; #endif /* !HAVE_REGEX_H */ } /* a_line and b_line are lines of text. The quotation part of a_line is * the first a_quote characters. Check that the quotation part of * b_line is the same. */ bool quotes_match(const char *a_line, size_t a_quote, const char *b_line) { /* Here is the assumption about a_quote. */ assert(a_quote == quote_length(a_line)); return (a_quote == quote_length(b_line) && strncmp(a_line, b_line, a_quote) == 0); } /* We assume a_line and b_line have no quote part. Then, we return * whether b_line could follow a_line in a paragraph. */ bool indents_match(const char *a_line, size_t a_indent, const char *b_line, size_t b_indent) { assert(a_indent == indent_length(a_line)); assert(b_indent == indent_length(b_line)); return (b_indent <= a_indent && strncmp(a_line, b_line, b_indent) == 0); } /* Is foo the beginning of a paragraph? * * A line of text consists of a "quote part", followed by an * "indentation part", followed by text. The functions quote_length() * and indent_length() calculate these parts. * * A line is "part of a paragraph" if it has a part not in the quote * part or the indentation. * * A line is "the beginning of a paragraph" if it is part of a * paragraph and * 1) it is the top line of the file, or * 2) the line above it is not part of a paragraph, or * 3) the line above it does not have precisely the same quote * part, or * 4) the indentation of this line is not an initial substring of * the indentation of the previous line, or * 5) this line has no quote part and some indentation, and * AUTOINDENT is not set. * The reason for number 5) is that if AUTOINDENT is not set, then an * indented line is expected to start a paragraph, like in books. * Thus, nano can justify an indented paragraph only if AUTOINDENT is * turned on. */ bool begpar(const filestruct *const foo) { size_t quote_len; size_t indent_len; size_t temp_id_len; /* Case 1). */ if (foo->prev == NULL) return TRUE; quote_len = quote_length(foo->data); indent_len = indent_length(foo->data + quote_len); /* Not part of a paragraph. */ if (foo->data[quote_len + indent_len] == '\0') return FALSE; /* Case 3). */ if (!quotes_match(foo->data, quote_len, foo->prev->data)) return TRUE; temp_id_len = indent_length(foo->prev->data + quote_len); /* Case 2) or 5) or 4). */ if (foo->prev->data[quote_len + temp_id_len] == '\0' || (quote_len == 0 && indent_len > 0 #ifndef NANO_SMALL && !ISSET(AUTOINDENT) #endif ) || !indents_match(foo->prev->data + quote_len, temp_id_len, foo->data + quote_len, indent_len)) return TRUE; return FALSE; } /* We find the last beginning-of-paragraph line before the current * line. */ void do_para_begin(void) { const filestruct *current_save = current; const size_t pww_save = placewewant; current_x = 0; placewewant = 0; if (current->prev != NULL) { do { current = current->prev; current_y--; } while (!begpar(current)); } edit_redraw(current_save, pww_save); } bool inpar(const char *str) { size_t quote_len = quote_length(str); return str[quote_len + indent_length(str + quote_len)] != '\0'; } /* A line is the last line of a paragraph if it is in a paragraph, and * the next line isn't, or is the beginning of a paragraph. We move * down to the end of a paragraph, then one line farther. */ void do_para_end(void) { const filestruct *const current_save = current; const size_t pww_save = placewewant; current_x = 0; placewewant = 0; while (current->next != NULL && !inpar(current->data)) current = current->next; while (current->next != NULL && inpar(current->next->data) && !begpar(current->next)) { current = current->next; current_y++; } if (current->next != NULL) current = current->next; edit_redraw(current_save, pww_save); } /* Put the next par_len lines, starting with first_line, into the * justify buffer, leaving copies of those lines in place. Assume there * are enough lines after first_line. Return the new copy of * first_line. */ filestruct *backup_lines(filestruct *first_line, size_t par_len, size_t quote_len) { filestruct *top = first_line; /* The top of the paragraph we're backing up. */ filestruct *bot = first_line; /* The bottom of the paragraph we're backing up. */ size_t i; /* Generic loop variable. */ size_t current_x_save = current_x; int fl_lineno_save = first_line->lineno; int edittop_lineno_save = edittop->lineno; int current_lineno_save = current->lineno; #ifndef NANO_SMALL bool old_mark_set = ISSET(MARK_ISSET); int mbb_lineno_save = 0; size_t mark_beginx_save = 0; if (old_mark_set) { mbb_lineno_save = mark_beginbuf->lineno; mark_beginx_save = mark_beginx; } #endif /* Move bot down par_len lines to the newline after the last line of * the paragraph. */ for (i = par_len; i > 0; i--) bot = bot->next; /* Move the paragraph from the main filestruct to the justify * buffer. */ move_to_filestruct(&jusbuffer, &jusbottom, top, 0, bot, 0); /* Copy the paragraph from the justify buffer to the main * filestruct. */ copy_from_filestruct(jusbuffer, jusbottom); /* Move upward from the last line of the paragraph to the first * line, putting first_line, edittop, current, and mark_beginbuf at * the same lines in the copied paragraph that they had in the * original paragraph. */ top = current->prev; for (i = par_len; i > 0; i--) { if (top->lineno == fl_lineno_save) first_line = top; if (top->lineno == edittop_lineno_save) edittop = top; if (top->lineno == current_lineno_save) current = top; #ifndef NANO_SMALL if (old_mark_set && top->lineno == mbb_lineno_save) { mark_beginbuf = top; mark_beginx = mark_beginx_save; } #endif top = top->prev; } /* Put current_x at the same place in the copied paragraph that it * had in the original paragraph. */ current_x = current_x_save; set_modified(); return first_line; } /* We are trying to break a chunk off line. We find the last space such * that the display length to there is at most goal + 1. If there is no * such space, and force is TRUE, then we find the first space. Anyway, * we then take the last space in that group of spaces. The terminating * '\0' counts as a space. */ ssize_t break_line(const char *line, ssize_t goal, bool force) { ssize_t space_loc = -1; /* Current tentative return value. Index of the last space we * found with short enough display width. */ ssize_t cur_loc = 0; /* Current index in line. */ assert(line != NULL); while (*line != '\0' && goal >= 0) { size_t pos = 0; int line_len; if (*line == ' ') space_loc = cur_loc; assert(*line != '\t'); line_len = parse_mbchar(line, NULL, NULL, &pos); goal -= pos; line += line_len; cur_loc += line_len; } if (goal >= 0) /* In fact, the whole line displays shorter than goal. */ return cur_loc; if (space_loc == -1) { /* No space found short enough. */ if (force) { for (; *line != '\0'; line++, cur_loc++) { if (*line == ' ' && *(line + 1) != ' ' && *(line + 1) != '\0') return cur_loc; } return -1; } } /* Perhaps the character after space_loc is a space. But because * of justify_format(), there can be only two adjacent. */ if (*(line - cur_loc + space_loc + 1) == ' ' || *(line - cur_loc + space_loc + 1) == '\0') space_loc++; return space_loc; } /* Find the beginning of the current paragraph if we're in one, or the * beginning of the next paragraph if we're not. Afterwards, save the * quote length and paragraph length in *quote and *par. Return FALSE * if we found a paragraph, or TRUE if there was an error or we didn't * find a paragraph. * * See the comment at begpar() for more about when a line is the * beginning of a paragraph. */ bool do_para_search(size_t *const quote, size_t *const par) { size_t quote_len; /* Length of the initial quotation of the paragraph we * search. */ size_t par_len; /* Number of lines in that paragraph. */ size_t indent_len; /* Generic indentation length. */ filestruct *line; /* Generic line of text. */ #ifdef HAVE_REGEX_H if (quoterc != 0) { statusbar(_("Bad quote string %s: %s"), quotestr, quoteerr); return TRUE; } #endif /* Here is an assumption that is always true anyway. */ assert(current != NULL); current_x = 0; quote_len = quote_length(current->data); indent_len = indent_length(current->data + quote_len); /* Here we find the first line of the paragraph to search. If the * current line is in a paragraph, then we move back to the first * line of the paragraph. Otherwise, we move to the first line that * is in a paragraph. */ if (current->data[quote_len + indent_len] != '\0') { /* This line is part of a paragraph. So we must search back to * the first line of this paragraph. First we check items 1) * and 3) above. */ while (current->prev != NULL && quotes_match(current->data, quote_len, current->prev->data)) { size_t temp_id_len = indent_length(current->prev->data + quote_len); /* The indentation length of the previous line. */ /* Is this line the beginning of a paragraph, according to * items 2), 5), or 4) above? If so, stop. */ if (current->prev->data[quote_len + temp_id_len] == '\0' || (quote_len == 0 && indent_len > 0 #ifndef NANO_SMALL && !ISSET(AUTOINDENT) #endif ) || !indents_match(current->prev->data + quote_len, temp_id_len, current->data + quote_len, indent_len)) break; indent_len = temp_id_len; current = current->prev; current_y--; } } else { /* This line is not part of a paragraph. Move down until we get * to a non "blank" line. */ do { /* There is no next paragraph, so nothing to move to. */ if (current->next == NULL) { placewewant = 0; return TRUE; } current = current->next; current_y++; quote_len = quote_length(current->data); indent_len = indent_length(current->data + quote_len); } while (current->data[quote_len + indent_len] == '\0'); } /* Now current is the first line of the paragraph, and quote_len is * the quotation length of that line. */ /* Next step, compute par_len, the number of lines in this * paragraph. */ line = current; par_len = 1; indent_len = indent_length(line->data + quote_len); while (line->next != NULL && quotes_match(current->data, quote_len, line->next->data)) { size_t temp_id_len = indent_length(line->next->data + quote_len); if (!indents_match(line->data + quote_len, indent_len, line->next->data + quote_len, temp_id_len) || line->next->data[quote_len + temp_id_len] == '\0' || (quote_len == 0 && temp_id_len > 0 #ifndef NANO_SMALL && !ISSET(AUTOINDENT) #endif )) break; indent_len = temp_id_len; line = line->next; par_len++; } /* Now par_len is the number of lines in this paragraph. We should * never call quotes_match() or quote_length() again. */ /* Save the values of quote_len and par_len. */ assert(quote != NULL && par != NULL); *quote = quote_len; *par = par_len; return FALSE; } /* If full_justify is TRUE, justify the entire file. Otherwise, justify * the current paragraph. */ void do_justify(bool full_justify) { filestruct *first_par_line = NULL; /* Will be the first line of the resulting justified paragraph. * For restoring after unjustify. */ filestruct *last_par_line; /* Will be the line containing the newline after the last line * of the result. Also for restoring after unjustify. */ /* We save these global variables to be restored if the user * unjustifies. Note that we don't need to save totlines. */ size_t current_x_save = current_x; int current_y_save = current_y; unsigned long flags_save = flags; size_t totsize_save = totsize; filestruct *edittop_save = edittop, *current_save = current; #ifndef NANO_SMALL filestruct *mark_beginbuf_save = mark_beginbuf; size_t mark_beginx_save = mark_beginx; #endif int kbinput; bool meta_key, func_key, s_or_t, ran_func, finished; /* If we're justifying the entire file, start at the beginning. */ if (full_justify) current = fileage; last_par_line = current; while (TRUE) { size_t i; /* Generic loop variable. */ size_t quote_len; /* Length of the initial quotation of the paragraph we * justify. */ size_t indent_len; /* Length of the initial indentation of the paragraph we * justify. */ size_t par_len; /* Number of lines in the paragraph we justify. */ ssize_t break_pos; /* Where we will break lines. */ char *indent_string = mallocstrcpy(NULL, ""); /* The first indentation that doesn't match the initial * indentation of the paragraph we justify. This is put at * the beginning of every line broken off the first * justified line of the paragraph. */ /* Find the first line of the paragraph to be justified. That * is the start of this paragraph if we're in one, or the start * of the next otherwise. Save the quote length and paragraph * length (number of lines). Don't refresh the screen yet, * since we'll do that after we justify. If the search * failed, we're justifying the whole file, and the search * didn't leave us on the last line of the file, set the last * line of the text to be justified to the last line of the file * and break out of the loop. Otherwise, refresh the screen and * get out. */ if (do_para_search("e_len, &par_len)) { if (full_justify && first_par_line != filebot) { last_par_line = filebot; break; } else { edit_refresh(); return; } } /* If we haven't already done it, copy the original paragraph(s) * to the justify buffer. */ if (first_par_line == NULL) first_par_line = backup_lines(current, full_justify ? filebot->lineno - current->lineno : par_len, quote_len); /* Find the first indentation in the paragraph that doesn't * match the indentation of the first line, and save it in * indent_string. If all the indentations are the same, save * the indentation of the first line in indent_string. */ { const filestruct *indent_line = current; bool past_first_line = FALSE; for (i = 0; i < par_len; i++) { indent_len = quote_len + indent_length(indent_line->data + quote_len); if (indent_len != strlen(indent_string)) { indent_string = mallocstrncpy(indent_string, indent_line->data, indent_len + 1); indent_string[indent_len] = '\0'; if (past_first_line) break; } if (indent_line == current) past_first_line = TRUE; indent_line = indent_line->next; } } /* Now tack all the lines of the paragraph together, skipping * the quoting and indentation on all lines after the first. */ for (i = 0; i < par_len - 1; i++) { filestruct *next_line = current->next; size_t line_len = strlen(current->data); size_t next_line_len = strlen(current->next->data); indent_len = quote_len + indent_length(current->next->data + quote_len); next_line_len -= indent_len; totsize -= indent_len; /* We're just about to tack the next line onto this one. If * this line isn't blank, make sure it ends in a space. */ if (line_len > 0 && current->data[line_len - 1] != ' ') { line_len++; current->data = charealloc(current->data, line_len + 1); current->data[line_len - 1] = ' '; current->data[line_len] = '\0'; totsize++; } current->data = charealloc(current->data, line_len + next_line_len + 1); strcat(current->data, next_line->data + indent_len); /* Don't destroy edittop! */ if (edittop == next_line) edittop = current; #ifndef NANO_SMALL /* Adjust the mark coordinates to compensate for the change * in the next line. */ if (mark_beginbuf == next_line) { mark_beginbuf = current; mark_beginx += line_len - indent_len; } #endif unlink_node(next_line); delete_node(next_line); /* If we've removed the next line, we need to go through * this line again. */ i--; par_len--; totlines--; totsize--; } /* Call justify_format() on the paragraph, which will remove * excess spaces from it and change all blank characters to * spaces. */ justify_format(current, quote_len + indent_length(current->data + quote_len)); while (par_len > 0 && strlenpt(current->data) > fill) { size_t line_len = strlen(current->data); indent_len = strlen(indent_string); /* If this line is too long, try to wrap it to the next line * to make it short enough. */ break_pos = break_line(current->data + indent_len, fill - strnlenpt(current->data, indent_len), TRUE); /* We can't break the line, or don't need to, so get out. */ if (break_pos == -1 || break_pos + indent_len == line_len) break; break_pos += indent_len; assert(break_pos < line_len); /* Make a new line and copy the text after where we broke * this line to the beginning of the new line. */ splice_node(current, make_new_node(current), current->next); /* If this paragraph is non-quoted, and autoindent isn't * turned on, set the indentation length to zero so that the * indentation is treated as part of the line. */ if (quote_len == 0 #ifndef NANO_SMALL && !ISSET(AUTOINDENT) #endif ) indent_len = 0; current->next->data = charalloc(indent_len + line_len - break_pos); charcpy(current->next->data, indent_string, indent_len); strcpy(current->next->data + indent_len, current->data + break_pos + 1); par_len++; totlines++; totsize += indent_len; #ifndef NANO_SMALL /* Adjust the mark coordinates to compensate for the change * in the current line. */ if (mark_beginbuf == current && mark_beginx > break_pos) { mark_beginbuf = current->next; mark_beginx -= break_pos + 1 - indent_len; } #endif /* Break the line. If this isn't the last line of the * paragraph, add a space after where we break it. */ null_at(¤t->data, break_pos); if (par_len > 1) { current->data = charealloc(current->data, break_pos + 2); current->data[break_pos] = ' '; current->data[break_pos + 1] = '\0'; totsize++; } /* Go to the next line. */ par_len--; current_y++; current = current->next; } /* We're done breaking lines, so we don't need indent_string * anymore. */ free(indent_string); indent_string = NULL; /* Go to the next line, the line after the last line of the * paragraph. */ current_y++; current = current->next; /* We've just justified a paragraph. If we're not justifying the * entire file, break out of the loop. Otherwise, continue the * loop so that we justify all the paragraphs in the file. */ if (!full_justify) break; } /* We are now done justifying the paragraph or the file, so clean * up. totlines, totsize, and current_y have been maintained above. * Set last_par_line to the new end of the paragraph, update * fileage, and renumber() since edit_refresh() needs the line * numbers to be right (but only do the last two if we actually * justified something). */ last_par_line = current; if (first_par_line != NULL) { if (first_par_line->prev == NULL) fileage = first_par_line; renumber(first_par_line); } edit_refresh(); statusbar(_("Can now UnJustify!")); /* Display the shortcut list with UnJustify. */ shortcut_init(TRUE); display_main_list(); /* Now get a keystroke and see if it's unjustify. If not, put back * the keystroke and return. */ kbinput = do_input(&meta_key, &func_key, &s_or_t, &ran_func, &finished, FALSE); if (!meta_key && !func_key && s_or_t && kbinput == NANO_UNJUSTIFY_KEY) { /* Restore the justify we just did (ungrateful user!). */ current = current_save; current_x = current_x_save; current_y = current_y_save; edittop = edittop_save; /* Splice the justify buffer back into the file, but only if we * actually justified something. */ if (first_par_line != NULL) { filestruct *bot_save; /* Partition the filestruct so that it contains only the * text of the justified paragraph. */ filepart = partition_filestruct(first_par_line, 0, last_par_line, 0); /* Remove the text of the justified paragraph, and * put the text in the justify buffer in its place. */ free_filestruct(fileage); fileage = jusbuffer; filebot = jusbottom; bot_save = filebot; /* Unpartition the filestruct so that it contains all the * text again. Note that the justified paragraph has been * replaced with the unjustified paragraph. */ unpartition_filestruct(&filepart); /* Renumber starting with the ending line of the old * partition. */ if (bot_save->next != NULL) renumber(bot_save->next); /* Restore global variables from before the justify. */ totsize = totsize_save; totlines = filebot->lineno; #ifndef NANO_SMALL mark_beginbuf = mark_beginbuf_save; mark_beginx = mark_beginx_save; #endif flags = flags_save; /* Clear the justify buffer. */ jusbuffer = NULL; if (!ISSET(MODIFIED)) titlebar(NULL); edit_refresh(); } } else { unget_kbinput(kbinput, meta_key, func_key); /* Blow away the text in the justify buffer. */ free_filestruct(jusbuffer); jusbuffer = NULL; } blank_statusbar(); /* Display the shortcut list with UnCut. */ shortcut_init(FALSE); display_main_list(); } void do_justify_void(void) { do_justify(FALSE); } void do_full_justify(void) { do_justify(TRUE); } #endif /* !DISABLE_JUSTIFY */ void do_exit(void) { int i; if (!ISSET(MODIFIED)) i = 0; /* Pretend the user chose not to save. */ else if (ISSET(TEMP_FILE)) i = 1; else i = do_yesno(FALSE, _("Save modified buffer (ANSWERING \"No\" WILL DESTROY CHANGES) ? ")); #ifdef DEBUG dump_buffer(fileage); #endif if (i == 0 || (i == 1 && do_writeout(TRUE) > 0)) { #ifdef ENABLE_MULTIBUFFER /* Exit only if there are no more open file buffers. */ if (!close_open_file()) #endif finish(); } else if (i != 1) statusbar(_("Cancelled")); display_main_list(); } void signal_init(void) { /* Trap SIGINT and SIGQUIT because we want them to do useful * things. */ memset(&act, 0, sizeof(struct sigaction)); act.sa_handler = SIG_IGN; sigaction(SIGINT, &act, NULL); sigaction(SIGQUIT, &act, NULL); /* Trap SIGHUP and SIGTERM because we want to write the file out. */ act.sa_handler = handle_hupterm; sigaction(SIGHUP, &act, NULL); sigaction(SIGTERM, &act, NULL); #ifndef NANO_SMALL /* Trap SIGWINCH because we want to handle window resizes. */ act.sa_handler = handle_sigwinch; sigaction(SIGWINCH, &act, NULL); allow_pending_sigwinch(FALSE); #endif /* Trap normal suspend (^Z) so we can handle it ourselves. */ if (!ISSET(SUSPEND)) { act.sa_handler = SIG_IGN; sigaction(SIGTSTP, &act, NULL); } else { /* Block all other signals in the suspend and continue handlers. * If we don't do this, other stuff interrupts them! */ sigfillset(&act.sa_mask); act.sa_handler = do_suspend; sigaction(SIGTSTP, &act, NULL); act.sa_handler = do_cont; sigaction(SIGCONT, &act, NULL); } } /* Handler for SIGHUP (hangup) and SIGTERM (terminate). */ void handle_hupterm(int signal) { die(_("Received SIGHUP or SIGTERM\n")); } /* Handler for SIGTSTP (suspend). */ void do_suspend(int signal) { endwin(); printf("\n\n\n\n\n%s\n", _("Use \"fg\" to return to nano")); fflush(stdout); /* Restore the old terminal settings. */ tcsetattr(0, TCSANOW, &oldterm); /* Trap SIGHUP and SIGTERM so we can properly deal with them while * suspended. */ act.sa_handler = handle_hupterm; sigaction(SIGHUP, &act, NULL); sigaction(SIGTERM, &act, NULL); /* Do what mutt does: send ourselves a SIGSTOP. */ kill(0, SIGSTOP); } /* Handler for SIGCONT (continue after suspend). */ void do_cont(int signal) { #ifndef NANO_SMALL /* Perhaps the user resized the window while we slept. Handle it * and update the screen in the process. */ handle_sigwinch(0); #else /* Just update the screen. */ doupdate(); #endif } #ifndef NANO_SMALL void handle_sigwinch(int s) { const char *tty = ttyname(0); int fd, result = 0; struct winsize win; if (tty == NULL) return; fd = open(tty, O_RDWR); if (fd == -1) return; result = ioctl(fd, TIOCGWINSZ, &win); close(fd); if (result == -1) return; /* Could check whether the COLS or LINES changed, and return * otherwise. EXCEPT, that COLS and LINES are ncurses global * variables, and in some cases ncurses has already updated them. * But not in all cases, argh. */ COLS = win.ws_col; LINES = win.ws_row; check_die_too_small(); resize_variables(); /* If we've partitioned the filestruct, unpartition it now. */ if (filepart != NULL) unpartition_filestruct(&filepart); #ifndef DISABLE_JUSTIFY /* If the justify buffer isn't empty, blow away the text in it and * display the shortcut list with UnCut. */ if (jusbuffer != NULL) { free_filestruct(jusbuffer); jusbuffer = NULL; shortcut_init(FALSE); } #endif #ifdef USE_SLANG /* Slang curses emulation brain damage, part 1: If we just do what * curses does here, it'll only work properly if the resize made the * window smaller. Do what mutt does: Leave and immediately reenter * Slang screen management mode. */ SLsmg_reset_smg(); SLsmg_init_smg(); #else /* Do the equivalent of what Minimum Profit does: Leave and * immediately reenter curses mode. */ endwin(); refresh(); #endif /* Restore the terminal to its previous state. */ terminal_init(); /* Do the equivalent of what both mutt and Minimum Profit do: * Reinitialize all the windows based on the new screen * dimensions. */ window_init(); /* Redraw the contents of the windows that need it. */ blank_statusbar(); currshortcut = main_list; total_refresh(); /* Turn the cursor back on for sure. */ curs_set(1); /* Reset all the input routines that rely on character sequences. */ reset_kbinput(); /* Jump back to the main loop. */ siglongjmp(jmpbuf, 1); } void allow_pending_sigwinch(bool allow) { sigset_t winch; sigemptyset(&winch); sigaddset(&winch, SIGWINCH); if (allow) sigprocmask(SIG_UNBLOCK, &winch, NULL); else sigprocmask(SIG_BLOCK, &winch, NULL); } #endif /* !NANO_SMALL */ #ifndef NANO_SMALL void do_toggle(const toggle *which) { bool enabled; TOGGLE(which->flag); switch (which->val) { #ifndef DISABLE_MOUSE case TOGGLE_MOUSE_KEY: mouse_init(); break; #endif case TOGGLE_MORESPACE_KEY: case TOGGLE_NOHELP_KEY: window_init(); total_refresh(); break; case TOGGLE_SUSPEND_KEY: signal_init(); break; #ifdef ENABLE_NANORC case TOGGLE_WHITESPACE_KEY: titlebar(NULL); edit_refresh(); break; #endif #ifdef ENABLE_COLOR case TOGGLE_SYNTAX_KEY: edit_refresh(); break; #endif } /* We are assuming here that shortcut_init() above didn't free and * reallocate the toggles. */ enabled = ISSET(which->flag); if (which->val == TOGGLE_NOHELP_KEY || which->val == TOGGLE_WRAP_KEY) enabled = !enabled; statusbar("%s %s", which->desc, enabled ? _("enabled") : _("disabled")); } #endif /* !NANO_SMALL */ void disable_extended_input(void) { struct termios term; tcgetattr(0, &term); term.c_lflag &= ~IEXTEN; tcsetattr(0, TCSANOW, &term); } void disable_signals(void) { struct termios term; tcgetattr(0, &term); term.c_lflag &= ~ISIG; tcsetattr(0, TCSANOW, &term); } #ifndef NANO_SMALL void enable_signals(void) { struct termios term; tcgetattr(0, &term); term.c_lflag |= ISIG; tcsetattr(0, TCSANOW, &term); } #endif void disable_flow_control(void) { struct termios term; tcgetattr(0, &term); term.c_iflag &= ~(IXON|IXOFF); tcsetattr(0, TCSANOW, &term); } void enable_flow_control(void) { struct termios term; tcgetattr(0, &term); term.c_iflag |= (IXON|IXOFF); tcsetattr(0, TCSANOW, &term); } /* Set up the terminal state. Put the terminal in cbreak mode (read one * character at a time and interpret the special control keys), disable * translation of carriage return (^M) into newline (^J) so that we can * tell the difference between the Enter key and Ctrl-J, and disable * echoing of characters as they're typed. Finally, disable extended * input processing, disable interpretation of the special control keys, * and if we're not in preserve mode, disable interpretation of the flow * control characters too. */ void terminal_init(void) { cbreak(); nonl(); noecho(); disable_extended_input(); disable_signals(); if (!ISSET(PRESERVE)) disable_flow_control(); } int do_input(bool *meta_key, bool *func_key, bool *s_or_t, bool *ran_func, bool *finished, bool allow_funcs) { int input; /* The character we read in. */ static int *kbinput = NULL; /* The input buffer. */ static size_t kbinput_len = 0; /* The length of the input buffer. */ const shortcut *s; bool have_shortcut; #ifndef NANO_SMALL const toggle *t; bool have_toggle; #endif *s_or_t = FALSE; *ran_func = FALSE; *finished = FALSE; /* Read in a character. */ input = get_kbinput(edit, meta_key, func_key); #ifndef DISABLE_MOUSE /* If we got a mouse click and it was on a shortcut, read in the * shortcut character. */ if (allow_funcs && *func_key == TRUE && input == KEY_MOUSE) { if (do_mouse()) input = get_kbinput(edit, meta_key, func_key); else input = ERR; } #endif /* Check for a shortcut in the main list. */ s = get_shortcut(main_list, &input, meta_key, func_key); /* If we got a shortcut from the main list, or a "universal" * edit window shortcut, set have_shortcut to TRUE. */ have_shortcut = (s != NULL || input == NANO_XON_KEY || input == NANO_XOFF_KEY || input == NANO_SUSPEND_KEY); #ifndef NANO_SMALL /* Check for a toggle in the main list. */ t = get_toggle(input, *meta_key); /* If we got a toggle from the main list, set have_toggle to * TRUE. */ have_toggle = (t != NULL); #endif /* Set s_or_t to TRUE if we got a shortcut or toggle. */ *s_or_t = (have_shortcut #ifndef NANO_SMALL || have_toggle #endif ); if (allow_funcs) { /* If we got a character, and it isn't a shortcut, toggle, or * control character, it's a normal text character. Display the * warning if we're in view mode, or add the character to the * input buffer if we're not. */ if (input != ERR && *s_or_t == FALSE && !is_cntrl_char(input)) { if (ISSET(VIEW_MODE)) print_view_warning(); else { kbinput_len++; kbinput = (int *)nrealloc(kbinput, kbinput_len * sizeof(int)); kbinput[kbinput_len - 1] = input; } } /* If we got a shortcut or toggle, or if there aren't any other * characters waiting after the one we read in, we need to * display all the characters in the input buffer if it isn't * empty. Note that it should be empty if we're in view * mode. */ if (*s_or_t == TRUE || get_buffer_len() == 0) { if (kbinput != NULL) { /* Display all the characters in the input buffer at * once. */ char *output = charalloc(kbinput_len + 1); size_t i; for (i = 0; i < kbinput_len; i++) output[i] = (char)kbinput[i]; output[i] = '\0'; do_output(output, kbinput_len); free(output); /* Empty the input buffer. */ kbinput_len = 0; free(kbinput); kbinput = NULL; } } if (have_shortcut) { switch (input) { /* Handle the "universal" statusbar prompt shortcuts. */ case NANO_XON_KEY: statusbar(_("XON ignored, mumble mumble.")); break; case NANO_XOFF_KEY: statusbar(_("XOFF ignored, mumble mumble.")); break; #ifndef NANO_SMALL case NANO_SUSPEND_KEY: if (ISSET(SUSPEND)) do_suspend(0); break; #endif /* Handle the normal edit window shortcuts, setting * ran_func to TRUE if we try to run their associated * functions and setting finished to TRUE to indicate * that we're done after trying to run their associated * functions. */ default: /* Blow away the text in the cutbuffer if we aren't * cutting text. */ if (s->func != do_cut_text) cutbuffer_reset(); if (s->func != NULL) { *ran_func = TRUE; if (ISSET(VIEW_MODE) && !s->viewok) print_view_warning(); else s->func(); } *finished = TRUE; break; } } #ifndef NANO_SMALL else if (have_toggle) { /* Blow away the text in the cutbuffer, since we aren't * cutting text. */ cutbuffer_reset(); /* Toggle the flag associated with this shortcut. */ if (allow_funcs) do_toggle(t); } #endif else /* Blow away the text in the cutbuffer, since we aren't * cutting text. */ cutbuffer_reset(); } return input; } #ifndef DISABLE_MOUSE bool do_mouse(void) { int mouse_x, mouse_y; bool retval; retval = get_mouseinput(&mouse_x, &mouse_y, TRUE); if (!retval) { /* We can click in the edit window to move the cursor. */ if (wenclose(edit, mouse_y, mouse_x)) { bool sameline; /* Did they click on the line with the cursor? If they * clicked on the cursor, we set the mark. */ size_t xcur; /* The character they clicked on. */ /* Subtract out the size of topwin. Perhaps we need a * constant somewhere? */ mouse_y -= (2 - no_more_space()); sameline = (mouse_y == current_y); /* Move to where the click occurred. */ for (; current_y < mouse_y && current->next != NULL; current_y++) current = current->next; for (; current_y > mouse_y && current->prev != NULL; current_y--) current = current->prev; xcur = actual_x(current->data, get_page_start(xplustabs()) + mouse_x); #ifndef NANO_SMALL /* Clicking where the cursor is toggles the mark, as does * clicking beyond the line length with the cursor at the * end of the line. */ if (sameline && xcur == current_x) { if (ISSET(VIEW_MODE)) { print_view_warning(); return retval; } do_mark(); } #endif current_x = xcur; placewewant = xplustabs(); edit_refresh(); } } return retval; } #endif /* !DISABLE_MOUSE */ /* The user typed kbinput_len multibyte characters. Add them to the * edit buffer. */ void do_output(char *output, size_t output_len) { size_t current_len = strlen(current->data), i = 0; bool old_constupdate = ISSET(CONSTUPDATE); bool do_refresh = FALSE; /* Do we have to call edit_refresh(), or can we get away with * update_line()? */ char *char_buf = charalloc(mb_cur_max()); int char_buf_len; assert(current != NULL && current->data != NULL); /* Turn off constant cursor position display. */ UNSET(CONSTUPDATE); while (i < output_len) { /* Null to newline, if needed. */ if (output[i] == '\0') output[i] = '\n'; /* Newline to Enter, if needed. */ else if (output[i] == '\n') { do_enter(); i++; continue; } /* Interpret the next multibyte character. If it's an invalid * multibyte character, interpret it as though it's a byte * character. */ char_buf_len = parse_mbchar(output + i, char_buf, NULL, NULL); i += char_buf_len; /* When a character is inserted on the current magicline, it * means we need a new one! */ if (filebot == current) new_magicline(); /* More dangerousness fun =) */ current->data = charealloc(current->data, current_len + (char_buf_len * 2)); assert(current_x <= current_len); charmove(¤t->data[current_x + char_buf_len], ¤t->data[current_x], current_len - current_x + char_buf_len); charcpy(¤t->data[current_x], char_buf, char_buf_len); current_len += char_buf_len; totsize++; set_modified(); #ifndef NANO_SMALL /* Note that current_x has not yet been incremented. */ if (current == mark_beginbuf && current_x < mark_beginx) mark_beginx += char_buf_len; #endif do_right(FALSE); #ifndef DISABLE_WRAPPING /* If we're wrapping text, we need to call edit_refresh(). */ if (!ISSET(NO_WRAP) && output[i] != '\t') { bool do_refresh_save = do_refresh; do_refresh = do_wrap(current); /* If we needed to call edit_refresh() before this, we'll * still need to after this. */ if (do_refresh_save) do_refresh = TRUE; } #endif #ifdef ENABLE_COLOR /* If color syntaxes are turned on, we need to call * edit_refresh(). */ if (!ISSET(NO_COLOR_SYNTAX)) do_refresh = TRUE; #endif } /* Turn constant cursor position display back on if it was on * before. */ if (old_constupdate) SET(CONSTUPDATE); free(char_buf); if (do_refresh) edit_refresh(); else update_line(current, current_x); } int main(int argc, char **argv) { int optchr; int startline = 0; /* Line to try and start at. */ #ifndef DISABLE_WRAPJUSTIFY bool fill_flag_used = FALSE; /* Was the fill option used? */ #endif #ifdef ENABLE_MULTIBUFFER bool old_multibuffer; /* The old value of the multibuffer option, restored after we * load all files on the command line. */ #endif #ifdef HAVE_GETOPT_LONG const struct option long_options[] = { {"help", 0, 0, 'h'}, #ifdef ENABLE_MULTIBUFFER {"multibuffer", 0, 0, 'F'}, #endif #ifdef ENABLE_NANORC #ifndef NANO_SMALL {"historylog", 0, 0, 'H'}, #endif {"ignorercfiles", 0, 0, 'I'}, #endif {"morespace", 0, 0, 'O'}, #ifndef DISABLE_JUSTIFY {"quotestr", 1, 0, 'Q'}, #endif #ifdef HAVE_REGEX_H {"regexp", 0, 0, 'R'}, #endif {"tabsize", 1, 0, 'T'}, {"version", 0, 0, 'V'}, #ifdef ENABLE_COLOR {"syntax", 1, 0, 'Y'}, #endif {"const", 0, 0, 'c'}, {"rebinddelete", 0, 0, 'd'}, {"nofollow", 0, 0, 'l'}, #ifndef DISABLE_MOUSE {"mouse", 0, 0, 'm'}, #endif #ifndef DISABLE_OPERATINGDIR {"operatingdir", 1, 0, 'o'}, #endif {"preserve", 0, 0, 'p'}, #ifndef DISABLE_WRAPJUSTIFY {"fill", 1, 0, 'r'}, #endif #ifndef DISABLE_SPELLER {"speller", 1, 0, 's'}, #endif {"tempfile", 0, 0, 't'}, {"view", 0, 0, 'v'}, #ifndef DISABLE_WRAPPING {"nowrap", 0, 0, 'w'}, #endif {"nohelp", 0, 0, 'x'}, {"suspend", 0, 0, 'z'}, #ifndef NANO_SMALL {"smarthome", 0, 0, 'A'}, {"backup", 0, 0, 'B'}, {"backupdir", 1, 0, 'E'}, {"noconvert", 0, 0, 'N'}, {"smooth", 0, 0, 'S'}, {"restricted", 0, 0, 'Z'}, {"autoindent", 0, 0, 'i'}, {"cut", 0, 0, 'k'}, #endif {0, 0, 0, 0} }; #endif #ifdef NANO_WIDE { /* If the locale set doesn't exist, or it exists but doesn't * include the string "UTF-8", we shouldn't use UTF-8 * support. */ char *locale = setlocale(LC_ALL, ""); if (locale == NULL || (locale != NULL && strstr(locale, "UTF-8") == NULL)) SET(NO_UTF8); #ifdef USE_SLANG if (!ISSET(NO_UTF8)) SLutf8_enable(TRUE); #endif } #else setlocale(LC_ALL, ""); #endif #ifdef ENABLE_NLS bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); #endif #if !defined(ENABLE_NANORC) && defined(DISABLE_ROOTWRAP) && !defined(DISABLE_WRAPPING) /* If we don't have rcfile support, we're root, and * --disable-wrapping-as-root is used, turn wrapping off. */ if (geteuid() == NANO_ROOT_UID) SET(NO_WRAP); #endif while ((optchr = #ifdef HAVE_GETOPT_LONG getopt_long(argc, argv, "h?ABE:FHINOQ:RST:VY:Zabcdefgijklmo:pr:s:tvwxz", long_options, NULL) #else getopt(argc, argv, "h?ABE:FHINOQ:RST:VY:Zabcdefgijklmo:pr:s:tvwxz") #endif ) != -1) { switch (optchr) { case 'a': case 'b': case 'e': case 'f': case 'g': case 'j': /* Pico compatibility flags. */ break; #ifndef NANO_SMALL case 'A': SET(SMART_HOME); break; case 'B': SET(BACKUP_FILE); break; case 'E': backup_dir = mallocstrcpy(backup_dir, optarg); break; #endif #ifdef ENABLE_MULTIBUFFER case 'F': SET(MULTIBUFFER); break; #endif #ifdef ENABLE_NANORC #ifndef NANO_SMALL case 'H': SET(HISTORYLOG); break; #endif case 'I': SET(NO_RCFILE); break; #endif #ifndef NANO_SMALL case 'N': SET(NO_CONVERT); break; #endif case 'O': SET(MORE_SPACE); break; #ifndef DISABLE_JUSTIFY case 'Q': quotestr = mallocstrcpy(quotestr, optarg); break; #endif #ifdef HAVE_REGEX_H case 'R': SET(USE_REGEXP); break; #endif #ifndef NANO_SMALL case 'S': SET(SMOOTHSCROLL); break; #endif case 'T': if (!parse_num(optarg, &tabsize) || tabsize <= 0) { fprintf(stderr, _("Requested tab size %s invalid"), optarg); fprintf(stderr, "\n"); exit(1); } break; case 'V': version(); exit(0); #ifdef ENABLE_COLOR case 'Y': syntaxstr = mallocstrcpy(syntaxstr, optarg); break; #endif case 'Z': SET(RESTRICTED); break; case 'c': SET(CONSTUPDATE); break; case 'd': SET(REBIND_DELETE); break; #ifndef NANO_SMALL case 'i': SET(AUTOINDENT); break; case 'k': SET(CUT_TO_END); break; #endif case 'l': SET(NOFOLLOW_SYMLINKS); break; #ifndef DISABLE_MOUSE case 'm': SET(USE_MOUSE); break; #endif #ifndef DISABLE_OPERATINGDIR case 'o': operating_dir = mallocstrcpy(operating_dir, optarg); break; #endif case 'p': SET(PRESERVE); break; #ifndef DISABLE_WRAPJUSTIFY case 'r': if (!parse_num(optarg, &wrap_at)) { fprintf(stderr, _("Requested fill size %s invalid"), optarg); fprintf(stderr, "\n"); exit(1); } fill_flag_used = TRUE; break; #endif #ifndef DISABLE_SPELLER case 's': alt_speller = mallocstrcpy(alt_speller, optarg); break; #endif case 't': SET(TEMP_FILE); break; case 'v': SET(VIEW_MODE); break; #ifndef DISABLE_WRAPPING case 'w': SET(NO_WRAP); break; #endif case 'x': SET(NO_HELP); break; case 'z': SET(SUSPEND); break; default: usage(); } } /* If the executable filename starts with 'r', we use restricted * mode. */ if (*(tail(argv[0])) == 'r') SET(RESTRICTED); /* If we're using restricted mode, disable suspending, backups, and * reading rcfiles, since they all would allow reading from or * writing to files not specified on the command line. */ if (ISSET(RESTRICTED)) { UNSET(SUSPEND); UNSET(BACKUP_FILE); SET(NO_RCFILE); } /* We've read through the command line options. Now back up the flags * and values that are set, and read the rcfile(s). If the values * haven't changed afterward, restore the backed-up values. */ #ifdef ENABLE_NANORC if (!ISSET(NO_RCFILE)) { #ifndef DISABLE_OPERATINGDIR char *operating_dir_cpy = operating_dir; #endif #ifndef DISABLE_WRAPJUSTIFY ssize_t wrap_at_cpy = wrap_at; #endif #ifndef NANO_SMALL char *backup_dir_cpy = backup_dir; #endif #ifndef DISABLE_JUSTIFY char *quotestr_cpy = quotestr; #endif #ifndef DISABLE_SPELLER char *alt_speller_cpy = alt_speller; #endif ssize_t tabsize_cpy = tabsize; unsigned long flags_cpy = flags; #ifndef DISABLE_OPERATINGDIR operating_dir = NULL; #endif #ifndef NANO_SMALL backup_dir = NULL; #endif #ifndef DISABLE_JUSTIFY quotestr = NULL; #endif #ifndef DISABLE_SPELLER alt_speller = NULL; #endif do_rcfile(); #ifndef DISABLE_OPERATINGDIR if (operating_dir_cpy != NULL) { free(operating_dir); operating_dir = operating_dir_cpy; } #endif #ifndef DISABLE_WRAPJUSTIFY if (fill_flag_used) wrap_at = wrap_at_cpy; #endif #ifndef NANO_SMALL if (backup_dir_cpy != NULL) { free(backup_dir); backup_dir = backup_dir_cpy; } #endif #ifndef DISABLE_JUSTIFY if (quotestr_cpy != NULL) { free(quotestr); quotestr = quotestr_cpy; } #endif #ifndef DISABLE_SPELLER if (alt_speller_cpy != NULL) { free(alt_speller); alt_speller = alt_speller_cpy; } #endif if (tabsize_cpy != -1) tabsize = tabsize_cpy; flags |= flags_cpy; } #if defined(DISABLE_ROOTWRAP) && !defined(DISABLE_WRAPPING) else if (geteuid() == NANO_ROOT_UID) SET(NO_WRAP); #endif #endif /* ENABLE_NANORC */ #ifndef NANO_SMALL history_init(); #ifdef ENABLE_NANORC if (!ISSET(NO_RCFILE) && ISSET(HISTORYLOG)) load_history(); #endif #endif #ifndef NANO_SMALL /* Set up the backup directory (unless we're using restricted mode, * in which case backups are disabled, since they would allow * reading from or writing to files not specified on the command * line). This entails making sure it exists and is a directory, so * that backup files will be saved there. */ if (!ISSET(RESTRICTED)) init_backup_dir(); #endif #ifndef DISABLE_OPERATINGDIR /* Set up the operating directory. This entails chdir()ing there, * so that file reads and writes will be based there. */ init_operating_dir(); #endif #ifndef DISABLE_JUSTIFY if (punct == NULL) punct = mallocstrcpy(punct, ".?!"); if (brackets == NULL) brackets = mallocstrcpy(brackets, "'\")}]>"); if (quotestr == NULL) quotestr = mallocstrcpy(NULL, #ifdef HAVE_REGEX_H "^([ \t]*[|>:}#])+" #else "> " #endif ); #ifdef HAVE_REGEX_H quoterc = regcomp("ereg, quotestr, REG_EXTENDED); if (quoterc == 0) { /* We no longer need quotestr, just quotereg. */ free(quotestr); quotestr = NULL; } else { size_t size = regerror(quoterc, "ereg, NULL, 0); quoteerr = charalloc(size); regerror(quoterc, "ereg, quoteerr, size); } #else quotelen = strlen(quotestr); #endif /* !HAVE_REGEX_H */ #endif /* !DISABLE_JUSTIFY */ #ifndef DISABLE_SPELLER /* If we don't have an alternative spell checker after reading the * command line and/or rcfile(s), check $SPELL for one, as Pico * does (unless we're using restricted mode, in which case spell * checking is disabled, since it would allow reading from or * writing to files not specified on the command line). */ if (!ISSET(RESTRICTED) && alt_speller == NULL) { char *spellenv = getenv("SPELL"); if (spellenv != NULL) alt_speller = mallocstrcpy(NULL, spellenv); } #endif #if !defined(NANO_SMALL) && defined(ENABLE_NANORC) /* If whitespace wasn't specified, set its default value. */ if (whitespace == NULL) { whitespace = mallocstrcpy(NULL, " "); whitespace_len[0] = 1; whitespace_len[1] = 1; } #endif /* If tabsize wasn't specified, set its default value. */ if (tabsize == -1) tabsize = WIDTH_OF_TAB; /* Back up the old terminal settings so that they can be restored. */ tcgetattr(0, &oldterm); /* Curses initialization stuff: Start curses and set up the * terminal state. */ initscr(); terminal_init(); /* Set up the global variables and the shortcuts. */ global_init(FALSE); shortcut_init(FALSE); /* Set up the signal handlers. */ signal_init(); #ifdef DEBUG fprintf(stderr, "Main: set up windows\n"); #endif window_init(); #ifndef DISABLE_MOUSE mouse_init(); #endif #ifdef DEBUG fprintf(stderr, "Main: open file\n"); #endif /* If there's a +LINE flag here, it is the first non-option * argument, and it is followed by at least one other argument, the * filename it applies to. */ if (0 < optind && optind < argc - 1 && argv[optind][0] == '+') { startline = atoi(&argv[optind][1]); optind++; } #ifdef ENABLE_MULTIBUFFER old_multibuffer = ISSET(MULTIBUFFER); SET(MULTIBUFFER); /* Read all the files after the first one on the command line into * new buffers. */ { int i = optind + 1, iline = 0; for (; i < argc; i++) { /* If there's a +LINE flag here, it is followed by at least * one other argument, the filename it applies to. */ if (i < argc - 1 && argv[i][0] == '+' && iline == 0) { iline = atoi(&argv[i][1]); } else { load_buffer(argv[i]); if (iline > 0) { do_gotoline(iline, FALSE); iline = 0; } } } } #endif /* Read the first file on the command line into either the current * buffer or a new buffer, depending on whether multibuffer mode is * enabled. */ if (optind < argc) load_buffer(argv[optind]); /* We didn't open any files if all the command line arguments were * invalid files like directories or if there were no command line * arguments given. In this case, we have to load a blank buffer. * Also, we unset view mode to allow editing. */ if (filename == NULL) { filename = mallocstrcpy(NULL, ""); new_file(); UNSET(VIEW_MODE); /* Add this new entry to the open_files structure if we have * multibuffer support, or to the main filestruct if we don't. */ load_file(); } #ifdef ENABLE_MULTIBUFFER if (!old_multibuffer) UNSET(MULTIBUFFER); #endif #ifdef DEBUG fprintf(stderr, "Main: top and bottom win\n"); #endif titlebar(NULL); display_main_list(); if (startline > 0) do_gotoline(startline, FALSE); #ifndef NANO_SMALL /* Return here after a SIGWINCH. */ sigsetjmp(jmpbuf, 1); #endif edit_refresh(); while (TRUE) { bool meta_key, func_key, s_or_t, ran_func, finished; /* Make sure the cursor is in the edit window. */ reset_cursor(); /* If constant cursor position display is on, display the cursor * position. */ if (ISSET(CONSTUPDATE)) do_cursorpos(TRUE); currshortcut = main_list; /* Read in and interpret characters. */ do_input(&meta_key, &func_key, &s_or_t, &ran_func, &finished, TRUE); } assert(FALSE); }