/************************************************************************** * nano.c * * * * Copyright (C) 1999 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 1, 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. * * * **************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "proto.h" #include "nano.h" #ifndef NANO_SMALL #include #define _(string) gettext(string) #else #define _(string) (string) #endif #ifdef HAVE_TERMIOS_H #include #endif #ifdef HAVE_TERMIO_H #include #endif #ifdef HAVE_GETOPT_H #include #endif /* Former globals, now static */ char *last_search; /* Last string we searched for */ char *last_replace; /* Last replacement string */ int temp_opt = 0; /* Editing temp file (pico -t option) */ int fill = 0; /* Fill - where to wrap lines, basically */ static char *alt_speller; /* Alternative spell command */ struct termios oldterm; /* The user's original term settings */ static char *help_text_init = ""; /* Initial message, not including shortcuts */ /* What we do when we're all set to exit */ RETSIGTYPE finish(int sigage) { if (!ISSET(NO_HELP)) { mvwaddstr(bottomwin, 1, 0, hblank); mvwaddstr(bottomwin, 2, 0, hblank); } else mvwaddstr(bottomwin, 0, 0, hblank); wrefresh(bottomwin); endwin(); /* Restore the old term settings */ tcsetattr(0, TCSANOW, &oldterm); exit(sigage); } /* Die (gracefully?) */ void die(char *msg, ...) { va_list ap; va_start(ap, msg); vfprintf(stderr, msg, ap); va_end(ap); /* if we can't save we have REAL bad problems, * but we might as well TRY. FIXME: This should probabally base it * off of the current filename */ write_file("nano.save", 0); /* Restore the old term settings */ tcsetattr(0, TCSANOW, &oldterm); clear(); refresh(); resetty(); endwin(); fprintf(stderr, msg); fprintf(stderr, _("\nBuffer written to 'nano.save'\n")); exit(1); /* We have a problem: exit w/ errorlevel(1) */ } void print_view_warning(void) { statusbar(_("Key illegal in VIEW mode")); } /* Initialize global variables - no better way for now */ void global_init(void) { int i; center_x = COLS / 2; center_y = LINES / 2; current_x = 0; current_y = 0; editwinrows = LINES - 5 + no_help(); fileage = NULL; cutbuffer = NULL; current = NULL; edittop = NULL; editbot = NULL; totlines = 0; placewewant = 0; if (!fill) fill = COLS - 8; hblank = nmalloc(COLS + 1); /* Thanks BG for this bit... */ for (i = 0; i <= COLS - 1; i++) hblank[i] = ' '; hblank[i] = 0; last_search = nmalloc(132); last_replace = nmalloc(132); answer = nmalloc(132); } void init_help_msg(void) { #ifndef NANO_SMALL help_text_init = _(" 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. Alt-key " "sequences are notated with an at (@) symbol. The following " "keystrokes are available in the main editor window. " "Optional keys are shown in parentheses:\n\n"); #endif } /* Make a copy of a node to a pointer (space will be malloc()ed) */ filestruct *copy_node(filestruct * src) { filestruct *dst; dst = nmalloc(sizeof(filestruct)); dst->data = nmalloc(strlen(src->data) + 1); dst->next = src->next; dst->prev = src->prev; strcpy(dst->data, src->data); dst->lineno = src->lineno; return dst; } /* Unlink a node from the rest of the struct */ void unlink_node(filestruct * fileptr) { if (fileptr->prev != NULL) fileptr->prev->next = fileptr->next; if (fileptr->next != NULL) fileptr->next->prev = fileptr->prev; } void delete_node(filestruct * fileptr) { if (fileptr->data != NULL) free(fileptr->data); free(fileptr); } /* Okay, now let's duplicate a whole struct! */ filestruct *copy_filestruct(filestruct * src) { filestruct *dst, *tmp, *head, *prev; head = copy_node(src); dst = head; /* Else we barf on copying just one line */ head->prev = NULL; tmp = src->next; prev = head; while (tmp != NULL) { dst = copy_node(tmp); dst->prev = prev; prev->next = dst; prev = dst; tmp = tmp->next; } dst->next = NULL; return head; } /* Free() a single node */ int free_node(filestruct * src) { if (src == NULL) return 0; if (src->next != NULL) free(src->data); free(src); return 1; } int free_filestruct(filestruct * src) { filestruct *fileptr = src; if (src == NULL) return 0; while (fileptr->next != NULL) { fileptr = fileptr->next; free_node(fileptr->prev); #ifdef DEBUG fprintf(stderr, _("free_node(): free'd a node, YAY!\n")); #endif } free_node(fileptr); #ifdef DEBUG fprintf(stderr, _("free_node(): free'd last node.\n")); #endif return 1; } int renumber_all(void) { filestruct *temp; long i = 1; for (temp = fileage; temp != NULL; temp = temp->next) { temp->lineno = i++; } return 0; } int renumber(filestruct * fileptr) { filestruct *temp; if (fileptr == NULL || fileptr->prev == NULL || fileptr == fileage) { renumber_all(); return 0; } for (temp = fileptr; temp != NULL; temp = temp->next) { temp->lineno = temp->prev->lineno + 1; } return 0; } /* Fix the memory allocation for a string */ void align(char **strp) { /* There was a serious bug here: the new address was never stored anywhere... */ *strp = nrealloc(*strp, strlen(*strp) + 1); } void usage(void) { #ifdef HAVE_GETOPT_LONG printf(_("Usage: nano [GNU long option] [option] +LINE \n\n")); printf(_("Option Long option Meaning\n")); #ifdef HAVE_TABSIZE printf(_ (" -T --tabsize=[num] Set width of a tab to num\n")); #endif printf (_ (" -V --version Print version information and exit\n")); printf(_ (" -c --const Constantly show cursor position\n")); printf(_ (" -h --help Show this message\n")); printf(_ (" -i --autoindent Automatically indent new lines\n")); printf(_ (" -l --nofollow Don't follow symbolic links, overwrite.\n")); #ifndef NANO_SMALL #ifdef NCURSES_MOUSE_VERSION printf(_(" -m --mouse Enable mouse\n")); #endif #endif printf (_ (" -r [#cols] --fill=[#cols] Set fill cols to (wrap lines at) #cols\n")); printf(_ (" -p --pico Make bottom 2 lines more Pico-like\n")); printf(_ (" -s [prog] --speller=[prog] Enable alternate speller\n")); printf(_ (" -t --tempfile Auto save on exit, don't prompt\n")); printf(_ (" -v --view View (read only) mode\n")); printf(_ (" -w --nowrap Don't wrap long lines\n")); printf(_ (" -x --nohelp Don't show help window\n")); printf(_ (" -z --suspend Enable suspend\n")); printf(_ (" +LINE Start at line number LINE\n")); #else printf(_("Usage: nano [option] +LINE \n\n")); printf(_("Option Meaning\n")); #ifdef HAVE_TABSIZE printf(_(" -T [num] Set width of a tab to num\n")); #endif printf(_(" -V Print version information and exit\n")); printf(_(" -c Constantly show cursor position\n")); printf(_(" -h Show this message\n")); printf(_(" -i Automatically indent new lines\n")); printf(_ (" -l Don't follow symbolic links, overwrite.\n")); #ifndef NANO_SMALL #ifdef NCURSES_MOUSE_VERSION printf(_(" -m Enable mouse\n")); #endif #endif printf(_ (" -r [#cols] Set fill cols to (wrap lines at) #cols\n")); printf(_(" -s [prog] Enable alternate speller\n")); printf(_(" -p Make bottom 2 lines more Pico-like\n")); printf(_(" -t Auto save on exit, don't prompt\n")); printf(_(" -v View (read only) mode\n")); printf(_(" -w Don't wrap long lines\n")); printf(_(" -x Don't show help window\n")); printf(_(" -z Enable suspend\n")); printf(_(" +LINE Start at line number LINE\n")); #endif exit(0); } void version(void) { printf(_(" nano version %s by Chris Allegretta (compiled %s, %s)\n"), VERSION, __TIME__, __DATE__); printf(_(" Email: nano@asty.org Web: http://www.asty.org/nano\n")); } filestruct *make_new_node(filestruct * prevnode) { filestruct *newnode; newnode = nmalloc(sizeof(filestruct)); newnode->data = NULL; newnode->prev = prevnode; newnode->next = NULL; if (prevnode != NULL) newnode->lineno = prevnode->lineno + 1; return newnode; } int do_mark() { #ifdef NANO_SMALL nano_small_msg(); #else if (!ISSET(MARK_ISSET)) { statusbar(_("Mark Set")); SET(MARK_ISSET); mark_beginbuf = current; mark_beginx = current_x; } else { statusbar(_("Mark UNset")); UNSET(MARK_ISSET); mark_beginbuf = NULL; mark_beginx = 0; edit_refresh(); } #endif return 1; } int no_help(void) { if ISSET (NO_HELP) return 2; else return 0; } void nano_small_msg(void) { statusbar("Sorry, this function not available with nano-tiny option"); } /* The user typed a printable character; add it to the edit buffer */ void do_char(char ch) { /* More dangerousness fun =) */ current->data = nrealloc(current->data, strlen(current->data) + 2); memmove(¤t->data[current_x + 1], ¤t->data[current_x], strlen(current->data) - current_x + 1); current->data[current_x] = ch; do_right(); if (!ISSET(NO_WRAP) && (ch != '\t')) check_wrap(current, ch); set_modified(); check_statblank(); UNSET(KEEP_CUTBUFFER); totsize++; } /* Someone hits return *gasp!* */ int do_enter(filestruct * inptr) { filestruct *new; char *tmp, *spc; int extra = 0; new = make_new_node(inptr); tmp = ¤t->data[current_x]; current_x = 0; /* Do auto-indenting, like the neolithic Turbo Pascal editor */ if (ISSET(AUTOINDENT)) { spc = current->data; if (spc) { while ((*spc == ' ') || (*spc == '\t')) { extra++; spc++; current_x++; } new->data = nmalloc(strlen(tmp) + extra + 1); strncpy(new->data, current->data, extra); strcpy(&new->data[extra], tmp); } } else { new->data = nmalloc(strlen(tmp) + 1); strcpy(new->data, tmp); } *tmp = 0; new->next = inptr->next; new->prev = inptr; inptr->next = new; if (new->next != NULL) new->next->prev = new; else { filebot = new; editbot = new; } totsize++; renumber(current); current = new; align(¤t->data); if (current_y == editwinrows - 1) { edit_update(current); /* FIXME - figure out why the hell this is needed =) */ reset_cursor(); } else current_y++; totlines++; set_modified(); update_cursor(); edit_refresh(); placewewant = xplustabs(); return 1; } int do_enter_void(void) { return do_enter(current); } void do_next_word(void) { filestruct *fileptr; int i; if (current == NULL) return; i = current_x; for (fileptr = current; fileptr != NULL; fileptr = fileptr->next) { if (fileptr == current) { while (isalnum((int) fileptr->data[i]) && fileptr->data[i] != 0) i++; if (fileptr->data[i] == 0) { i = 0; continue; } } while (!isalnum((int) fileptr->data[i]) && fileptr->data[i] != 0) i++; if (fileptr->data[i] != 0) break; i = 0; } if (fileptr == NULL) current = filebot; else current = fileptr; current_x = i; placewewant = xplustabs(); if (current->lineno >= editbot->lineno) edit_update(current); } void do_wrap(filestruct * inptr, char input_char) { int i = 0; /* Index into ->data for line. */ int i_tabs = 0; /* Screen position of ->data[i]. */ int last_word_end = -1; /* Location of end of last word found. */ int current_word_start = -1; /* Location of start of current word. */ int current_word_start_t = -1; /* Location of start of current word screen position. */ int current_word_end = -1; /* Location of end of current word */ int current_word_end_t = -1; /* Location of end of current word screen position. */ int len = strlen(inptr->data); int down = 0; int right = 0; struct filestruct *temp = NULL; assert(strlenpt(inptr->data) > fill); for (i = 0, i_tabs = 0; i < len; i++, i_tabs++) { if (!isspace(inptr->data[i])) { last_word_end = current_word_end; current_word_start = i; current_word_start_t = i_tabs; while (!isspace(inptr->data[i]) && inptr->data[i]) { i++; i_tabs++; if (inptr->data[i] < 32) i_tabs++; } if (inptr->data[i]) { current_word_end = i; current_word_end_t = i_tabs; } else { current_word_end = i - 1; current_word_end_t = i_tabs - 1; } } if (inptr->data[i] == NANO_CONTROL_I) { if (i_tabs % TABSIZE != 0); i_tabs += TABSIZE - (i_tabs % TABSIZE); } if (current_word_end_t > fill) break; } assert(current_word_end_t > fill); /* There are a few (ever changing) cases of what the line could look like. * 1) only one word on the line before wrap point. * a) one word takes up the whole line with no starting spaces. * - do nothing and return. * b) cursor is on word or before word at wrap point and there are spaces at beginning. * - word starts new line. * - keep white space on original line up to the cursor. * *) cursor is after word at wrap point * - either it's all white space after word, and this routine isn't called. * - or we are actually in case 2 (2 words). * 2) Two or more words on the line before wrap point. * a) cursor is at a word or space before wrap point * - word at wrap point starts a new line. * - white space at end of original line is cleared, unless * it is all spaces between previous word and next word which appears after fill. * b) cursor is at the word at the wrap point. * - word at wrap point starts a new line. * 1. pressed a space and at first character of wrap point word. * - white space on original line is kept to where cursor was. * 2. pressed non space (or space elsewhere). * - white space at end of original line is cleared. * c) cursor is past the word at the wrap point. * - word at wrap point starts a new line. * - white space at end of original line is cleared */ temp = nmalloc(sizeof(filestruct)); /* Category 1a: one word taking up the whole line with no beginning spaces. */ if ((last_word_end == -1) && (!isspace(inptr->data[0]))) { for (i = current_word_end; i < len; i++) { if (!isspace(inptr->data[i]) && i < len) { current_word_start = i; while (!isspace(inptr->data[i]) && (i < len)) { i++; } last_word_end = current_word_end; current_word_end = i; break; } } if (last_word_end == -1) { free(temp); return; } if (current_x >= last_word_end) { right = (current_x - current_word_start) + 1; current_x = last_word_end; down = 1; } temp->data = nmalloc(strlen(&inptr->data[current_word_start]) + 1); strcpy(temp->data, &inptr->data[current_word_start]); inptr->data = nrealloc(inptr->data, last_word_end + 2); inptr->data[last_word_end + 1] = 0; } else /* Category 1b: one word on the line and word not taking up whole line (i.e. there are spaces at the beginning of the line) */ if (last_word_end == -1) { temp->data = nmalloc(strlen(&inptr->data[current_word_start]) + 1); strcpy(temp->data, &inptr->data[current_word_start]); /* Inside word, remove it from original, and move cursor to right spot. */ if (current_x >= current_word_start) { right = current_x - current_word_start; current_x = 0; down = 1; } inptr->data = nrealloc(inptr->data, current_x + 1); inptr->data[current_x] = 0; if (ISSET(MARK_ISSET) && (mark_beginbuf == inptr)) { mark_beginbuf = temp; mark_beginx = 0; } } /* Category 2: two or more words on the line. */ else { /* Case 2a: cursor before word at wrap point. */ if (current_x < current_word_start) { temp->data = nmalloc(strlen(&inptr->data[current_word_start]) + 1); strcpy(temp->data, &inptr->data[current_word_start]); if (!isspace(input_char)) { i = current_word_start - 1; while (isspace(inptr->data[i])) { i--; assert(i >= 0); } } else if (current_x <= last_word_end) i = last_word_end - 1; else i = current_x; inptr->data = nrealloc(inptr->data, i + 2); inptr->data[i + 1] = 0; } /* Case 2b: cursor at word at wrap point. */ else if ((current_x >= current_word_start) && (current_x <= (current_word_end + 1))) { temp->data = nmalloc(strlen(&inptr->data[current_word_start]) + 1); strcpy(temp->data, &inptr->data[current_word_start]); down = 1; right = current_x - current_word_start; i = current_word_start - 1; if (isspace(input_char) && (current_x == current_word_start)) { current_x = current_word_start; inptr->data = nrealloc(inptr->data, current_word_start + 1); inptr->data[current_word_start] = 0; } else { while (isspace(inptr->data[i])) { i--; assert(i >= 0); } inptr->data = nrealloc(inptr->data, i + 2); inptr->data[i + 1] = 0; } } /* Case 2c: cursor past word at wrap point. */ else { temp->data = nmalloc(strlen(&inptr->data[current_word_start]) + 1); strcpy(temp->data, &inptr->data[current_word_start]); down = 1; right = current_x - current_word_start; current_x = current_word_start; i = current_word_start - 1; while (isspace(inptr->data[i])) { i--; assert(i >= 0); inptr->data = nrealloc(inptr->data, i + 2); inptr->data[i + 1] = 0; } } } /* We pre-pend wrapped part to next line. */ if (ISSET(SAMELINEWRAP) && inptr->next) { /* Plus one for the space which concatenates the two lines together plus 1 for \0. */ char *p = nmalloc(strlen(temp->data) + strlen(inptr->next->data) + 2); int old_x = current_x, old_y = current_y; strcpy(p, temp->data); strcat(p, " "); strcat(p, inptr->next->data); free(inptr->next->data); inptr->next->data = p; free(temp->data); free(temp); /* The next line line may need to be wrapped as well. */ current_y = old_y + 1; current_x = strlen(inptr->next->data); while (current_x >= 0) { if (isspace(inptr->next->data[current_x]) && (current_x < fill)) break; current_x--; } if (current_x >= 0) check_wrap(inptr->next, ' '); current_x = old_x; current_y = old_y; } /* Else we start a new line. */ else { temp->prev = inptr; temp->next = inptr->next; if (inptr->next) inptr->next->prev = temp; inptr->next = temp; if (!temp->next) filebot = temp; SET(SAMELINEWRAP); } totlines++; totsize++; renumber(inptr); edit_update_top(edittop); /* Move the cursor to the new line if appropriate. */ if (down) { do_right(); } /* Move the cursor to the correct spot in the line if appropriate. */ while (right--) { do_right(); } edit_update_top(edittop); reset_cursor(); edit_refresh(); } /* Check to see if we've just caused the line to wrap to a new line */ void check_wrap(filestruct * inptr, char ch) { int len = strlenpt(inptr->data); #ifdef DEBUG fprintf(stderr, _("check_wrap called with inptr->data=\"%s\"\n"), inptr->data); #endif if (len <= fill) return; else { int i = actual_x(inptr, fill); /* Do not wrap if there are no words on or after wrap point. */ int char_found = 0; while (isspace(inptr->data[i]) && inptr->data[i]) i++; if (!inptr->data[i]) return; /* String must be at least 1 character long. */ for (i = strlen(inptr->data) - 1; i >= 0; i--) { if (isspace(inptr->data[i])) { if (!char_found) continue; char_found = 2; /* 2 for yes do wrap. */ break; } else char_found = 1; /* 1 for yes found a word, but must check further. */ } if (char_found == 2) do_wrap(inptr, ch); } } /* Stuff we do when we abort from programs and want to clean up the * screen. This doesnt do much right now. */ void do_early_abort(void) { blank_statusbar_refresh(); } int page_up(void) { wrap_reset(); current_x = 0; placewewant = 0; if (current == fileage) return 0; current_y = 0; edit_update_bot(edittop); update_cursor(); UNSET(KEEP_CUTBUFFER); check_statblank(); return 1; } void delete_buffer(filestruct * inptr) { if (inptr != NULL) { delete_buffer(inptr->next); free(inptr->data); free(inptr); } } int do_backspace(void) { filestruct *previous, *tmp; if (current_x != 0) { /* Let's get dangerous */ memmove(¤t->data[current_x - 1], ¤t->data[current_x], strlen(current->data) - current_x + 1); #ifdef DEBUG fprintf(stderr, _("current->data now = \"%s\"\n"), current->data); #endif align(¤t->data); do_left(); } else { if (current == fileage) return 0; /* Can't delete past top of file */ previous = current->prev; current_x = strlen(previous->data); previous->data = nrealloc(previous->data, strlen(previous->data) + strlen(current->data) + 1); strcat(previous->data, current->data); tmp = current; unlink_node(current); delete_node(current); if (current == edittop) { if (previous->next) current = previous->next; else current = previous; page_up(); } else { if (previous->next) current = previous->next; else current = previous; update_line(current, current_x); } /* Ooops, sanity check */ if (tmp == filebot) { filebot = current; editbot = current; } current = previous; renumber(current); previous_line(); totlines--; #ifdef DEBUG fprintf(stderr, _("After, data = \"%s\"\n"), current->data); #endif } totsize--; set_modified(); UNSET(KEEP_CUTBUFFER); edit_refresh(); return 1; } int do_delete(void) { filestruct *foo; if (current_x != strlen(current->data)) { /* Let's get dangerous */ memmove(¤t->data[current_x], ¤t->data[current_x + 1], strlen(current->data) - current_x); align(¤t->data); } else if (current->next != NULL) { current->data = nrealloc(current->data, strlen(current->data) + strlen(current->next->data) + 1); strcat(current->data, current->next->data); foo = current->next; if (filebot == foo) { filebot = current; editbot = current; } unlink_node(foo); delete_node(foo); update_line(current, current_x); renumber(current); totlines--; } else return 0; totsize--; set_modified(); UNSET(KEEP_CUTBUFFER); edit_refresh(); return 1; } void wrap_reset(void) { UNSET(SAMELINEWRAP); } /* Stuff we want to do when we exit the spell program one of its many ways */ void exit_spell(char *tmpfilename, char *foo) { free(foo); if (remove(tmpfilename) == -1) statusbar(_("Error deleting tempfile, ack!")); } /* * This is Chris' very ugly spell function. Someone please make this * better =-) */ int do_spell(void) { #ifdef NANO_SMALL nano_small_msg(); return 1; #else char *temp, *foo; int i, size; if ((temp = tempnam(0, "nano.")) == NULL) { statusbar(_("Could not create a temporary filename: %s"), strerror(errno)); return 0; } if (write_file(temp, 1) == -1) return 0; if (alt_speller) { size = strlen(temp) + strlen(alt_speller) + 2; foo = nmalloc(size); snprintf(foo, size, "%s %s", alt_speller, temp); } else { /* For now, we only try ispell because we're not capable of handling the normal spell program (yet...) */ size = strlen(temp) + 8; foo = nmalloc(size); snprintf(foo, size, "ispell %s", temp); } endwin(); resetty(); if (alt_speller) { if ((i = system(foo)) == -1 || i == 32512) { statusbar(_("Could not invoke spell program \"%s\""), alt_speller); exit_spell(temp, foo); return 0; } } else if ((i = system(foo)) == -1 || i == 32512) { /* Why 32512? I dont know! */ statusbar(_("Could not invoke \"ispell\"")); exit_spell(temp, foo); return 0; } initscr(); free_filestruct(fileage); global_init(); open_file(temp, 0, 1); edit_update(fileage); set_modified(); exit_spell(temp, foo); statusbar(_("Finished checking spelling")); return 1; #endif } int do_exit(void) { int i; if (!ISSET(MODIFIED)) finish(0); if (temp_opt) { i = 1; } else { i = do_yesno(0, 0, _ ("Save modified buffer (ANSWERING \"No\" WILL DESTROY CHANGES) ? ")); } #ifdef DEBUG dump_buffer(fileage); #endif if (i == 1) { if (do_writeout(1) > 0) finish(0); } else if (i == 0) finish(0); else statusbar(_("Cancelled")); display_main_list(); return 1; } #ifndef NANO_SMALL #ifdef NCURSES_MOUSE_VERSION void do_mouse(void) { MEVENT mevent; if (getmouse(&mevent) == ERR) return; /* If mouse not in edit window, return (add help selection later). */ if (!wenclose(edit, mevent.y, mevent.x)) return; /* Subtract out size of topwin. Perhaps we need a constant somewhere? */ mevent.y -= 2; /* Selecting where the cursor is sets the mark. * Selecting beyond the line length with the cursor at the end of the * line sets the mark as well. */ if ((mevent.y == current_y) && ((mevent.x == current_x) || (current_x == strlen(current->data) && (mevent.x > strlen(current->data))))) { if (ISSET(VIEW_MODE)) { print_view_warning(); return; } do_mark(); } else if (mevent.y > current_y) { while (mevent.y > current_y) { if (current->next != NULL) current = current->next; else break; current_y++; } } else if (mevent.y < current_y) { while (mevent.y < current_y) { if (current->prev != NULL) current = current->prev; else break; current_y--; } } current_x = mevent.x; if (current_x > strlen(current->data)) current_x = strlen(current->data); update_cursor(); edit_refresh(); } #endif #endif /* Handler for SIGHUP */ RETSIGTYPE handle_hup(int signal) { write_file("nano.save", 0); finish(1); } void handle_sigwinch(int s) { #ifndef NANO_SMALL char *tty = NULL; int fd = 0; int result = 0; int i = 0; struct winsize win; tty = ttyname(0); if (!tty) return; fd = open(tty, O_RDWR); if (fd == -1) return; result = ioctl(fd, TIOCGWINSZ, &win); if (result == -1) return; COLS = win.ws_col; LINES = win.ws_row; center_x = COLS / 2; center_y = LINES / 2; editwinrows = LINES - 5 + no_help(); fill = COLS - 8; free(hblank); hblank = nmalloc(COLS + 1); for (i = 0; i <= COLS - 1; i++) hblank[i] = ' '; hblank[i] = 0; #ifdef HAVE_NCURSES_H resizeterm(LINES, COLS); #ifdef HAVE_WRESIZE if (wresize(topwin, 2, COLS) == ERR) die(_("Cannot resize top win")); if (mvwin(topwin, 0, 0) == ERR) die(_("Cannot move top win")); if (wresize(edit, editwinrows, COLS) == ERR) die(_("Cannot resize edit win")); if (mvwin(edit, 2, 0) == ERR) die(_("Cannot move edit win")); if (wresize(bottomwin, 3 - no_help(), COLS) == ERR) die(_("Cannot resize bottom win")); if (mvwin(bottomwin, LINES - 3 + no_help(), 0) == ERR) die(_("Cannot move bottom win")); #endif /* HAVE_WRESIZE */ #endif /* HAVE_NCURSES_H */ editbot = edittop; for (i = 0; (i <= editwinrows - 1) && (editbot->next != NULL) && (editbot->next != filebot); i++) editbot = editbot->next; if (current_y > editwinrows - 1) { edit_update(editbot); } erase(); /* Do these b/c width may have changed... */ refresh(); titlebar(); edit_refresh(); display_main_list(); total_refresh(); #endif } int do_tab(void) { do_char('\t'); return 1; } #ifndef NANO_SMALL int empty_line(const char *data) { while (*data) { if (!isspace(*data)) return 0; data++; } return 1; } int no_spaces(const char *data) { while (*data) { if (isspace(*data)) return 0; data++; } return 1; } void justify_format(char *data) { int i = 0; int len = strlen(data); /* Skip first character regardless and leading whitespace. */ for (i = 1; i < len; i++) { if (!isspace(data[i])) break; } i++; /* (i) is now at least 2. */ /* No double spaces allowed unless following a period. Tabs -> space. No double tabs. */ for (; i < len; i++) { if (isspace(data[i]) && isspace(data[i - 1]) && (data[i - 2] != '.')) { memmove(data + i, data + i + 1, len - i); len--; i--; } } } #endif int do_justify(void) { #ifndef NANO_SMALL int slen = 0; /* length of combined lines on one line. */ int initial_y; filestruct *initial = NULL; if (empty_line(current->data)) { /* Justify starting at first non-empty line. */ do { if (!current->next) return 1; current = current->next; current_y++; } while (empty_line(current->data)); } else { /* Search back for the beginning of the paragraph, where * Paragraph is 1) A line with leading whitespace * or 2) A line following an empty line. */ while (current->prev != NULL) { if (isspace(current->data[0]) || !current->data[0]) break; current = current->prev; current_y--; } /* First line with leading whitespace may be empty. */ if (empty_line(current->data)) { if (current->next) { current = current->next; current_y++; } else return 1; } } initial = current; initial_y = current_y; set_modified(); /* Put the whole paragraph into one big line. */ while (current->next && !isspace(current->next->data[0]) && current->next->data[0]) { filestruct *tmpnode = current->next; int len = strlen(current->data); int len2 = strlen(current->next->data); /* length of both strings plus space between strings and ending \0. */ current->data = nrealloc(current->data, len + len2 + 2); current->data[len++] = ' '; current->data[len] = '\0'; strncat(current->data, current->next->data, len2); unlink_node(tmpnode); delete_node(tmpnode); } justify_format(current->data); slen = strlen(current->data); while ((strlenpt(current->data) > (fill)) && !no_spaces(current->data)) { int i = 0; int len2 = 0; filestruct *tmpline = nmalloc(sizeof(filestruct)); /* Start at fill , unless line isn't that long (but it appears at least * fill long with tabs. */ if (slen > fill) i = fill; else i = slen; for (; i > 0; i--) { if (isspace(current->data[i]) && ((strlenpt(current->data) - strlen(current->data + i)) <= fill)) break; } if (!i) break; current->data[i] = '\0'; len2 = strlen(current->data + i + 1); tmpline->data = nmalloc(len2 + 1); /* Skip the white space in current. */ memcpy(tmpline->data, current->data + i + 1, len2); tmpline->data[len2] = '\0'; current->data = nrealloc(current->data, i + 1); tmpline->prev = current; tmpline->next = current->next; if (current->next != NULL) current->next->prev = tmpline; current->next = tmpline; current = tmpline; slen -= i + 1; current_y++; } if (current->next) current = current->next; else filebot = current; current_x = 0; placewewant = 0; renumber(initial); totlines = filebot->lineno; werase(edit); if ((current_y < 0) || (current_y >= editwinrows - 1) || (initial_y <= 0)) { edit_update(current); center_cursor(); } else { int i = 0; editbot = edittop; for (i = 0; (i <= editwinrows - 1) && (editbot->next != NULL) && (editbot->next != filebot); i++) editbot = editbot->next; } edit_refresh(); edit_refresh(); /* XXX FIXME XXX */ statusbar("Justify Complete"); return 1; #else nano_small_msg(); return 1; #endif } void help_init(void) { int i, sofar = 0; long allocsize = 1; /* How much space we're gonna need for the help text */ char buf[BUFSIZ]; /* Compute the space needed for the shortcut lists - we add 15 to have room for the shortcut abbrev and its possible alternate keys */ for (i = 0; i < MAIN_LIST_LEN; i++) if (main_list[i].help != NULL) allocsize += strlen(main_list[i].help) + 15; allocsize += strlen(help_text_init); if (help_text != NULL) free(help_text); /* Allocate space for the help text */ help_text = nmalloc(allocsize); /* Now add the text we want */ strcpy(help_text, help_text_init); /* Now add our shortcut info */ for (i = 0; i < MAIN_LIST_LEN; i++) { sofar = snprintf(buf, BUFSIZ, "^%c ", main_list[i].val + 64); if (main_list[i].misc1 > KEY_F0 && main_list[i].misc1 <= KEY_F(64)) sofar += snprintf(&buf[sofar], BUFSIZ - sofar, "(F%d) ", main_list[i].misc1 - KEY_F0); else sofar += snprintf(&buf[sofar], BUFSIZ - sofar, " "); if (main_list[i].altval > 0) sofar += snprintf(&buf[sofar], BUFSIZ - sofar, "(@%c) ", main_list[i].altval - 32); else sofar += snprintf(&buf[sofar], BUFSIZ - sofar, " "); if (main_list[i].help != NULL) snprintf(&buf[sofar], BUFSIZ - sofar, "%s\n", main_list[i].help); strcat(help_text, buf); } } int main(int argc, char *argv[]) { int optchr; int kbinput; /* Input from keyboard */ long startline = 0; /* Line to try and start at */ struct sigaction act; /* For our lovely signals */ int keyhandled = 0; /* Have we handled the keystroke yet? */ int tmpkey = 0, i; #ifdef HAVE_TABSIZE int usrtabsize = 0; /* User defined tab size */ #endif char *argv0; struct termios term; #ifdef HAVE_GETOPT_LONG int option_index = 0; struct option long_options[] = { {"version", 0, 0, 'V'}, {"const", 0, 0, 'c'}, {"suspend", 0, 0, 'z'}, {"nowrap", 0, 0, 'w'}, {"nohelp", 0, 0, 'x'}, {"help", 0, 0, 'h'}, {"autoindent", 0, 0, 'i'}, {"tempfile", 0, 0, 't'}, {"speller", 1, 0, 's'}, {"fill", 1, 0, 'r'}, {"mouse", 0, 0, 'm'}, {"pico", 0, 0, 'p'}, {"nofollow", 0, 0, 'l'}, #ifdef HAVE_TABSIZE {"tabsize", 0, 0, 'T'}, #endif {0, 0, 0, 0} }; #endif /* Flag inits... */ SET(FOLLOW_SYMLINKS); #ifndef NANO_SMALL setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); #endif #ifdef HAVE_GETOPT_LONG while ((optchr = getopt_long(argc, argv, "?T:Vchilmpr:s:tvwxz", long_options, &option_index)) != EOF) { #else while ((optchr = getopt(argc, argv, "h?T:Vcilmpr:s:tvwxz")) != EOF) { #endif switch (optchr) { #ifdef HAVE_TABSIZE case 'T': usrtabsize = atoi(optarg); if (usrtabsize <= 0) { usage(); /* To stop bogus data for tab width */ finish(1); } break; #else case 'T': usage(); /* Oops! You dont really have that option */ finish(1); #endif case 'V': version(); exit(0); case 'c': SET(CONSTUPDATE); break; case 'h': case '?': usage(); exit(0); case 'i': SET(AUTOINDENT); break; case 'l': UNSET(FOLLOW_SYMLINKS); break; case 'm': SET(USE_MOUSE); break; case 'p': SET(PICO_MSGS); break; case 'r': fill = atoi(optarg); if (fill <= 0) { usage(); /* To stop bogus data (like a string) */ finish(1); } break; case 's': alt_speller = nmalloc(strlen(optarg) + 1); strcpy(alt_speller, optarg); break; case 't': temp_opt = 1; break; case 'v': SET(VIEW_MODE); break; case 'w': SET(NO_WRAP); break; case 'x': SET(NO_HELP); break; case 'z': SET(SUSPEND); break; default: usage(); exit(0); } } argv0 = strrchr(argv[0], '/'); if ((argv0 && strstr(argv0, "pico")) || (!argv0 && strstr(argv[0], "pico"))) SET(PICO_MSGS); filename = nmalloc(PATH_MAX); strcpy(filename, ""); /* See if there's a non-option in argv (first non-option is the filename, if +LINE is not given) */ if (argc == 1 || argc <= optind) strcpy(filename, ""); else { /* Look for the +line flag... */ if (argv[optind][0] == '+') { startline = atoi(&argv[optind][1]); optind++; if (argc == 1 || argc <= optind) strcpy(filename, ""); else strncpy(filename, argv[optind], 132); } else strncpy(filename, argv[optind], 132); } /* First back up the old settings so they can be restored, duh */ tcgetattr(0, &oldterm); /* Adam's code to blow away intr character so ^C can show cursor pos */ tcgetattr(0, &term); for (i = 0; i < NCCS; i++) { if (term.c_cc[i] == CINTR || term.c_cc[i] == CQUIT) term.c_cc[i] = 0; } tcsetattr(0, TCSANOW, &term); /* now ncurses init stuff... */ initscr(); savetty(); nonl(); cbreak(); noecho(); timeout(0); /* Set up some global variables */ global_init(); shortcut_init(); init_help_msg(); help_init(); /* Trap SIGINT and SIGQUIT cuz 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); if (!ISSET(SUSPEND)) sigaction(SIGTSTP, &act, NULL); /* Trap SIGHUP cuz we want to write the file out. */ act.sa_handler = handle_hup; sigaction(SIGHUP, &act, NULL); act.sa_handler = handle_sigwinch; sigaction(SIGWINCH, &act, NULL); #ifdef DEBUG fprintf(stderr, _("Main: set up windows\n")); #endif /* Setup up the main text window */ edit = newwin(editwinrows, COLS, 2, 0); keypad(edit, TRUE); #ifndef NANO_SMALL #ifdef NCURSES_MOUSE_VERSION if (ISSET(USE_MOUSE)) { mousemask(BUTTON1_RELEASED, NULL); mouseinterval(50); } #endif #endif /* And the other windows */ topwin = newwin(2, COLS, 0, 0); bottomwin = newwin(3 - no_help(), COLS, LINES - 3 + no_help(), 0); keypad(bottomwin, TRUE); #ifdef DEBUG fprintf(stderr, _("Main: bottom win\n")); #endif /* Set up up bottom of window */ display_main_list(); #ifdef DEBUG fprintf(stderr, _("Main: open file\n")); #endif titlebar(); if (argc == 1) new_file(); else open_file(filename, 0, 0); if (startline > 0) do_gotoline(startline); else edit_update(fileage); edit_refresh(); reset_cursor(); #ifdef HAVE_TABSIZE if (usrtabsize > 0) TABSIZE = usrtabsize; #endif while (1) { kbinput = wgetch(edit); if (kbinput == 27) { /* Grab Alt-key stuff first */ switch (kbinput = wgetch(edit)) { case 91: switch (kbinput = wgetch(edit)) { case 'A': kbinput = KEY_UP; break; case 'B': kbinput = KEY_DOWN; break; case 'C': kbinput = KEY_RIGHT; break; case 'D': kbinput = KEY_LEFT; break; case 'H': kbinput = KEY_HOME; break; case 'F': kbinput = KEY_END; break; case 49: /* X window F-keys */ tmpkey = wgetch(edit); kbinput = KEY_F(tmpkey) - 48; wgetch(edit); /* Junk character */ break; case 53: /* page up */ kbinput = KEY_PPAGE; if ((kbinput = wgetch(edit)) == 126) kbinput = KEY_PPAGE; /* Ignore extra tilde */ else { /* I guess this could happen ;-) */ ungetch(kbinput); continue; } break; case 54: /* page down */ kbinput = KEY_NPAGE; if ((kbinput = wgetch(edit)) == 126) kbinput = KEY_NPAGE; /* Same thing here */ else { ungetch(kbinput); continue; } break; default: #ifdef DEBUG fprintf(stderr, _("I got Alt-[-%c! (%d)\n"), kbinput, kbinput); #endif break; } break; default: /* Check for the altkey defs.... */ for (i = 0; i <= MAIN_LIST_LEN - 1; i++) if (kbinput == main_list[i].altval || kbinput == main_list[i].altval - 32) { kbinput = main_list[i].val; break; } #ifdef DEBUG fprintf(stderr, _("I got Alt-%c! (%d)\n"), kbinput, kbinput); #endif break; } } /* Look through the main shortcut list to see if we've hit a shortcut key */ for (i = 0; i < MAIN_LIST_LEN; i++) { if (kbinput == main_list[i].val || (main_list[i].misc1 && kbinput == main_list[i].misc1) || (main_list[i].misc2 && kbinput == main_list[i].misc2)) { if (ISSET(VIEW_MODE) && !main_list[i].viewok) print_view_warning(); else main_list[i].func(); keyhandled = 1; } } /* Last gasp, stuff that's not in the main lists */ if (!keyhandled) switch (kbinput) { #ifndef NANO_SMALL #ifdef NCURSES_MOUSE_VERSION case KEY_MOUSE: do_mouse(); break; #endif #endif case 0: /* Erg */ do_next_word(); break; case 331: /* Stuff that we don't want to do squat */ case -1: case 410: /* Must ignore this, it gets sent when we resize */ break; default: #ifdef DEBUG fprintf(stderr, "I got %c (%d)!\n", kbinput, kbinput); #endif /* We no longer stop unhandled sequences so that people with odd character sets can type... */ if (ISSET(VIEW_MODE)) { print_view_warning(); break; } do_char(kbinput); } if (ISSET(CONSTUPDATE)) do_cursorpos(); reset_cursor(); wrefresh(edit); keyhandled = 0; } getchar(); finish(0); }