/**************************************************************************
 *   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 <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <errno.h>
#include <ctype.h>
#include <locale.h>
#include <limits.h>
#include <assert.h>

#include "config.h"
#include "proto.h"
#include "nano.h"

#ifndef NANO_SMALL
#include <libintl.h>
#define _(string) gettext(string)
#else
#define _(string) (string)
#endif

#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif

#ifdef HAVE_TERMIO_H
#include <termio.h>
#endif

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#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 */
static int editwineob = 0;	/* Last Line in edit buffer
				   (0 - editwineob) */
struct termios oldterm;		/* The user's original term settings */
static char *alt_speller;	/* Alternative spell command */
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) */
}

/* Thanks BG, many ppl have been asking for this... */
void *nmalloc(size_t howmuch)
{
    void *r;

    /* Panic save? */

    if (!(r = malloc(howmuch)))
	die(_("nano: malloc: out of memory!"));

    return r;
}

void *nrealloc(void *ptr, size_t howmuch)
{
    void *r;

    if (!(r = realloc(ptr, howmuch)))
	die("nano: realloc: out of memory!");

    return r;
}

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();
    editwineob = editwinrows - 1;
    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);
}

/* Load file into edit buffer - takes data from file struct */
void load_file(void)
{
    current = fileage;
    wmove(edit, current_y, current_x);
}

/* What happens when there is no file to open? aiee! */
void new_file(void)
{
    fileage = nmalloc(sizeof(filestruct));
    fileage->data = nmalloc(1);
    strcpy(fileage->data, "");
    fileage->prev = NULL;
    fileage->next = NULL;
    fileage->lineno = 1;
    filebot = fileage;
    edittop = fileage;
    editbot = fileage;
    current = fileage;
    totlines = 1;
    UNSET(VIEW_MODE);
}


int read_byte(int fd, char *filename, char *input)
{
    static char buf[BUFSIZ];
    static int index = 0;
    static int size = 0;

    if (index == size) {
	index = 0;
	size = read(fd, buf, BUFSIZ);
	if (size == -1) {
	    clear();
	    refresh();
	    resetty();
	    endwin();
	    perror(filename);
	}
	if (!size)
	    return 0;
    }
    *input = buf[index++];
    return 1;
}

filestruct *read_line(char *buf, filestruct * prev, int *line1ins)
{
    filestruct *fileptr;

    fileptr = nmalloc(sizeof(filestruct));
    fileptr->data = nmalloc(strlen(buf) + 2);
    strcpy(fileptr->data, buf);

    if (*line1ins) {
	/* Special case, insert with cursor on 1st line. */
	fileptr->prev = NULL;
	fileptr->next = fileage;
	fileptr->lineno = 1;
	*line1ins = 0;
	fileage = fileptr;
    } else if (fileage == NULL) {
	fileage = fileptr;
	fileage->lineno = 1;
	fileage->next = fileage->prev = NULL;
	fileptr = filebot = fileage;
    } else if (prev) {
	fileptr->prev = prev;
	fileptr->next = NULL;
	fileptr->lineno = prev->lineno + 1;
	prev->next = fileptr;
    } else {
	die(_("read_line: not on first line and prev is NULL"));
    }

    return fileptr;
}


int read_file(int fd, char *filename)
{
    long size, lines = 0, linetemp = 0;
    char input[2];		/* buffer */
    char *buf;
    long i = 0, bufx = 128;
    filestruct *fileptr = current, *tmp = NULL;
    int line1ins = 0;

    buf = nmalloc(bufx);

    if (fileptr != NULL && fileptr->prev != NULL) {
	fileptr = fileptr->prev;
	tmp = fileptr;
    } else if (fileptr != NULL && fileptr->prev == NULL) {
	tmp = fileage;
	current = fileage;
	line1ins = 1;
    }
    input[1] = 0;
    /* Read the entire file into file struct */
    while ((size = read_byte(fd, filename, input)) > 0) {
	linetemp = 0;
	if (input[0] == '\n') {
	    fileptr = read_line(buf, fileptr, &line1ins);
	    lines++;
	    buf[0] = 0;
	    i = 0;
	} else {
	    /* Now we allocate a bigger buffer 128 characters at a time.
	       If we allocate a lot of space for one line, we may indeed 
	       have to use a buffer this big later on, so we don't
	       decrease it at all.  We do free it at the end though. */

	    if (i >= bufx - 1) {
		buf = nrealloc(buf, bufx + 128);
		bufx += 128;
	    }
	    buf[i] = input[0];
	    buf[i + 1] = 0;
	    i++;
	}
	totsize += size;
    }

    /* Did we not get a newline but still have stuff to do? */
    if (buf[0]) {
	fileptr = read_line(buf, fileptr, &line1ins);
	lines++;
	buf[0] = 0;
    }
    /* Did we even GET a file? */
    if (totsize == 0) {
	new_file();
	statusbar(_("Read %d lines"), lines);
	return 1;
    }
    if (current != NULL) {
	fileptr->next = current;
	current->prev = fileptr;
	renumber(current);
	current_x = 0;
	placewewant = 0;
	edit_update(fileptr);
    } else if (fileptr->next == NULL) {
	filebot = fileptr;
	load_file();
    }
    statusbar(_("Read %d lines"), lines);
    totlines += lines;

    free(buf);
    close(fd);

    return 1;
}

/* Open the file (and decide if it exists) */
int open_file(char *filename, int insert, int quiet)
{
    int fd;
    struct stat fileinfo;

    if (!strcmp(filename, "") || stat(filename, &fileinfo) == -1) {
	if (insert) {
	    if (!quiet)
		statusbar(_("\"%s\" not found"), filename);
	    return -1;
	} else {
	    /* We have a new file */
	    statusbar(_("New File"));
	    new_file();
	}
    } else if ((fd = open(filename, O_RDONLY)) == -1) {
	if (!quiet)
	    statusbar("%s: %s", strerror(errno), filename);
	return -1;
    } else {			/* File is A-OK */
	if (S_ISDIR(fileinfo.st_mode)) {
	    statusbar(_("File \"%s\" is a directory"), filename);
	    new_file();
	    return -1;
	}
	if (!quiet)
	    statusbar(_("Reading File"));
	read_file(fd, filename);
    }

    return 1;
}

int do_insertfile(void)
{
    int i;

    wrap_reset();
    i = statusq(writefile_list, WRITEFILE_LIST_LEN, "",
		_("File to insert [from ./] "));
    if (i != -1) {

#ifdef DEBUG
	fprintf(stderr, "filename is %s", answer);
#endif

	i = open_file(answer, 1, 0);

	dump_buffer(fileage);
	set_modified();
	UNSET(KEEP_CUTBUFFER);
	display_main_list();
	return i;
    } else {
	statusbar(_("Cancelled"));
	UNSET(KEEP_CUTBUFFER);
	display_main_list();
	return 0;
    }
}

void usage(void)
{
#ifdef HAVE_GETOPT_LONG
    printf(_("Usage: nano [GNU long option] [option] +LINE <file>\n\n"));
    printf(_("Option		Long option		Meaning\n"));
    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 <file>\n\n"));
    printf(_("Option		Meaning\n"));
    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"));
}

void page_down_center(void)
{
    if (editbot->next != NULL && editbot->next != filebot) {
	edit_update(editbot->next);
	center_cursor();
    } else if (editbot != filebot) {
	edit_update(editbot);
	center_cursor();
    } else {
	while (current != filebot)
	    current = current->next;
	edit_update(current);
    }
    update_cursor();

}

int page_down(void)
{
    wrap_reset();
    current_x = 0;
    placewewant = 0;

    if (current == filebot)
	return 0;

    if (editbot != filebot) {
	current_y = 0;
	current = editbot;
    } else
	while (current != filebot) {
	    current = current->next;
	    current_y++;
	}

    edit_update_top(current);
    update_cursor();
    UNSET(KEEP_CUTBUFFER);
    check_statblank();
    return 1;
}

int do_home(void)
{
    current_x = 0;
    placewewant = 0;
    update_line(current, current_x);
    return 1;
}

int do_end(void)
{
    current_x = strlen(current->data);
    placewewant = xplustabs();
    update_line(current, current_x);

    return 1;
}

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");
}

/* What happens when we want to go past the bottom of the buffer */
int do_down(void)
{
    wrap_reset();
    if (current->next != NULL) {
	update_line(current->prev, 0);

	if (placewewant > 0)
	    current_x = actual_x(current->next, placewewant);

	if (current_x > strlen(current->next->data))
	    current_x = strlen(current->next->data);
    } else {
	UNSET(KEEP_CUTBUFFER);
	check_statblank();
	return 0;
    }
 
   if (current_y < editwineob && current != editbot)
	current_y++;
    else
	page_down_center();

    update_cursor();
    update_line(current->prev, 0);
    update_line(current, current_x);
    UNSET(KEEP_CUTBUFFER);
    check_statblank();
    return 1;
}

void page_up_center(void)
{
    if (edittop != fileage) {
	edit_update(edittop);
	center_cursor();
    } else
	current_y = 0;

    update_cursor();

}

int do_up(void)
{
    wrap_reset();
    if (current->prev != NULL) {
	update_line(current, 0);

	if (placewewant > 0)
	    current_x = actual_x(current->prev, placewewant);

	if (current_x > strlen(current->prev->data))
	    current_x = strlen(current->prev->data);
    }
    if (current_y > 0)
	current_y--;
    else
	page_up_center();

    update_cursor();
    update_line(current->next, 0);
    update_line(current, current_x);
    UNSET(KEEP_CUTBUFFER);
    check_statblank();
    return 1;
}

int do_right(void)
{
    if (current_x < strlen(current->data)) {
	current_x++;
    } else {
	if (do_down())
	    current_x = 0;
    }

    placewewant = xplustabs();
    update_cursor();
    update_line(current, current_x);
    UNSET(KEEP_CUTBUFFER);
    check_statblank();
    return 1;
}

int do_left(void)
{
    if (current_x > 0)
	current_x--;
    else if (current != fileage) {
	placewewant = 0;
	current_x = strlen(current->prev->data);
	do_up();
    }
    placewewant = xplustabs();

    update_cursor();
    update_line(current, current_x);
    UNSET(KEEP_CUTBUFFER);
    check_statblank();
    return 1;
}

/* 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(&current->data[current_x + 1],
	    &current->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 = &current->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(&current->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; 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 % 8 != 0);
                i_tabs += 8 - (i_tabs % 8);
        } 

	if (current_word_end_t >= fill)
	    break;
    }

    assert (current_word_end_t >= fill);

    /* There are 4 cases of what the line could look like.
     * 1) only one word on the line before wrap point.
     *    a) cursor is on word or before word at wrap point.  
     *         - 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 before wrap point
     *         - word at wrap point starts a new line.
     *         - white space at end of original line is cleared.
     *    b) cursor is at the word at the wrap point.
     *         - word at wrap point starts a new line.
     *         1. pressed a space.
     *            - white space on original line is kept to where cursor was.
     *         2. pressed non space.
     *            - 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.
     *         1. pressed a space.
     *            - white space on original line is kept to where wrap point was.
     *         2. pressed a non space.
     *            - white space at end of original line is cleared
     */

    temp = nmalloc (sizeof (filestruct));

    /* Category 1a: one word on 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;
    }

    /* 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]);

	    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;
        }


	/* 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;

		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;

	    if (isspace(input_char)) {

		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;
	    }
        }
    }

	/* We pre-pend wrapped part to next line. */
    if (ISSET(SAMELINEWRAP)) {
	    /* 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);

	strcpy (p, temp->data);
	strcat (p, " ");
	strcat (p, inptr->next->data);

	free (inptr->next->data);
	inptr->next->data = p;

	free (temp->data);
	free (temp);
    }
	/* 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(current);

    /* 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(current);
    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. */
	/* First check to see if we typed space and passed a word. */
	if (isspace(ch) && !isspace(inptr->data[i - 1]))
	    do_wrap(inptr, ch);
	else {

	    while (isspace(inptr->data[i]) && inptr->data[i])
		i++;
	
	    if (!inptr->data[i])
		return;

	    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();
}

/* Set up the system variables for a search or replace.  Returns -1 on
   abort, 0 on success, and 1 on rerun calling program 
   Return -2 to run opposite program (searchg -> replace, replace -> search)

   replacing = 1 if we call from do_replace, 0 if called from do_search func.
*/
int search_init(int replacing)
{
    int i;
    char buf[135];

    if (last_search[0]) {
	sprintf(buf, " [%s]", last_search);
    } else {
	buf[0] = '\0';
    }

    i = statusq(replacing ? replace_list : whereis_list,
		replacing ? REPLACE_LIST_LEN : WHEREIS_LIST_LEN, "",
		ISSET(CASE_SENSITIVE) ? _("Case Sensitive Search%s") :
		_("Search%s"), buf);

    /* Cancel any search, or just return with no previous search */
    if ((i == -1) || (i < 0 && !last_search[0])) {
	statusbar(_("Search Cancelled"));
	reset_cursor();
	return -1;
    } else if (i == -2) {	/* Same string */
	strncpy(answer, last_search, 132);
    } else if (i == 0) {	/* They entered something new */
	strncpy(last_search, answer, 132);

	/* Blow away last_replace because they entered a new search
	   string....uh, right? =) */
	last_replace[0] = '\0';
    } else if (i == NANO_CASE_KEY) {	/* They want it case sensitive */
	if (ISSET(CASE_SENSITIVE))
	    UNSET(CASE_SENSITIVE);
	else
	    SET(CASE_SENSITIVE);

	return 1;
    } else if (i == NANO_OTHERSEARCH_KEY) {
	return -2;		/* Call the opposite search function */
    } else {			/* First line key, etc. */
	do_early_abort();
	return -3;
    }

    return 0;
}

filestruct *findnextstr(int quiet, filestruct * begin, char *needle)
{
    filestruct *fileptr;
    char *searchstr, *found = NULL, *tmp;

    fileptr = current;

    searchstr = &current->data[current_x + 1];
    /* Look for searchstr until EOF */
    while (fileptr != NULL &&
	   (found = strstrwrapper(searchstr, needle)) == NULL) {
	fileptr = fileptr->next;

	if (fileptr == begin)
	    return NULL;

	if (fileptr != NULL)
	    searchstr = fileptr->data;
    }

    /* If we're not at EOF, we found an instance */
    if (fileptr != NULL) {
	current = fileptr;
	current_x = 0;
	for (tmp = fileptr->data; tmp != found; tmp++)
	    current_x++;

	edit_update(current);
	reset_cursor();
    } else {			/* We're at EOF, go back to the top, once */

	fileptr = fileage;

	while (fileptr != current && fileptr != begin &&
	       (found = strstrwrapper(fileptr->data, needle)) == NULL)
	    fileptr = fileptr->next;

	if (fileptr == begin) {
	    if (!quiet)
		statusbar(_("\"%s\" not found"), needle);

	    return NULL;
	}
	if (fileptr != current) {	/* We found something */
	    current = fileptr;
	    current_x = 0;
	    for (tmp = fileptr->data; tmp != found; tmp++)
		current_x++;

	    edit_update(current);
	    reset_cursor();

	    if (!quiet)
		statusbar(_("Search Wrapped"));
	} else {		/* Nada */

	    if (!quiet)
		statusbar(_("\"%s\" not found"), needle);
	    return NULL;
	}
    }

    return fileptr;
}

void search_abort(void)
{
    UNSET(KEEP_CUTBUFFER);
    display_main_list();
    wrefresh(bottomwin);
}

/* Search for a string */
int do_search(void)
{
    int i;
    filestruct *fileptr = current;

    wrap_reset();
    if ((i = search_init(0)) == -1) {
	current = fileptr;
	search_abort();
	return 0;
    } else if (i == -3) {
	search_abort();
	return 0;
    } else if (i == -2) {
	search_abort();
	do_replace();
	return 0;
    } else if (i == 1) {
	do_search();
	search_abort();
	return 1;
    }
    findnextstr(0, current, answer);
    search_abort();
    return 1;
}

void print_replaced(int num)
{
    if (num > 1)
	statusbar(_("Replaced %d occurences"), num);
    else if (num == 1)
	statusbar(_("Replaced 1 occurence"));
}

void replace_abort(void)
{
    UNSET(KEEP_CUTBUFFER);
    display_main_list();
    reset_cursor();
}

/* Search for a string */
int do_replace(void)
{
    int i, replaceall = 0, numreplaced = 0, beginx;
    filestruct *fileptr, *begin;
    char *tmp, *copy, prevanswer[132] = "";

    if ((i = search_init(1)) == -1) {
	statusbar(_("Replace Cancelled"));
	replace_abort();
	return 0;
    } else if (i == 1) {
	do_replace();
	return 1;
    } else if (i == -2) {
	replace_abort();
	do_search();
	return 0;
    } else if (i == -3) {
	replace_abort();
	return 0;
    }
    strncpy(prevanswer, answer, 132);

    if (strcmp(last_replace, "")) {	/* There's a previous replace str */
	i = statusq(replace_list, REPLACE_LIST_LEN, "",
		    _("Replace with [%s]"), last_replace);

	if (i == -1) {		/* Aborted enter */
	    strncpy(answer, last_replace, 132);
	    statusbar(_("Replace Cancelled"));
	    replace_abort();
	    return 0;
	} else if (i == 0)	/* They actually entered something */
	    strncpy(last_replace, answer, 132);
	else if (i == NANO_CASE_KEY) {	/* They asked for case sensitivity */
	    if (ISSET(CASE_SENSITIVE))
		UNSET(CASE_SENSITIVE);
	    else
		SET(CASE_SENSITIVE);

	    do_replace();
	    return 0;
	} else {		/* First page, last page, for example could get here */

	    do_early_abort();
	    replace_abort();
	    return 0;
	}
    } else {			/* last_search is empty */

	i = statusq(replace_list, REPLACE_LIST_LEN, "", _("Replace with"));
	if (i == -1) {
	    statusbar(_("Replace Cancelled"));
	    reset_cursor();
	    replace_abort();
	    return 0;
	} else if (i == 0)	/* They entered something new */
	    strncpy(last_replace, answer, 132);
	else if (i == NANO_CASE_KEY) {	/* They want it case sensitive */
	    if (ISSET(CASE_SENSITIVE))
		UNSET(CASE_SENSITIVE);
	    else
		SET(CASE_SENSITIVE);

	    do_replace();
	    return 1;
	} else {		/* First line key, etc. */

	    do_early_abort();
	    replace_abort();
	    return 0;
	}
    }

    /* save where we are */
    begin = current;
    beginx = current_x;

    while (1) {

	if (replaceall)
	    fileptr = findnextstr(1, begin, prevanswer);
	else
	    fileptr = findnextstr(0, begin, prevanswer);

	/* No more matches.  Done! */
	if (!fileptr)
	    break;

	/* If we're here, we've found the search string */
	if (!replaceall)
	    i = do_yesno(1, 1, _("Replace this instance?"));

	if (i > 0 || replaceall) {	/* Yes, replace it!!!! */
	    if (i == 2)
		replaceall = 1;

	    /* Create buffer */
	    copy = nmalloc(strlen(current->data) - strlen(last_search) +
			   strlen(last_replace) + 1);

	    /* Head of Original Line */
	    strncpy(copy, current->data, current_x);
	    copy[current_x] = 0;

	    /* Replacement Text */
	    strcat(copy, last_replace);

	    /* The tail of the original line */
	    /*  This may expose other bugs, because it no longer
	       goes through each character on the string
	       and tests for string goodness.  But because
	       we can assume the invariant that current->data
	       is less than current_x + strlen(last_search) long,   
	       this should be safe.  Or it will expose bugs ;-) */
	    tmp = current->data + current_x + strlen(last_search);
	    strcat(copy, tmp);

	    /* Cleanup */
	    free(current->data);
	    current->data = copy;

	    /* Stop bug where we replace a substring of the replacement text */
	    current_x += strlen(last_replace);

	    edit_refresh();
	    set_modified();
	    numreplaced++;
	} else if (i == -1)	/* Abort, else do nothing and continue loop */
	    break;
    }

    current = begin;
    current_x = beginx;
    renumber_all();
    edit_update(current);
    print_replaced(numreplaced);
    replace_abort();
    return 1;
}


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(&current->data[current_x - 1], &current->data[current_x],
		strlen(current->data) - current_x + 1);
#ifdef DEBUG
	fprintf(stderr, _("current->data now = \"%s\"\n"), current->data);
#endif
	align(&current->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(&current->data[current_x], &current->data[current_x + 1],
		strlen(current->data) - current_x);

	align(&current->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 goto_abort(void)
{
    UNSET(KEEP_CUTBUFFER);
    display_main_list();
}

int do_gotoline(long defline)
{
    long line, i = 1, j = 0;
    filestruct *fileptr;

    if (defline > 0)		/* We already know what line we want to go to */
	line = defline;
    else {			/* Ask for it */

	j = statusq(goto_list, GOTO_LIST_LEN, "", _("Enter line number"));
	if (j == -1) {
	    statusbar(_("Aborted"));
	    goto_abort();
	    return 0;
	} else if (j != 0) {
	    do_early_abort();
	    goto_abort();
	    return 0;
	}
	if (!strcmp(answer, "$")) {
	    current = filebot;
	    current_x = 0;
	    edit_update(current);
	    goto_abort();
	    return 1;
	}
	line = atoi(answer);
    }

    /* Bounds check */
    if (line <= 0) {
	statusbar(_("Come on, be reasonable"));
	goto_abort();
	return 0;
    }
    if (line > totlines) {
	statusbar(_("Only %d lines available, skipping to last line"),
		  filebot->lineno);
	current = filebot;
	current_x = 0;
	edit_update(current);
    } else {
	for (fileptr = fileage; fileptr != NULL && i < line; i++)
	    fileptr = fileptr->next;

	current = fileptr;
	current_x = 0;
	edit_update(current);
    }

    goto_abort();
    return 1;
}

int do_gotoline_void(void)
{
    return do_gotoline(0);
}

void wrap_reset(void)
{
    UNSET(SAMELINEWRAP);
}

/*
 * Write a file out.  If tmp is nonzero, we set the umask to 0600,
 * we don't set the global variable filename to it's name, and don't
 * print out how many lines we wrote on the statusbar.
 * 
 * Note that tmp is only set to 1 for storing temporary files internal
 * to the editor, and is completely different from temp_opt.
 */
int write_file(char *name, int tmp)
{
    long size, lineswritten = 0;
    char buf[PATH_MAX + 1];
    filestruct *fileptr;
    int fd, mask = 0;
    struct stat st;

    if (!strcmp(name, "")) {
	statusbar(_("Cancelled"));
	return -1;
    }
    titlebar();
    fileptr = fileage;


    /* Open the file and truncate it.  Trust the symlink. */
    if (ISSET(FOLLOW_SYMLINKS) && !tmp) {
	if ((fd = open(name, O_WRONLY | O_CREAT | O_TRUNC,
		       S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH |
		       S_IWOTH)) == -1) {
	    statusbar(_("Could not open file for writing: %s"),
		      strerror(errno));
	    return -1;
	}
    }
    /* Don't follow symlink.  Create new file. */
    else {
	if (strlen(name) > (PATH_MAX - 7)) {
	    statusbar(_("Could not open file: Path length exceeded."));
	    return -1;
	}

	memset(buf, 0x00, PATH_MAX + 1);
	strcat(buf, name);
	strcat(buf, ".XXXXXX");
	if ((fd = mkstemp(buf)) == -1) {
	    statusbar(_("Could not open file for writing: %s"),
		      strerror(errno));
	    return -1;
	}
    }



    dump_buffer(fileage);
    while (fileptr != NULL && fileptr->next != NULL) {
	size = write(fd, fileptr->data, strlen(fileptr->data));
	if (size == -1) {
	    statusbar(_("Could not open file for writing: %s"),
		      strerror(errno));
	    return -1;
	} else {
#ifdef DEBUG
	    fprintf(stderr, _("Wrote >%s\n"), fileptr->data);
#endif
	}
	write(fd, "\n", 1);

	fileptr = fileptr->next;
	lineswritten++;
    }

    if (fileptr != NULL) {
	size = write(fd, fileptr->data, strlen(fileptr->data));
	if (size == -1) {
	    statusbar(_("Could not open file for writing: %s"),
		      strerror(errno));
	    return -1;
	} else if (size > 0) {
	    size = write(fd, "\n", 1);
	    if (size == -1) {
		statusbar(_("Could not open file for writing: %s"),
			  strerror(errno));
		return -1;
	    }
	}
    }


    if (close(fd) == -1) {
	statusbar(_("Could not close %s: %s"), name, strerror(errno));
	unlink(buf);
	return -1;
    }

    if (!ISSET(FOLLOW_SYMLINKS) || tmp) {
	if (stat(name, &st) == -1) {
	    /* Use default umask as file permisions if file is a new file. */
	    mask = umask(0);
	    umask(mask);

	    if (tmp)		/* We don't want anyone reading our temporary file! */
		mask = 0600;
	    else
		mask = 0666 & ~mask;

	} else {
	    /* Use permissions from file we are overwriting. */
	    mask = st.st_mode;
	    if (unlink(name) == -1) {
		if (errno != ENOENT) {
		    statusbar(_("Could not open %s for writing: %s"),
			      name, strerror(errno));
		    unlink(buf);
		    return -1;
		}
	    }
	}

	if (link(buf, name) != -1)
	    unlink(buf);
	else if (errno != EPERM) {
	    statusbar(_("Could not open %s for writing: %s"),
		      name, strerror(errno));
	    unlink(buf);
	    return -1;
	} else if (rename(buf, name) == -1) {	/* Try a rename?? */
	    statusbar(_("Could not open %s for writing: %s"),
		      name, strerror(errno));
	    unlink(buf);
	    return -1;
	}
	if (chmod(name, mask) == -1) {
	    statusbar(_("Could not set permissions %o on %s: %s"),
		      mask, name, strerror(errno));
	}

    }
    if (!tmp) {
	strncpy(filename, name, 132);
	statusbar(_("Wrote %d lines"), lineswritten);
    }
    UNSET(MODIFIED);
    titlebar();
    return 1;
}

int do_writeout(int exiting)
{
    int i = 0;

    strncpy(answer, filename, 132);

    if ((exiting) && (temp_opt) && (filename)) {
	i = write_file(answer, 0);
	display_main_list();
	return i;
    }

    while (1) {
	i = statusq(writefile_list, WRITEFILE_LIST_LEN, answer,
	            _("File Name to write"));

        if (i != -1) {

#ifdef DEBUG
	    fprintf(stderr, _("filename is %s"), answer);
#endif
	    if (strncmp(answer, filename, 132)) {
		struct stat st;
		if (!stat(answer, &st)) {
		    i = do_yesno(0, 0, _("File exists, OVERWRITE ?"));

		    if (!i || (i == -1))
			continue;
                }
            }
	    i = write_file(answer, 0);

	    display_main_list();
	    return i;
        } else {
	    statusbar(_("Cancelled"));
	    display_main_list();
	    return 0;
        }
    }
}

int do_writeout_void(void)
{
    return do_writeout(0);
}

/* 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_oldspell(void)
{
    char *temp, *foo;
    int i;

    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) {
	foo = nmalloc(strlen(temp) + strlen(alt_speller) + 2);
	sprintf(foo, "%s %s", alt_speller, temp);
    } else {

	/* For now, we only try ispell because we're not capable of
	   handling the normal spell program (yet...) */
	foo = nmalloc(strlen(temp) + 8);
	sprintf(foo, "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;
}

int do_spell(void)
{
    char *temp, *foo;
    int i;

    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) {
	foo = nmalloc(strlen(temp) + strlen(alt_speller) + 2);
	sprintf(foo, "%s %s", alt_speller, temp);
    } else {

	/* For now, we only try ispell because we're not capable of
	   handling the normal spell program (yet...) */
	foo = nmalloc(strlen(temp) + 8);
	sprintf(foo, "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;
}

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();
    editwineob = editwinrows - 1;
    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 <= editwineob) && (editbot->next != NULL)
	 && (editbot->next != filebot); i++)
	editbot = editbot->next;

    if (current_y > editwineob) {
	edit_update(editbot);
    }
    erase();
    refresh();
    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 + 1))
	   && !no_spaces(current->data)) {
	int i = 0;
	int len2 = 0;
	filestruct *tmpline = nmalloc(sizeof(filestruct));

	/* Start at fill + 2, unless line isn't that long (but it appears at least
	 * fill + 2 long with tabs.
	 */
	if (slen > (fill + 2))
	    i = fill + 2;
	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++;
    }

    renumber(initial);

    if (current->next)
	current = current->next;
    current_x = 0;
    placewewant = 0;

    if ((current_y < 0) || (current_y >= editwineob) || (initial_y <= 0)) {
	edit_update(current);
	center_cursor();
    } else {
	int i = 0;

	editbot = edittop;
	for (i = 0; (i <= editwineob) && (editbot->next != NULL)
	     && (editbot->next != filebot); i++)
	    editbot = editbot->next;
	edit_refresh();
    }

    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 = sprintf(buf, "^%c	", main_list[i].val + 64);

	if (main_list[i].misc1 > KEY_F0 && main_list[i].misc1 <= KEY_F(64))
	    sofar += sprintf(&buf[sofar], "(F%d)	",
			     main_list[i].misc1 - KEY_F0);
	else
	    sofar += sprintf(&buf[sofar], "	");

	if (main_list[i].altval > 0)
	    sofar += sprintf(&buf[sofar], "(@%c)	",
			     main_list[i].altval - 32);
	else
	    sofar += sprintf(&buf[sofar], "	");

	if (main_list[i].help != NULL)
	    sprintf(&buf[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;
    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'},
	{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, "?Vchilmpr:s:tvwxz",
				 long_options, &option_index)) != EOF) {
#else
    while ((optchr = getopt(argc, argv, "h?Vcilmpr:s:tvwxz")) != EOF) {
#endif

	switch (optchr) {
	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();

    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);

}