mirror of git://git.sv.gnu.org/nano.git
2803 lines
62 KiB
C
2803 lines
62 KiB
C
/**************************************************************************
|
|
* 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(¤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();
|
|
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 = ¤t->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(¤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 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);
|
|
|
|
}
|