3448 lines
82 KiB
C
3448 lines
82 KiB
C
/* $Id$ */
|
|
/**************************************************************************
|
|
* nano.c *
|
|
* *
|
|
* Copyright (C) 1999-2002 Chris Allegretta *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2, or (at your option) *
|
|
* any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License *
|
|
* along with this program; if not, write to the Free Software *
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
|
|
* *
|
|
**************************************************************************/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <signal.h>
|
|
#include <setjmp.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/param.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <errno.h>
|
|
#include <ctype.h>
|
|
#include <locale.h>
|
|
#include <limits.h>
|
|
#include <assert.h>
|
|
#include "proto.h"
|
|
#include "nano.h"
|
|
|
|
#ifdef ENABLE_NLS
|
|
#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
|
|
|
|
#ifndef DISABLE_WRAPJUSTIFY
|
|
/* Former globals, now static */
|
|
int fill = 0;/* Fill - where to wrap lines, basically */
|
|
int wrap_at = 0;/* Right justified fill value, allows resize */
|
|
#endif
|
|
|
|
struct termios oldterm; /* The user's original term settings */
|
|
static struct sigaction act; /* For all our fun signal handlers */
|
|
|
|
char *last_search = NULL; /* Last string we searched for */
|
|
char *last_replace = NULL; /* Last replacement string */
|
|
int search_last_line; /* Is this the last search line? */
|
|
|
|
static sigjmp_buf jmpbuf; /* Used to return to mainloop after SIGWINCH */
|
|
|
|
/* What we do when we're all set to exit */
|
|
RETSIGTYPE finish(int sigage)
|
|
{
|
|
|
|
keypad(edit, TRUE);
|
|
keypad(bottomwin, TRUE);
|
|
|
|
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);
|
|
|
|
#ifdef DEBUG
|
|
thanks_for_all_the_fish();
|
|
#endif
|
|
|
|
exit(sigage);
|
|
}
|
|
|
|
/* Die (gracefully?) */
|
|
void die(char *msg, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, msg);
|
|
vfprintf(stderr, msg, ap);
|
|
va_end(ap);
|
|
|
|
/* Restore the old term settings */
|
|
tcsetattr(0, TCSANOW, &oldterm);
|
|
|
|
clear();
|
|
refresh();
|
|
resetty();
|
|
endwin();
|
|
|
|
fprintf(stderr, msg);
|
|
|
|
/* save the currently loaded file if it's been modified */
|
|
if (ISSET(MODIFIED))
|
|
die_save_file(filename);
|
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
|
/* then save all of the other modified loaded files, if any */
|
|
if (open_files) {
|
|
openfilestruct *tmp;
|
|
|
|
tmp = open_files;
|
|
|
|
while (open_files->prev)
|
|
open_files = open_files->prev;
|
|
|
|
while (open_files->next) {
|
|
|
|
/* if we already saved the file above (i. e. if it was the
|
|
currently loaded file), don't save it again */
|
|
if (tmp != open_files) {
|
|
/* make sure open_files->fileage and fileage, and
|
|
open_files->filebot and filebot, are in sync; they
|
|
might not be if lines have been cut from the top or
|
|
bottom of the file */
|
|
fileage = open_files->fileage;
|
|
filebot = open_files->filebot;
|
|
/* save the file if it's been modified */
|
|
if (open_files->file_flags & MODIFIED)
|
|
die_save_file(open_files->filename);
|
|
}
|
|
|
|
open_files = open_files->next;
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
exit(1); /* We have a problem: exit w/ errorlevel(1) */
|
|
}
|
|
|
|
void die_save_file(char *die_filename)
|
|
{
|
|
char *name, *ret;
|
|
int i = -1;
|
|
|
|
/* if we can't save we have REAL bad problems,
|
|
* but we might as well TRY. */
|
|
if (die_filename[0] == '\0') {
|
|
name = "nano.save";
|
|
ret = get_next_filename(name);
|
|
if (ret[0] != '\0')
|
|
i = write_file(ret, 1, 0, 0);
|
|
name = ret;
|
|
}
|
|
else {
|
|
char *buf = charalloc(strlen(die_filename) + 6);
|
|
strcpy(buf, die_filename);
|
|
strcat(buf, ".save");
|
|
ret = get_next_filename(buf);
|
|
if (ret[0] != '\0')
|
|
i = write_file(ret, 1, 0, 0);
|
|
name = ret;
|
|
free(buf);
|
|
}
|
|
|
|
if (i != -1)
|
|
fprintf(stderr, _("\nBuffer written to %s\n"), name);
|
|
else
|
|
fprintf(stderr, _("\nNo %s written (too many backup files?)\n"), name);
|
|
|
|
free(ret);
|
|
}
|
|
|
|
/* Die with an error message that the screen was too small if, well, the
|
|
screen is too small */
|
|
void die_too_small(void)
|
|
{
|
|
char *too_small_msg = _("Window size is too small for Nano...");
|
|
|
|
die(too_small_msg);
|
|
|
|
}
|
|
|
|
void print_view_warning(void)
|
|
{
|
|
statusbar(_("Key illegal in VIEW mode"));
|
|
}
|
|
|
|
/* Initialize global variables - no better way for now. If
|
|
save_cutbuffer is nonzero, don't set cutbuffer to NULL. */
|
|
void global_init(int save_cutbuffer)
|
|
{
|
|
current_x = 0;
|
|
current_y = 0;
|
|
|
|
if ((editwinrows = LINES - 5 + no_help()) < MIN_EDITOR_ROWS)
|
|
die_too_small();
|
|
|
|
fileage = NULL;
|
|
if (!save_cutbuffer)
|
|
cutbuffer = NULL;
|
|
current = NULL;
|
|
edittop = NULL;
|
|
editbot = NULL;
|
|
totlines = 0;
|
|
totsize = 0;
|
|
placewewant = 0;
|
|
|
|
#ifndef DISABLE_WRAPJUSTIFY
|
|
if (wrap_at)
|
|
fill = COLS + wrap_at;
|
|
else if (!fill)
|
|
fill = COLS - CHARS_FROM_EOL;
|
|
|
|
if (fill < MIN_FILL_LENGTH)
|
|
die_too_small();
|
|
#endif
|
|
|
|
hblank = charalloc(COLS + 1);
|
|
memset(hblank, ' ', COLS);
|
|
hblank[COLS] = '\0';
|
|
|
|
}
|
|
|
|
/* 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 = charalloc(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 filestruct. */
|
|
void unlink_node(filestruct * fileptr)
|
|
{
|
|
if (fileptr->prev != NULL)
|
|
fileptr->prev->next = fileptr->next;
|
|
|
|
if (fileptr->next != NULL)
|
|
fileptr->next->prev = fileptr->prev;
|
|
}
|
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
|
/* Unlink a node from the rest of the openfilestruct. */
|
|
void unlink_opennode(openfilestruct * fileptr)
|
|
{
|
|
if (fileptr->prev != NULL)
|
|
fileptr->prev->next = fileptr->next;
|
|
|
|
if (fileptr->next != NULL)
|
|
fileptr->next->prev = fileptr->prev;
|
|
}
|
|
#endif
|
|
|
|
/* Delete a node from the filestruct. */
|
|
void delete_node(filestruct * fileptr)
|
|
{
|
|
if (fileptr == NULL)
|
|
return;
|
|
|
|
if (fileptr->data != NULL)
|
|
free(fileptr->data);
|
|
free(fileptr);
|
|
}
|
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
|
/* Delete a node from the openfilestruct. */
|
|
void delete_opennode(openfilestruct * fileptr)
|
|
{
|
|
if (fileptr == NULL)
|
|
return;
|
|
|
|
if (fileptr->filename != NULL)
|
|
free(fileptr->filename);
|
|
if (fileptr->fileage != NULL)
|
|
free_filestruct(fileptr->fileage);
|
|
free(fileptr);
|
|
}
|
|
#endif
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* Frees a filestruct. */
|
|
int free_filestruct(filestruct * src)
|
|
{
|
|
filestruct *fileptr = src;
|
|
|
|
if (src == NULL)
|
|
return 0;
|
|
|
|
while (fileptr->next != NULL) {
|
|
fileptr = fileptr->next;
|
|
delete_node(fileptr->prev);
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, _("delete_node(): free'd a node, YAY!\n"));
|
|
#endif
|
|
}
|
|
delete_node(fileptr);
|
|
#ifdef DEBUG
|
|
fprintf(stderr, _("delete_node(): free'd last node.\n"));
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
|
/* Frees an openfilestruct. */
|
|
int free_openfilestruct(openfilestruct * src)
|
|
{
|
|
openfilestruct *fileptr = src;
|
|
|
|
if (src == NULL)
|
|
return 0;
|
|
|
|
while (fileptr->next != NULL) {
|
|
fileptr = fileptr->next;
|
|
delete_opennode(fileptr->prev);
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, _("delete_opennode(): free'd a node, YAY!\n"));
|
|
#endif
|
|
}
|
|
delete_opennode(fileptr);
|
|
#ifdef DEBUG
|
|
fprintf(stderr, _("delete_opennode(): free'd last node.\n"));
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
int renumber_all(void)
|
|
{
|
|
filestruct *temp;
|
|
int i = 1;
|
|
|
|
assert(fileage==NULL || fileage!=fileage->next);
|
|
for (temp = fileage; temp != NULL; temp = temp->next) {
|
|
temp->lineno = i++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int renumber(filestruct * fileptr)
|
|
{
|
|
filestruct *temp;
|
|
|
|
if (fileptr == NULL || fileptr->prev == NULL || fileptr == fileage) {
|
|
renumber_all();
|
|
return 0;
|
|
}
|
|
assert(fileptr==NULL || fileptr!=fileptr->next);
|
|
for (temp = fileptr; temp != NULL; temp = temp->next) {
|
|
if (temp->prev != NULL)
|
|
temp->lineno = temp->prev->lineno + 1;
|
|
else
|
|
temp->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);
|
|
}
|
|
|
|
/* Null a string at a certain index and align it */
|
|
void null_at(char **data, int index)
|
|
{
|
|
|
|
/* Ahh! Damn dereferencing */
|
|
(*data)[index] = '\0';
|
|
align(data);
|
|
}
|
|
|
|
|
|
/* Print one usage string to the screen, removes lots of duplicate
|
|
strings to translate and takes out the parts that shouldn't be
|
|
translatable (the flag names) */
|
|
void print1opt(char *shortflag, char *longflag, char *desc)
|
|
{
|
|
printf(" %s\t", shortflag);
|
|
if (strlen(shortflag) < 8)
|
|
printf("\t");
|
|
|
|
#ifdef HAVE_GETOPT_LONG
|
|
printf("%s\t", longflag);
|
|
if (strlen(longflag) < 8)
|
|
printf("\t\t");
|
|
else if (strlen(longflag) < 16)
|
|
printf("\t");
|
|
#endif
|
|
|
|
printf("%s\n", desc);
|
|
}
|
|
|
|
void usage(void)
|
|
{
|
|
#ifdef HAVE_GETOPT_LONG
|
|
printf(_("Usage: nano [GNU long option] [option] +LINE <file>\n\n"));
|
|
printf(_("Option Long option Meaning\n"));
|
|
#else
|
|
printf(_("Usage: nano [option] +LINE <file>\n\n"));
|
|
printf(_("Option Meaning\n"));
|
|
#endif /* HAVE_GETOPT_LONG */
|
|
|
|
print1opt("-h, -?", "--help", _("Show this message"));
|
|
#ifndef NANO_SMALL
|
|
print1opt("-B", "--backup", _("Backup existing files on save"));
|
|
print1opt("-D", "--dos", _("Write file in DOS format"));
|
|
#endif
|
|
#ifdef ENABLE_MULTIBUFFER
|
|
print1opt("-F", "--multibuffer", _("Enable multiple file buffers"));
|
|
#endif
|
|
print1opt("-K", "--keypad", _("Use alternate keypad routines"));
|
|
#ifndef NANO_SMALL
|
|
print1opt("-M", "--mac", _("Write file in Mac format"));
|
|
print1opt("-N", "--noconvert", _("Don't convert files from DOS/Mac format"));
|
|
#endif
|
|
#ifndef DISABLE_JUSTIFY
|
|
print1opt(_("-Q [str]"), _("--quotestr [str]"), _("Quoting string, default \"> \""));
|
|
#endif
|
|
#ifdef HAVE_REGEX_H
|
|
print1opt("-R", "--regexp", _("Do regular expression searches"));
|
|
#endif
|
|
#ifndef NANO_SMALL
|
|
print1opt("-S", "--smooth", _("Smooth scrolling"));
|
|
#endif
|
|
print1opt(_("-T [num]"), _("--tabsize=[num]"), _("Set width of a tab to num"));
|
|
print1opt("-V", "--version", _("Print version information and exit"));
|
|
#ifdef ENABLE_COLOR
|
|
print1opt(_("-Y [str]"), _("--syntax [str]"), _("Syntax definition to use"));
|
|
#endif
|
|
print1opt("-c", "--const", _("Constantly show cursor position"));
|
|
#ifndef NANO_SMALL
|
|
print1opt("-i", "--autoindent", _("Automatically indent new lines"));
|
|
print1opt("-k", "--cut", _("Let ^K cut from cursor to end of line"));
|
|
#endif
|
|
print1opt("-l", "--nofollow", _("Don't follow symbolic links, overwrite"));
|
|
#ifndef DISABLE_MOUSE
|
|
#ifdef NCURSES_MOUSE_VERSION
|
|
print1opt("-m", "--mouse", _("Enable mouse"));
|
|
#endif
|
|
#endif
|
|
#ifndef DISABLE_OPERATINGDIR
|
|
print1opt(_("-o [dir]"), _("--operatingdir=[dir]"), _("Set operating directory"));
|
|
#endif
|
|
print1opt("-p", "--pico", _("Emulate Pico as closely as possible"));
|
|
#ifndef DISABLE_WRAPJUSTIFY
|
|
print1opt(_("-r [#cols]"), _("--fill=[#cols]"), _("Set fill cols to (wrap lines at) #cols"));
|
|
#endif
|
|
#ifndef DISABLE_SPELLER
|
|
print1opt(_("-s [prog]"), _("--speller=[prog]"), _("Enable alternate speller"));
|
|
#endif
|
|
print1opt("-t", "--tempfile", _("Auto save on exit, don't prompt"));
|
|
print1opt("-v", "--view", _("View (read only) mode"));
|
|
#ifndef DISABLE_WRAPPING
|
|
print1opt("-w", "--nowrap", _("Don't wrap long lines"));
|
|
#endif
|
|
print1opt("-x", "--nohelp", _("Don't show help window"));
|
|
print1opt("-z", "--suspend", _("Enable suspend"));
|
|
print1opt(_("+LINE"), "", _("Start at line number LINE"));
|
|
|
|
exit(0);
|
|
}
|
|
|
|
void version(void)
|
|
{
|
|
printf(_(" GNU nano version %s (compiled %s, %s)\n"),
|
|
VERSION, __TIME__, __DATE__);
|
|
printf(_
|
|
(" Email: nano@nano-editor.org Web: http://www.nano-editor.org"));
|
|
printf(_("\n Compiled options:"));
|
|
|
|
#ifdef NANO_EXTRA
|
|
printf(" --enable-extra");
|
|
#endif
|
|
#ifdef ENABLE_MULTIBUFFER
|
|
printf(" --enable-multibuffer");
|
|
#endif
|
|
#ifdef ENABLE_NANORC
|
|
printf(" --enable-nanorc");
|
|
#endif
|
|
#ifdef ENABLE_COLOR
|
|
printf(" --enable-color");
|
|
#endif
|
|
|
|
#ifdef NANO_SMALL
|
|
printf(" --enable-tiny");
|
|
#else
|
|
#ifdef DISABLE_BROWSER
|
|
printf(" --disable-browser");
|
|
#endif
|
|
#ifdef DISABLE_TABCOMP
|
|
printf(" --disable-tabcomp");
|
|
#endif
|
|
#ifdef DISABLE_JUSTIFY
|
|
printf(" --disable-justify");
|
|
#endif
|
|
#ifdef DISABLE_SPELLER
|
|
printf(" --disable-speller");
|
|
#endif
|
|
#ifdef DISABLE_HELP
|
|
printf(" --disable-help");
|
|
#endif
|
|
#ifdef DISABLE_MOUSE
|
|
printf(" --disable-mouse");
|
|
#endif
|
|
#ifdef DISABLE_OPERATINGDIR
|
|
printf(" --disable-operatingdir");
|
|
#endif
|
|
#endif /* NANO_SMALL */
|
|
|
|
#ifdef DISABLE_WRAPPING
|
|
printf(" --disable-wrapping");
|
|
#endif
|
|
#ifdef USE_SLANG
|
|
printf(" --with-slang");
|
|
#endif
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
/* Create a new filestruct node. */
|
|
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;
|
|
}
|
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
|
/* Create a new openfilestruct node. */
|
|
openfilestruct *make_new_opennode(openfilestruct * prevnode)
|
|
{
|
|
openfilestruct *newnode;
|
|
|
|
newnode = nmalloc(sizeof(openfilestruct));
|
|
newnode->filename = NULL;
|
|
newnode->fileage = NULL;
|
|
newnode->filebot = NULL;
|
|
|
|
newnode->prev = prevnode;
|
|
newnode->next = NULL;
|
|
|
|
return newnode;
|
|
}
|
|
#endif
|
|
|
|
/* Splice a node into an existing filestruct. */
|
|
void splice_node(filestruct * begin, filestruct * newnode,
|
|
filestruct * end)
|
|
{
|
|
newnode->next = end;
|
|
newnode->prev = begin;
|
|
begin->next = newnode;
|
|
if (end != NULL)
|
|
end->prev = newnode;
|
|
}
|
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
|
/* Splice a node into an existing openfilestruct. */
|
|
void splice_opennode(openfilestruct * begin, openfilestruct * newnode,
|
|
openfilestruct * end)
|
|
{
|
|
newnode->next = end;
|
|
newnode->prev = begin;
|
|
begin->next = newnode;
|
|
if (end != NULL)
|
|
end->prev = newnode;
|
|
}
|
|
#endif
|
|
|
|
int do_mark(void)
|
|
{
|
|
#ifdef NANO_SMALL
|
|
nano_disabled_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;
|
|
}
|
|
|
|
#if defined(DISABLE_JUSTIFY) || defined(DISABLE_SPELLER) || \
|
|
defined(DISABLE_HELP) || defined(NANO_SMALL)
|
|
void nano_disabled_msg(void)
|
|
{
|
|
statusbar("Sorry, support for this function has been disabled");
|
|
}
|
|
#endif
|
|
|
|
/* The user typed a printable character; add it to the edit buffer */
|
|
void do_char(char ch)
|
|
{
|
|
/* magic-line: when a character is inserted on the current magic line,
|
|
* it means we need a new one! */
|
|
if (filebot == current && current->data[0] == '\0') {
|
|
new_magicline();
|
|
fix_editbot();
|
|
}
|
|
|
|
/* 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();
|
|
|
|
/* note that current_x has already been incremented */
|
|
if (current == mark_beginbuf && mark_beginx >= current_x)
|
|
mark_beginx++;
|
|
|
|
#ifdef ENABLE_COLOR
|
|
edit_refresh();
|
|
#endif
|
|
|
|
#ifndef DISABLE_WRAPPING
|
|
if (!ISSET(NO_WRAP) && (ch != '\t'))
|
|
check_wrap(current);
|
|
#endif
|
|
|
|
set_modified();
|
|
check_statblank();
|
|
UNSET(KEEP_CUTBUFFER);
|
|
totsize++;
|
|
|
|
}
|
|
|
|
/* Someone hits return *gasp!* */
|
|
int do_enter(filestruct * inptr)
|
|
{
|
|
filestruct *newnode;
|
|
char *tmp;
|
|
|
|
newnode = make_new_node(inptr);
|
|
assert(current->data != NULL);
|
|
tmp = ¤t->data[current_x];
|
|
|
|
#ifndef NANO_SMALL
|
|
/* Do auto-indenting, like the neolithic Turbo Pascal editor */
|
|
if (ISSET(AUTOINDENT)) {
|
|
int extra = 0;
|
|
char *spc = current->data;
|
|
while ((*spc == ' ') || (*spc == '\t')) {
|
|
extra++;
|
|
spc++;
|
|
}
|
|
/* If the cursor is in the indentation, only indent to the cursor.
|
|
* Ex, if cursor is at col 0, don't indent at all.
|
|
*/
|
|
if (current_x < extra)
|
|
extra = current_x;
|
|
else
|
|
current_x = extra;
|
|
totsize += extra;
|
|
|
|
newnode->data = charalloc(strlen(tmp) + extra + 1);
|
|
strncpy(newnode->data, current->data, extra);
|
|
strcpy(&newnode->data[extra], tmp);
|
|
} else
|
|
#endif
|
|
{
|
|
current_x = 0;
|
|
newnode->data = charalloc(strlen(tmp) + 1);
|
|
strcpy(newnode->data, tmp);
|
|
}
|
|
*tmp = 0;
|
|
|
|
if (inptr->next == NULL) {
|
|
filebot = newnode;
|
|
editbot = newnode;
|
|
}
|
|
splice_node(inptr, newnode, inptr->next);
|
|
|
|
totsize++;
|
|
renumber(current);
|
|
current = newnode;
|
|
align(¤t->data);
|
|
|
|
/* The logic here is as follows:
|
|
* -> If we are at the bottom of the buffer, we want to recenter
|
|
* (read: rebuild) the screen and forcibly move the cursor.
|
|
* -> otherwise, we want simply to redraw the screen and update
|
|
* where we think the cursor is.
|
|
*/
|
|
if (current_y == editwinrows - 1) {
|
|
edit_update(current, CENTER);
|
|
reset_cursor();
|
|
} else {
|
|
current_y++;
|
|
edit_refresh();
|
|
update_cursor();
|
|
}
|
|
|
|
totlines++;
|
|
set_modified();
|
|
|
|
placewewant = xplustabs();
|
|
return 1;
|
|
}
|
|
|
|
int do_enter_void(void)
|
|
{
|
|
return do_enter(current);
|
|
}
|
|
|
|
#ifndef NANO_SMALL
|
|
void do_next_word(void)
|
|
{
|
|
filestruct *fileptr, *old;
|
|
int i;
|
|
|
|
if (current == NULL)
|
|
return;
|
|
|
|
old = current;
|
|
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, CENTER);
|
|
else {
|
|
/* If we've jumped lines, refresh the old line. We can't just use
|
|
* current->prev here, because we may have skipped over some blank
|
|
* lines, in which case the previous line is the wrong one.
|
|
*/
|
|
if (current != old)
|
|
update_line(old, 0);
|
|
|
|
update_line(current, current_x);
|
|
}
|
|
}
|
|
|
|
int do_next_word_void(void) {
|
|
do_next_word();
|
|
return 0;
|
|
}
|
|
|
|
/* the same thing for backwards */
|
|
void do_prev_word(void)
|
|
{
|
|
filestruct *fileptr, *old;
|
|
int i;
|
|
|
|
if (current == NULL)
|
|
return;
|
|
|
|
old = current;
|
|
i = current_x;
|
|
for (fileptr = current; fileptr != NULL; fileptr = fileptr->prev) {
|
|
if (fileptr == current) {
|
|
while (isalnum((int) fileptr->data[i])
|
|
&& i != 0)
|
|
i--;
|
|
|
|
if (i == 0) {
|
|
if (fileptr->prev != NULL)
|
|
i = strlen(fileptr->prev->data);
|
|
else if (fileptr == fileage && filebot != NULL) {
|
|
current_x = 0;
|
|
return;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
while (!isalnum((int) fileptr->data[i]) && i != 0)
|
|
i--;
|
|
|
|
if (i > 0) {
|
|
i--;
|
|
|
|
while (isalnum((int) fileptr->data[i]) && i != 0)
|
|
i--;
|
|
|
|
if (!isalnum((int) fileptr->data[i]))
|
|
i++;
|
|
|
|
if (i != 0 || i != current_x)
|
|
break;
|
|
|
|
}
|
|
if (fileptr->prev != NULL)
|
|
i = strlen(fileptr->prev->data);
|
|
else if (fileptr == fileage && filebot != NULL) {
|
|
current_x = 0;
|
|
return;
|
|
}
|
|
}
|
|
if (fileptr == NULL)
|
|
current = fileage;
|
|
else
|
|
current = fileptr;
|
|
|
|
current_x = i;
|
|
placewewant = xplustabs();
|
|
|
|
if (current->lineno <= edittop->lineno)
|
|
edit_update(current, CENTER);
|
|
else {
|
|
/* If we've jumped lines, refresh the old line. We can't just use
|
|
* current->prev here, because we may have skipped over some blank
|
|
* lines, in which case the previous line is the wrong one.
|
|
*/
|
|
if (current != old)
|
|
update_line(old, 0);
|
|
|
|
update_line(current, current_x);
|
|
}
|
|
|
|
}
|
|
|
|
int do_prev_word_void(void) {
|
|
do_prev_word();
|
|
return 0;
|
|
}
|
|
#endif /* NANO_SMALL */
|
|
|
|
#ifndef DISABLE_WRAPPING
|
|
/* We wrap the given line. Precondition: we assume the cursor has been
|
|
* moved forward since the last typed character. */
|
|
void do_wrap(filestruct * inptr)
|
|
{
|
|
int len = strlen(inptr->data); /* length of the line we wrap */
|
|
int i; /* generic loop variable */
|
|
int wrap_loc = -1; /* index of inptr->data where we wrap */
|
|
int word_back = -1;
|
|
#ifndef NANO_SMALL
|
|
char *indentation = NULL; /* indentation to prepend to the new line */
|
|
int indent_len = 0; /* strlen(indentation) */
|
|
#endif
|
|
char *after_break; /* text after the wrap point */
|
|
int after_break_len; /* strlen(after_break) */
|
|
int wrapping = 0; /* do we prepend to the next line? */
|
|
char *wrap_line = NULL; /* the next line, minus indentation */
|
|
int wrap_line_len = 0; /* strlen(wrap_line) */
|
|
char *newline = NULL; /* the line we create */
|
|
int new_line_len = 0; /* eventual length of newline */
|
|
|
|
/* There are three steps. First, we decide where to wrap. Then, we
|
|
* create the new wrap line. Finally, we clean up. */
|
|
|
|
/* We need the following assertion, since otherwise we would wrap the
|
|
* last word to the next line regardless. */
|
|
assert(strlenpt(inptr->data) > fill);
|
|
|
|
/* Step 1, finding where to wrap. We are going to replace a white-space
|
|
* character with a new-line. In this step, we set wrap_loc as the
|
|
* location of this replacement.
|
|
*
|
|
* Where should we break the line? We need the last "legal wrap point"
|
|
* such that the last word before it ended at or before fill. If there
|
|
* is no such point, we settle for the first legal wrap point.
|
|
*
|
|
* A "legal wrap point" is a white-space character that is not the last
|
|
* typed character and is not followed by white-space.
|
|
*
|
|
* If there is no legal wrap point or we found the last character of the
|
|
* line, we should return without wrapping.
|
|
*
|
|
* Note that the initial indentation does not count as a legal wrap
|
|
* point if we are going to auto-indent!
|
|
*
|
|
* Note that the code below could be optimised, by not calling strlenpt
|
|
* so often, and by not calling isspace(inptr->data[i+1]) and then in
|
|
* the next loop calling isspace(inptr->data[i]). Oh well, fixing the
|
|
* first point would entail expanding the definition of strnlenpt, which
|
|
* I won't do since it will probably change soon. Fixing the second
|
|
* point would entail nested loops.
|
|
*/
|
|
|
|
i = 0;
|
|
#ifndef NANO_SMALL
|
|
if (ISSET(AUTOINDENT)) {
|
|
while (inptr->data[i] == ' ' || inptr->data[i] == '\t')
|
|
i++;
|
|
}
|
|
#endif
|
|
for(; i<len; i++) {
|
|
/* record where the last word ended */
|
|
if (!isspace((int) inptr->data[i]))
|
|
word_back = i;
|
|
/* if we have found a "legal wrap point" and the current word
|
|
* extends too far, then we stop */
|
|
if (wrap_loc != -1 && strnlenpt(inptr->data,word_back) > fill)
|
|
break;
|
|
/* we record the latest "legal wrap point" */
|
|
if (i != (current_x - 1) && isspace((int) inptr->data[i]) &&
|
|
(i == (len - 1) || !isspace((int)inptr->data[i + 1]))) {
|
|
wrap_loc = i;
|
|
}
|
|
}
|
|
if (wrap_loc < 0 || wrap_loc == (len - 1))
|
|
return;
|
|
|
|
|
|
/* Step 2, making the new wrap line. It will consist of indentation +
|
|
* after_break + " " + wrap_line (although indentation and wrap_line are
|
|
* conditional on flags and #defines). */
|
|
|
|
/* after_break is the text that will be moved to the next line. */
|
|
after_break = inptr->data + wrap_loc + 1;
|
|
after_break_len = len - wrap_loc - 1;
|
|
assert(after_break_len == strlen(after_break));
|
|
|
|
/* new_line_len will later be increased by the lengths of indentation
|
|
* and wrap_line. */
|
|
new_line_len = after_break_len;
|
|
|
|
/* We prepend the wrapped text to the next line, if the flag is set,
|
|
* and there is a next line, and prepending would not make the line
|
|
* too long. */
|
|
if (ISSET(SAMELINEWRAP) && inptr->next) {
|
|
wrap_line = inptr->next->data;
|
|
wrap_line_len = strlen(wrap_line);
|
|
|
|
/* +1 for the space between after_break and wrap_line */
|
|
if ((new_line_len + 1 + wrap_line_len) <= fill) {
|
|
wrapping = 1;
|
|
new_line_len += (1 + wrap_line_len);
|
|
}
|
|
}
|
|
|
|
#ifndef NANO_SMALL
|
|
if (ISSET(AUTOINDENT)) {
|
|
/* indentation comes from the next line if wrapping, else from
|
|
* this line */
|
|
indentation = (wrapping ? wrap_line : inptr->data);
|
|
while (indentation[indent_len] == ' ' ||
|
|
indentation[indent_len] == '\t')
|
|
indent_len++;
|
|
if (wrapping)
|
|
/* The wrap_line text should not duplicate indentation. Note
|
|
* in this case we need not increase new_line_len. */
|
|
wrap_line += indent_len;
|
|
else
|
|
new_line_len += indent_len;
|
|
}
|
|
#endif
|
|
|
|
/* Now we allocate the new line and copy into it. */
|
|
newline = charalloc(new_line_len + 1); /* +1 for \0 */
|
|
*newline = '\0';
|
|
|
|
#ifndef NANO_SMALL
|
|
if (ISSET(AUTOINDENT))
|
|
strncpy(newline, indentation, indent_len);
|
|
#endif
|
|
strcat(newline, after_break);
|
|
after_break = NULL;
|
|
/* We end the old line at wrap_loc. Note this eats the space. */
|
|
null_at(&inptr->data, wrap_loc);
|
|
if (wrapping) {
|
|
/* In this case, totsize does not change. We ate a space in the
|
|
* null_at() above, but we add a space between after_break and
|
|
* wrap_line below. */
|
|
strcat(newline, " ");
|
|
strcat(newline, wrap_line);
|
|
free(inptr->next->data);
|
|
inptr->next->data = newline;
|
|
} else {
|
|
filestruct *temp = (filestruct *)nmalloc(sizeof(filestruct));
|
|
/* In this case, the file size changes by -1 for the eaten
|
|
* space, +1 for the new line, and +indent_len for the new
|
|
* indentation. */
|
|
#ifndef NANO_SMALL
|
|
totsize += indent_len;
|
|
#endif
|
|
totlines++;
|
|
temp->data = newline;
|
|
temp->prev = inptr;
|
|
temp->next = inptr->next;
|
|
temp->prev->next = temp;
|
|
/* If !temp->next, then temp is the last line of the file, so we
|
|
* must set filebot */
|
|
if (temp->next)
|
|
temp->next->prev = temp;
|
|
else
|
|
filebot = temp;
|
|
}
|
|
|
|
|
|
/* Step 3, clean up. Here we reposition the cursor and mark, and do some
|
|
* other sundry things. */
|
|
|
|
/* later wraps of this line will be prepended to the next line. */
|
|
SET(SAMELINEWRAP);
|
|
|
|
/* Each line knows its line number. We recalculate these if we
|
|
* inserted a new line. */
|
|
if (!wrapping)
|
|
renumber(inptr);
|
|
edit_update(edittop, TOP);
|
|
|
|
/* if the cursor was after the break point, we must move it */
|
|
if (current_x > wrap_loc) {
|
|
/* We move it right by the number of characters that come before
|
|
* its corresponding position in the new line. That is,
|
|
* current_x - wrap_loc + indent_len. We actually need to go one
|
|
* further for the new line, but remember that current_x has
|
|
* already been incremented. */
|
|
int right =
|
|
#ifndef NANO_SMALL
|
|
indent_len +
|
|
#endif
|
|
current_x - wrap_loc;
|
|
|
|
/* note that do_right depends on the value of current_x */
|
|
current_x = wrap_loc;
|
|
while (right--)
|
|
do_right();
|
|
}
|
|
|
|
/* If the mark was on this line after the wrap point, we move it down.
|
|
* If it was on the next line and we wrapped, we must move it right.
|
|
*/
|
|
if (mark_beginbuf == inptr && mark_beginx > wrap_loc) {
|
|
mark_beginbuf = inptr->next;
|
|
mark_beginx -= wrap_loc;
|
|
} else if (wrapping && mark_beginbuf == inptr->next) {
|
|
mark_beginx += after_break_len;
|
|
}
|
|
|
|
/* The following lines are all copied from do_wrap() in version 1.1.7. It
|
|
* is not clear whether they are necessary. It looks like do_right()
|
|
* takes care of these things. It also appears that do_right() is very
|
|
* inefficient. */
|
|
/* Perhaps the global variable editbot, the last visible line in the
|
|
* editor, needs to change. */
|
|
fix_editbot();
|
|
/* Place the cursor. */
|
|
reset_cursor();
|
|
/* Display the changes on the screen. */
|
|
edit_refresh();
|
|
}
|
|
|
|
/* Check to see if we've just caused the line to wrap to a new line */
|
|
void check_wrap(filestruct * inptr)
|
|
{
|
|
int len = strlenpt(inptr->data);
|
|
#ifdef DEBUG
|
|
fprintf(stderr, _("check_wrap called with inptr->data=\"%s\"\n"),
|
|
inptr->data);
|
|
#endif
|
|
|
|
if (len <= fill)
|
|
return;
|
|
else {
|
|
int i = actual_x(inptr, fill);
|
|
|
|
/* Do not wrap if there are no words on or after wrap point. */
|
|
int char_found = 0;
|
|
|
|
while (isspace((int) inptr->data[i]) && inptr->data[i])
|
|
i++;
|
|
|
|
if (!inptr->data[i])
|
|
return;
|
|
|
|
/* String must be at least 1 character long. */
|
|
for (i = strlen(inptr->data) - 1; i >= 0; i--) {
|
|
if (isspace((int) inptr->data[i])) {
|
|
if (!char_found)
|
|
continue;
|
|
char_found = 2; /* 2 for yes do wrap. */
|
|
break;
|
|
} else
|
|
char_found = 1; /* 1 for yes found a word, but must check further. */
|
|
}
|
|
|
|
if (char_found == 2)
|
|
do_wrap(inptr);
|
|
}
|
|
}
|
|
#endif /* DISABLE_WRAPPING */
|
|
|
|
/* Stuff we do when we abort from programs and want to clean up the
|
|
* screen. This doesn't do much right now.
|
|
*/
|
|
void do_early_abort(void)
|
|
{
|
|
blank_statusbar_refresh();
|
|
}
|
|
|
|
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;
|
|
|
|
/* Recreate the magic line if we're deleting it AND if the
|
|
line we're on now is NOT blank. if it is blank we
|
|
can just use IT for the magic line. This is how Pico
|
|
appears to do it, in any case */
|
|
if (current->data[0] != '\0') {
|
|
new_magicline();
|
|
fix_editbot();
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
/* blbf -> blank line before filebot (see below) */
|
|
int blbf = 0;
|
|
|
|
if (current->next == filebot && current->data[0] == '\0')
|
|
blbf = 1;
|
|
|
|
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->next != filebot || blbf)) {
|
|
/* We can delete the line before filebot only if it is blank: it
|
|
* becomes the new magic line then. */
|
|
|
|
filestruct *foo;
|
|
|
|
current->data = nrealloc(current->data,
|
|
strlen(current->data) +
|
|
strlen(current->next->data) + 1);
|
|
strcat(current->data, current->next->data);
|
|
|
|
foo = current->next;
|
|
if (filebot == foo) {
|
|
filebot = current;
|
|
editbot = current;
|
|
}
|
|
|
|
unlink_node(foo);
|
|
delete_node(foo);
|
|
update_line(current, current_x);
|
|
renumber(current);
|
|
totlines--;
|
|
} else
|
|
return 0;
|
|
|
|
totsize--;
|
|
set_modified();
|
|
UNSET(KEEP_CUTBUFFER);
|
|
edit_refresh();
|
|
return 1;
|
|
}
|
|
|
|
void wrap_reset(void)
|
|
{
|
|
UNSET(SAMELINEWRAP);
|
|
}
|
|
|
|
#ifndef DISABLE_SPELLER
|
|
|
|
int do_int_spell_fix(char *word)
|
|
{
|
|
char *prevanswer = NULL, *save_search = NULL, *save_replace = NULL;
|
|
filestruct *begin;
|
|
int i = 0, j = 0, beginx, beginx_top, reverse_search_set;
|
|
#ifndef NANO_SMALL
|
|
int mark_set;
|
|
#endif
|
|
|
|
/* save where we are */
|
|
begin = current;
|
|
beginx = current_x + 1;
|
|
|
|
/* Make sure Spell Check goes forward only */
|
|
reverse_search_set = ISSET(REVERSE_SEARCH);
|
|
UNSET(REVERSE_SEARCH);
|
|
|
|
#ifndef NANO_SMALL
|
|
/* Make sure the marking highlight is off during Spell Check */
|
|
mark_set = ISSET(MARK_ISSET);
|
|
UNSET(MARK_ISSET);
|
|
#endif
|
|
|
|
/* save the current search/replace strings */
|
|
search_init_globals();
|
|
save_search = mallocstrcpy(save_search, last_search);
|
|
save_replace = mallocstrcpy(save_replace, last_replace);
|
|
|
|
/* set search/replace strings to mis-spelt word */
|
|
prevanswer = mallocstrcpy(prevanswer, word);
|
|
last_search = mallocstrcpy(last_search, word);
|
|
last_replace = mallocstrcpy(last_replace, word);
|
|
|
|
/* start from the top of file */
|
|
current = fileage;
|
|
current_x = beginx_top = -1;
|
|
|
|
search_last_line = FALSE;
|
|
|
|
edit_update(fileage, TOP);
|
|
|
|
while (1) {
|
|
|
|
/* make sure word is still mis-spelt (i.e. when multi-errors) */
|
|
if (findnextstr(TRUE, FALSE, fileage, beginx_top, prevanswer) != NULL) {
|
|
|
|
/* find wholewords only */
|
|
if (!is_whole_word(current_x, current, prevanswer))
|
|
continue;
|
|
|
|
do_replace_highlight(TRUE, prevanswer);
|
|
|
|
/* allow replace word to be corrected */
|
|
i = statusq(0, spell_list, last_replace, _("Edit a replacement"));
|
|
|
|
do_replace_highlight(FALSE, prevanswer);
|
|
|
|
/* start from the start of this line again */
|
|
current = fileage;
|
|
current_x = beginx_top;
|
|
|
|
search_last_line = FALSE;
|
|
|
|
if (strcmp(prevanswer, answer)) {
|
|
j = i;
|
|
do_replace_loop(prevanswer, fileage, &beginx_top, TRUE, &j);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* restore the search/replace strings */
|
|
free(last_search); last_search=save_search;
|
|
free(last_replace); last_replace=save_replace;
|
|
free(prevanswer);
|
|
|
|
/* restore where we were */
|
|
current = begin;
|
|
current_x = beginx - 1;
|
|
|
|
/* restore Search/Replace direction */
|
|
if (reverse_search_set)
|
|
SET(REVERSE_SEARCH);
|
|
|
|
#ifndef NANO_SMALL
|
|
/* restore marking highlight */
|
|
if (mark_set)
|
|
SET(MARK_ISSET);
|
|
#endif
|
|
|
|
edit_update(current, CENTER);
|
|
|
|
if (i == -1)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Integrated spell checking using 'spell' program */
|
|
int do_int_speller(char *tempfile_name)
|
|
{
|
|
char *read_buff, *read_buff_ptr, *read_buff_word;
|
|
size_t pipe_buff_size, read_buff_size, read_buff_read, bytesread;
|
|
int in_fd[2], tempfile_fd, spell_status;
|
|
pid_t pid_spell;
|
|
|
|
/* Create a pipe to spell program */
|
|
|
|
if (pipe(in_fd) == -1)
|
|
return FALSE;
|
|
|
|
/* A new process to run spell in */
|
|
|
|
if ((pid_spell = fork()) == 0) {
|
|
|
|
/* Child continues, (i.e. future spell process) */
|
|
|
|
close(in_fd[0]);
|
|
|
|
/* replace the standard in with the tempfile */
|
|
|
|
if ((tempfile_fd = open(tempfile_name, O_RDONLY)) == -1) {
|
|
|
|
close(in_fd[1]);
|
|
exit(1);
|
|
}
|
|
|
|
if (dup2(tempfile_fd, STDIN_FILENO) != STDIN_FILENO) {
|
|
|
|
close(tempfile_fd);
|
|
close(in_fd[1]);
|
|
exit(1);
|
|
}
|
|
close(tempfile_fd);
|
|
|
|
/* send spell's standard out to the pipe */
|
|
|
|
if (dup2(in_fd[1], STDOUT_FILENO) != STDOUT_FILENO) {
|
|
|
|
close(in_fd[1]);
|
|
exit(1);
|
|
}
|
|
close(in_fd[1]);
|
|
|
|
/* Start spell program, we are using the PATH here!?!? */
|
|
execlp("spell", "spell", NULL);
|
|
|
|
/* Should not be reached, if spell is found!!! */
|
|
|
|
exit(1);
|
|
}
|
|
|
|
/* Parent continues here */
|
|
|
|
close(in_fd[1]);
|
|
|
|
/* Child process was not forked successfully */
|
|
|
|
if (pid_spell < 0) {
|
|
|
|
close(in_fd[0]);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Get system pipe buffer size */
|
|
|
|
if ((pipe_buff_size = fpathconf(in_fd[0], _PC_PIPE_BUF)) < 1) {
|
|
|
|
close(in_fd[0]);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Read-in the returned spelling errors */
|
|
|
|
read_buff_read = 0;
|
|
read_buff_size = pipe_buff_size + 1;
|
|
read_buff = read_buff_ptr = charalloc(read_buff_size);
|
|
|
|
while ((bytesread = read(in_fd[0], read_buff_ptr, pipe_buff_size)) > 0) {
|
|
|
|
read_buff_read += bytesread;
|
|
read_buff_size += pipe_buff_size;
|
|
read_buff = read_buff_ptr = nrealloc(read_buff, read_buff_size);
|
|
read_buff_ptr += read_buff_read;
|
|
}
|
|
|
|
*read_buff_ptr = (char) NULL;
|
|
close(in_fd[0]);
|
|
|
|
/* Process the spelling errors */
|
|
|
|
read_buff_word = read_buff_ptr = read_buff;
|
|
|
|
while (*read_buff_ptr) {
|
|
|
|
if ((*read_buff_ptr == '\n') || (*read_buff_ptr == '\r')) {
|
|
*read_buff_ptr = (char) NULL;
|
|
if (read_buff_word != read_buff_ptr) {
|
|
if (!do_int_spell_fix(read_buff_word)) {
|
|
read_buff_word = read_buff_ptr;
|
|
break;
|
|
}
|
|
}
|
|
|
|
read_buff_word = read_buff_ptr + 1;
|
|
}
|
|
|
|
read_buff_ptr++;
|
|
}
|
|
|
|
/* special case where last word doesn't end with \n or \r */
|
|
if (read_buff_word != read_buff_ptr)
|
|
do_int_spell_fix(read_buff_word);
|
|
|
|
free(read_buff);
|
|
replace_abort();
|
|
|
|
/* Process end of spell process */
|
|
|
|
wait(&spell_status);
|
|
if (WIFEXITED(spell_status)) {
|
|
if (WEXITSTATUS(spell_status) != 0)
|
|
return FALSE;
|
|
} else
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* External spell checking */
|
|
int do_alt_speller(char *file_name)
|
|
{
|
|
int alt_spell_status, lineno_cur = current->lineno;
|
|
int x_cur = current_x, y_cur = current_y, pww_cur = placewewant;
|
|
#ifndef NANO_SMALL
|
|
int mark_set = 0, mbb_lineno_cur, mbx_cur;
|
|
#endif
|
|
|
|
pid_t pid_spell;
|
|
char *ptr;
|
|
static int arglen = 3;
|
|
static char **spellargs = (char **) NULL;
|
|
|
|
#ifndef NANO_SMALL
|
|
mark_set = ISSET(MARK_ISSET);
|
|
if (mark_set) {
|
|
/* Save the marking position */
|
|
mbb_lineno_cur = mark_beginbuf->lineno;
|
|
mbx_cur = mark_beginx;
|
|
UNSET(MARK_ISSET);
|
|
}
|
|
#endif
|
|
|
|
endwin();
|
|
|
|
/* Set up an argument list to pass the execvp function */
|
|
if (spellargs == NULL) {
|
|
spellargs = nmalloc(arglen * sizeof(char *));
|
|
|
|
spellargs[0] = strtok(alt_speller, " ");
|
|
while ((ptr = strtok(NULL, " ")) != NULL) {
|
|
arglen++;
|
|
spellargs = nrealloc(spellargs, arglen * sizeof(char *));
|
|
spellargs[arglen - 3] = ptr;
|
|
}
|
|
spellargs[arglen - 1] = NULL;
|
|
}
|
|
spellargs[arglen - 2] = file_name;
|
|
|
|
/* Start a new process for the alternate speller */
|
|
if ((pid_spell = fork()) == 0) {
|
|
|
|
/* Start alternate spell program; we are using the PATH here!?!? */
|
|
execvp(spellargs[0], spellargs);
|
|
|
|
/* Should not be reached, if alternate speller is found!!! */
|
|
|
|
exit(1);
|
|
}
|
|
|
|
/* Could not fork?? */
|
|
|
|
if (pid_spell < 0)
|
|
return FALSE;
|
|
|
|
/* Wait for alternate speller to complete */
|
|
|
|
wait(&alt_spell_status);
|
|
if (WIFEXITED(alt_spell_status)) {
|
|
if (WEXITSTATUS(alt_spell_status) != 0)
|
|
return FALSE;
|
|
} else
|
|
return FALSE;
|
|
|
|
refresh();
|
|
free_filestruct(fileage);
|
|
global_init(1);
|
|
open_file(file_name, 0, 1);
|
|
|
|
#ifndef NANO_SMALL
|
|
if (mark_set) {
|
|
/* Restore the marking position */
|
|
do_gotopos(mbb_lineno_cur, mbx_cur, y_cur, 0);
|
|
mark_beginbuf = current;
|
|
SET(MARK_ISSET);
|
|
}
|
|
#endif
|
|
|
|
/* go back to the old position, mark the file as modified, and make
|
|
sure that the titlebar is refreshed */
|
|
do_gotopos(lineno_cur, x_cur, y_cur, pww_cur);
|
|
set_modified();
|
|
clearok(topwin, FALSE);
|
|
titlebar(NULL);
|
|
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
int do_spell(void)
|
|
{
|
|
|
|
#ifdef DISABLE_SPELLER
|
|
nano_disabled_msg();
|
|
return (TRUE);
|
|
#else
|
|
char *temp;
|
|
int spell_res;
|
|
|
|
if ((temp = safe_tempnam(0, "nano.")) == NULL) {
|
|
statusbar(_("Could not create a temporary filename: %s"),
|
|
strerror(errno));
|
|
return 0;
|
|
}
|
|
|
|
if (write_file(temp, 1, 0, 0) == -1) {
|
|
statusbar(_("Spell checking failed: unable to write temp file!"));
|
|
free(temp);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
|
/* update the current open_files entry before spell-checking, in case
|
|
any problems occur */
|
|
add_open_file(1);
|
|
#endif
|
|
|
|
if (alt_speller)
|
|
spell_res = do_alt_speller(temp);
|
|
else
|
|
spell_res = do_int_speller(temp);
|
|
|
|
remove(temp);
|
|
|
|
if (spell_res)
|
|
statusbar(_("Finished checking spelling"));
|
|
else
|
|
statusbar(_("Spell checking failed"));
|
|
|
|
free(temp);
|
|
return spell_res;
|
|
|
|
#endif
|
|
}
|
|
|
|
#ifndef NANO_SMALL
|
|
static int pid; /* this is the PID of the newly forked process below.
|
|
* It must be global since the signal handler needs it.
|
|
*/
|
|
|
|
RETSIGTYPE cancel_fork(int signal) {
|
|
if (kill(pid, SIGKILL)==-1) nperror("kill");
|
|
}
|
|
|
|
int open_pipe(char *command)
|
|
{
|
|
int fd[2];
|
|
FILE *f;
|
|
struct sigaction oldaction, newaction;
|
|
/* original and temporary handlers for SIGINT */
|
|
#ifdef _POSIX_VDISABLE
|
|
struct termios term, newterm;
|
|
#endif /* _POSIX_VDISABLE */
|
|
int cancel_sigs = 0;
|
|
/* cancel_sigs==1 means that sigaction failed without changing the
|
|
* signal handlers. cancel_sigs==2 means the signal handler was
|
|
* changed, but the tcsetattr didn't succeed.
|
|
* I use this variable since it is important to put things back when
|
|
* we finish, even if we get errors.
|
|
*/
|
|
|
|
/* Make our pipes. */
|
|
|
|
if (pipe(fd) == -1) {
|
|
statusbar(_("Could not pipe"));
|
|
return 1;
|
|
}
|
|
|
|
/* Fork a child */
|
|
|
|
if ((pid = fork()) == 0) {
|
|
close(fd[0]);
|
|
dup2(fd[1], fileno(stdout));
|
|
dup2(fd[1], fileno(stderr));
|
|
/* If execl() returns at all, there was an error. */
|
|
|
|
execl("/bin/sh","sh","-c",command,0);
|
|
exit(0);
|
|
}
|
|
|
|
/* Else continue as parent */
|
|
|
|
close(fd[1]);
|
|
|
|
if (pid == -1) {
|
|
close(fd[0]);
|
|
statusbar(_("Could not fork"));
|
|
return 1;
|
|
}
|
|
|
|
/* before we start reading the forked command's output, we set
|
|
* things up so that ^C will cancel the new process.
|
|
*/
|
|
if (sigaction(SIGINT, NULL, &newaction)==-1) {
|
|
cancel_sigs = 1;
|
|
nperror("sigaction");
|
|
} else {
|
|
newaction.sa_handler = cancel_fork;
|
|
if (sigaction(SIGINT, &newaction, &oldaction)==-1) {
|
|
cancel_sigs = 1;
|
|
nperror("sigaction");
|
|
}
|
|
}
|
|
/* note that now oldaction is the previous SIGINT signal handler, to
|
|
* be restored later */
|
|
|
|
/* if the platform supports disabling individual control characters */
|
|
#ifdef _POSIX_VDISABLE
|
|
if (!cancel_sigs && tcgetattr(0, &term) == -1) {
|
|
cancel_sigs = 2;
|
|
nperror("tcgetattr");
|
|
}
|
|
if (!cancel_sigs) {
|
|
newterm = term;
|
|
/* Grab oldterm's VINTR key :-) */
|
|
newterm.c_cc[VINTR] = oldterm.c_cc[VINTR];
|
|
if (tcsetattr(0, TCSANOW, &newterm) == -1) {
|
|
cancel_sigs = 2;
|
|
nperror("tcsetattr");
|
|
}
|
|
}
|
|
#endif /* _POSIX_VDISABLE */
|
|
|
|
f = fdopen(fd[0], "rb");
|
|
if (!f)
|
|
nperror("fdopen");
|
|
|
|
read_file(f, "stdin", 0);
|
|
set_modified();
|
|
|
|
if (wait(NULL) == -1)
|
|
nperror("wait");
|
|
|
|
#ifdef _POSIX_VDISABLE
|
|
if (!cancel_sigs && tcsetattr(0, TCSANOW, &term) == -1)
|
|
nperror("tcsetattr");
|
|
#endif /* _POSIX_VDISABLE */
|
|
|
|
if (cancel_sigs!=1 && sigaction(SIGINT, &oldaction, NULL) == -1)
|
|
nperror("sigaction");
|
|
|
|
return 0;
|
|
}
|
|
#endif /* NANO_SMALL */
|
|
|
|
int do_exit(void)
|
|
{
|
|
int i;
|
|
|
|
if (!ISSET(MODIFIED)) {
|
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
|
if (!close_open_file()) {
|
|
display_main_list();
|
|
return 1;
|
|
}
|
|
else
|
|
#endif
|
|
|
|
finish(0);
|
|
}
|
|
|
|
if (ISSET(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(filename, 1, 0) > 0) {
|
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
|
if (!close_open_file()) {
|
|
display_main_list();
|
|
return 1;
|
|
}
|
|
else
|
|
#endif
|
|
|
|
finish(0);
|
|
}
|
|
} else if (i == 0) {
|
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
|
if (!close_open_file()) {
|
|
display_main_list();
|
|
return 1;
|
|
}
|
|
else
|
|
#endif
|
|
|
|
finish(0);
|
|
} else
|
|
statusbar(_("Cancelled"));
|
|
|
|
display_main_list();
|
|
return 1;
|
|
}
|
|
|
|
#ifndef DISABLE_MOUSE
|
|
#ifdef NCURSES_MOUSE_VERSION
|
|
void do_mouse(void)
|
|
{
|
|
MEVENT mevent;
|
|
int foo = 0, tab_found = 0;
|
|
int currslen;
|
|
shortcut *s = currshortcut;
|
|
|
|
if (getmouse(&mevent) == ERR)
|
|
return;
|
|
|
|
/* If mouse not in edit or bottom window, return */
|
|
if (wenclose(edit, mevent.y, mevent.x)) {
|
|
|
|
/* Don't let people screw with the marker when they're in a
|
|
subfunction */
|
|
if (currshortcut != main_list)
|
|
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;
|
|
placewewant = current_x;
|
|
while (foo < current_x) {
|
|
if (current->data[foo] == NANO_CONTROL_I) {
|
|
current_x -= tabsize - (foo % tabsize);
|
|
tab_found = 1;
|
|
} else if (current->data[foo] & 0x80);
|
|
else if (current->data[foo] < 32)
|
|
current_x--;
|
|
foo++;
|
|
}
|
|
/* This is where tab_found comes in. I can't figure out why,
|
|
* but without it any line with a tab will place the cursor
|
|
* one character behind. Whatever, this fixes it. */
|
|
if (tab_found == 1)
|
|
current_x++;
|
|
|
|
if (current_x > strlen(current->data))
|
|
current_x = strlen(current->data);
|
|
|
|
update_cursor();
|
|
edit_refresh();
|
|
} else if (wenclose(bottomwin, mevent.y, mevent.x) && !ISSET(NO_HELP)) {
|
|
|
|
int i, k;
|
|
|
|
if (currshortcut == main_list)
|
|
currslen = MAIN_VISIBLE;
|
|
else
|
|
currslen = length_of_list(currshortcut);
|
|
|
|
if (currslen < 2)
|
|
k = COLS / 6;
|
|
else
|
|
k = COLS / ((currslen + (currslen %2)) / 2);
|
|
|
|
/* Determine what shortcut list was clicked */
|
|
mevent.y -= (editwinrows + 3);
|
|
|
|
if (mevent.y < 0) /* They clicked on the statusbar */
|
|
return;
|
|
|
|
/* Don't select stuff beyond list length */
|
|
if (mevent.x / k >= currslen)
|
|
return;
|
|
|
|
for (i = 0; i < (mevent.x / k) * 2 + mevent.y; i++)
|
|
s = s->next;
|
|
|
|
/* And ungetch that value */
|
|
ungetch(s->val);
|
|
|
|
/* And if it's an alt-key sequence, we should probably send alt
|
|
too ;-) */
|
|
if (s->val >= 'a' && s->val <= 'z')
|
|
ungetch(27);
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
/* Handler for SIGHUP */
|
|
RETSIGTYPE handle_hup(int signal)
|
|
{
|
|
die(_("Received SIGHUP"));
|
|
}
|
|
|
|
/* What do we do when we catch the suspend signal */
|
|
RETSIGTYPE do_suspend(int signal)
|
|
{
|
|
endwin();
|
|
printf("\n\n\n\n\nUse \"fg\" to return to nano\n");
|
|
fflush(stdout);
|
|
|
|
/* Restore the terminal settings for the disabled keys */
|
|
tcsetattr(0, TCSANOW, &oldterm);
|
|
|
|
/* We used to re-enable the default SIG_DFL and raise SIGTSTP, but
|
|
then we could be (and were) interrupted in the middle of the call.
|
|
So we do it the mutt way instead */
|
|
kill(0, SIGSTOP);
|
|
}
|
|
|
|
/* Restore the suspend handler when we come back into the prog */
|
|
RETSIGTYPE do_cont(int signal)
|
|
{
|
|
|
|
/* Now we just update the screen instead of having to reenable the
|
|
SIGTSTP handler */
|
|
|
|
doupdate();
|
|
/* The Hurd seems to need this, otherwise a ^Y after a ^Z will
|
|
start suspending again */
|
|
signal_init();
|
|
}
|
|
|
|
void handle_sigwinch(int s)
|
|
{
|
|
#ifndef NANO_SMALL
|
|
char *tty = NULL;
|
|
int fd = 0;
|
|
int result = 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;
|
|
|
|
if ((editwinrows = LINES - 5 + no_help()) < MIN_EDITOR_ROWS)
|
|
die_too_small();
|
|
|
|
#ifndef DISABLE_WRAPJUSTIFY
|
|
if ((fill = COLS - CHARS_FROM_EOL) < MIN_FILL_LENGTH)
|
|
die_too_small();
|
|
#endif
|
|
|
|
hblank = nrealloc(hblank, COLS + 1);
|
|
memset(hblank, ' ', COLS);
|
|
hblank[COLS] = '\0';
|
|
|
|
#ifdef HAVE_RESIZETERM
|
|
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_RESIZETERM */
|
|
|
|
fix_editbot();
|
|
|
|
if (current_y > editwinrows - 1) {
|
|
edit_update(editbot, CENTER);
|
|
}
|
|
erase();
|
|
|
|
/* Do these b/c width may have changed... */
|
|
refresh();
|
|
titlebar(NULL);
|
|
edit_refresh();
|
|
display_main_list();
|
|
blank_statusbar();
|
|
total_refresh();
|
|
|
|
/* Turn cursor back on for sure */
|
|
curs_set(1);
|
|
|
|
/* Jump back to mainloop */
|
|
siglongjmp(jmpbuf, 1);
|
|
|
|
#endif
|
|
}
|
|
|
|
void signal_init(void)
|
|
{
|
|
#ifdef _POSIX_VDISABLE
|
|
struct termios term;
|
|
#endif
|
|
|
|
/* 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);
|
|
|
|
/* 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 _POSIX_VDISABLE
|
|
tcgetattr(0, &term);
|
|
|
|
#ifdef VDSUSP
|
|
term.c_cc[VDSUSP] = _POSIX_VDISABLE;
|
|
#endif /* VDSUSP */
|
|
|
|
#endif /* _POSIX_VDISABLE */
|
|
|
|
if (!ISSET(SUSPEND)) {
|
|
|
|
/* Insane! */
|
|
#ifdef _POSIX_VDISABLE
|
|
term.c_cc[VSUSP] = _POSIX_VDISABLE;
|
|
#else
|
|
act.sa_handler = SIG_IGN;
|
|
sigaction(SIGTSTP, &act, NULL);
|
|
#endif
|
|
|
|
} else {
|
|
/* if we don't do this, it seems other stuff interrupts the
|
|
suspend handler! Try using nano with mutt without this line */
|
|
sigfillset(&act.sa_mask);
|
|
|
|
act.sa_handler = do_suspend;
|
|
sigaction(SIGTSTP, &act, NULL);
|
|
|
|
act.sa_handler = do_cont;
|
|
sigaction(SIGCONT, &act, NULL);
|
|
}
|
|
|
|
|
|
#ifdef _POSIX_VDISABLE
|
|
tcsetattr(0, TCSANOW, &term);
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
void window_init(void)
|
|
{
|
|
if ((editwinrows = LINES - 5 + no_help()) < MIN_EDITOR_ROWS)
|
|
die_too_small();
|
|
|
|
/* Set up the main text window */
|
|
edit = newwin(editwinrows, COLS, 2, 0);
|
|
|
|
/* And the other windows */
|
|
topwin = newwin(2, COLS, 0, 0);
|
|
bottomwin = newwin(3 - no_help(), COLS, LINES - 3 + no_help(), 0);
|
|
|
|
#ifdef PDCURSES
|
|
/* Oops, I guess we need this again.
|
|
Moved here so the keypad still works after a Meta-X, for example */
|
|
keypad(edit, TRUE);
|
|
keypad(bottomwin, TRUE);
|
|
#endif
|
|
|
|
}
|
|
|
|
void mouse_init(void)
|
|
{
|
|
#ifndef DISABLE_MOUSE
|
|
#ifdef NCURSES_MOUSE_VERSION
|
|
if (ISSET(USE_MOUSE)) {
|
|
keypad_on(edit, 1);
|
|
keypad_on(bottomwin, 1);
|
|
|
|
mousemask(BUTTON1_RELEASED, NULL);
|
|
mouseinterval(50);
|
|
|
|
} else
|
|
mousemask(0, NULL);
|
|
|
|
#endif
|
|
#endif
|
|
|
|
}
|
|
|
|
int do_tab(void)
|
|
{
|
|
do_char('\t');
|
|
return 1;
|
|
}
|
|
|
|
#ifndef DISABLE_JUSTIFY
|
|
int empty_line(const char *data)
|
|
{
|
|
while (*data) {
|
|
if (!isspace((int) *data))
|
|
return 0;
|
|
|
|
data++;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int no_spaces(const char *data)
|
|
{
|
|
while (*data) {
|
|
if (isspace((int) *data))
|
|
return 0;
|
|
|
|
data++;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void justify_format(char *data)
|
|
{
|
|
int i = 0;
|
|
int len = strlen(data);
|
|
|
|
/* Skip leading whitespace. */
|
|
for (i = 0; i < len; i++) {
|
|
if (!isspace((int) 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((int) data[i]) && isspace((int) data[i - 1])
|
|
&& (data[i - 2] != '.')
|
|
&& (data[i-2]!='!') && (data[i-2]!='?')) {
|
|
memmove(data + i, data + i + 1, len - i);
|
|
len--;
|
|
i--;
|
|
}
|
|
}
|
|
/* Skip trailing whitespace.
|
|
* i<=len iff there was a non-space in the line. In that case, we
|
|
* strip spaces from the end of the line. Note that "line" means the
|
|
* whole paragraph. */
|
|
if (i<=len) {
|
|
for(i=len-1; i>0 && isspace((int) data[i]); i--);
|
|
data[i+1] = '\0';
|
|
}
|
|
}
|
|
#endif
|
|
|
|
int do_justify(void)
|
|
{
|
|
#ifdef DISABLE_JUSTIFY
|
|
nano_disabled_msg();
|
|
return 1;
|
|
#else
|
|
int slen = 0; /* length of combined lines on one line. */
|
|
int initial_y, kbinput = 0;
|
|
long totbak;
|
|
filestruct *initial = NULL, *tmpjust = NULL, *cutbak, *tmptop, *tmpbot;
|
|
filestruct *samecheck = current;
|
|
int qdepth = 0;
|
|
|
|
/* Compute quote depth level */
|
|
while (!strncmp(¤t->data[qdepth], quotestr, strlen(quotestr)))
|
|
qdepth += strlen(quotestr);
|
|
|
|
if (empty_line(¤t->data[qdepth])) {
|
|
/* Justify starting at first non-empty line. */
|
|
do {
|
|
if (!current->next)
|
|
return 1;
|
|
|
|
current = current->next;
|
|
current_y++;
|
|
}
|
|
while (strlen(current->data) >= qdepth
|
|
&& !strncmp(current->data, samecheck->data, qdepth)
|
|
&& empty_line(¤t->data[qdepth]));
|
|
|
|
} 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 (strncmp(current->data, samecheck->data, qdepth)
|
|
|
|
/* Don't keep going back if the previous line is more
|
|
intented quotestr-wise than samecheck */
|
|
|| !strncmp(¤t->data[qdepth], quotestr, strlen(quotestr))
|
|
|| isspace((int) current->data[qdepth])
|
|
|| empty_line(¤t->data[qdepth]))
|
|
break;
|
|
|
|
current = current->prev;
|
|
current_y--;
|
|
}
|
|
|
|
/* First line with leading whitespace may be empty. */
|
|
if (strncmp(current->data, samecheck->data, qdepth)
|
|
|| !strncmp(¤t->data[qdepth], quotestr, strlen(quotestr))
|
|
|| empty_line(¤t->data[qdepth])) {
|
|
if (current->next) {
|
|
current = current->next;
|
|
current_y++;
|
|
} else
|
|
return 1;
|
|
}
|
|
}
|
|
initial = current;
|
|
initial_y = current_y;
|
|
|
|
set_modified();
|
|
cutbak = cutbuffer; /* Got to like cutbak ;) */
|
|
totbak = totsize;
|
|
cutbuffer = NULL;
|
|
|
|
tmptop = current;
|
|
tmpjust = copy_node(current);
|
|
samecheck = tmpjust;
|
|
|
|
/* This is annoying because it mucks with totsize */
|
|
add_to_cutbuffer(tmpjust);
|
|
|
|
/* Put the whole paragraph into one big line. */
|
|
while (current->next && !isspace((int) current->next->data[0])
|
|
&& !strncmp(current->next->data, samecheck->data, qdepth)
|
|
|
|
/* Don't continue if current->next is indented more! */
|
|
&& strncmp(¤t->next->data[qdepth], quotestr, strlen(quotestr))
|
|
&& !empty_line(¤t->next->data[qdepth])) {
|
|
filestruct *tmpnode = current->next;
|
|
int len = strlen(current->data);
|
|
int len2 = strlen(current->next->data) - qdepth;
|
|
|
|
tmpjust = NULL;
|
|
tmpjust = copy_node(current->next);
|
|
add_to_cutbuffer(tmpjust);
|
|
|
|
/* Wiping out a newline */
|
|
totsize--;
|
|
|
|
/* 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, ¤t->next->data[qdepth], len2);
|
|
|
|
unlink_node(tmpnode);
|
|
delete_node(tmpnode);
|
|
}
|
|
|
|
justify_format(current->data);
|
|
|
|
slen = strlen(current->data);
|
|
totsize += slen;
|
|
|
|
while (strlenpt(current->data) > fill
|
|
&& !no_spaces(current->data + qdepth)) {
|
|
int i = 0, j = 0;
|
|
filestruct *tmpline = nmalloc(sizeof(filestruct));
|
|
|
|
/* The following code maybe could be better. In particular, can we
|
|
* merely increment instead of calling strnlenpt for each new character?
|
|
* In fact, can we assume the only tabs are at the beginning of the line?
|
|
*/
|
|
/* Note that we CAN break before the first word, since that is how
|
|
* pico does it. */
|
|
int last_space = -1; /* index of the last breakpoint */
|
|
|
|
for(i=qdepth; i<slen; i++) {
|
|
if (isspace((int) current->data[i]))
|
|
last_space = i;
|
|
/* Note we must look at the length of the first i+1 chars. */
|
|
if (last_space!=-1 &&
|
|
strnlenpt(current->data,i+1) > fill) {
|
|
i = last_space;
|
|
break;
|
|
}
|
|
}
|
|
/* Now data[i] is a space. We want to break at the LAST space in this
|
|
* group. Probably, the only possibility is two in a row, but let's be
|
|
* generic. Note that we actually replace this final space with \0. Is
|
|
* this okay? It seems to work fine. */
|
|
for(; i<slen-1 && isspace((int) current->data[i+1]); i++)
|
|
;
|
|
|
|
current->data[i] = '\0';
|
|
|
|
slen -= i + 1 - qdepth; /* note i > qdepth */
|
|
tmpline->data = charalloc(slen + 1);
|
|
|
|
for (j = 0; j < qdepth; j += strlen(quotestr))
|
|
strcpy(&tmpline->data[j], quotestr);
|
|
|
|
/* Skip the white space in current. */
|
|
memcpy(&tmpline->data[qdepth], current->data + i + 1, slen - qdepth);
|
|
tmpline->data[slen] = '\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;
|
|
current_y++;
|
|
} /* end of while (!no_spaces) */
|
|
tmpbot = current;
|
|
|
|
if (current->next)
|
|
current = current->next;
|
|
else
|
|
filebot = current;
|
|
current_x = 0;
|
|
placewewant = 0;
|
|
|
|
renumber(initial);
|
|
totlines = filebot->lineno;
|
|
|
|
werase(edit);
|
|
|
|
if ((current_y < 0) || (current_y >= editwinrows - 1)
|
|
|| (initial_y <= 0)) {
|
|
edit_update(current, CENTER);
|
|
center_cursor();
|
|
} else {
|
|
fix_editbot();
|
|
}
|
|
|
|
edit_refresh();
|
|
statusbar(_("Can now UnJustify!"));
|
|
/* Change the shortcut list to display the unjustify code */
|
|
shortcut_init(1);
|
|
display_main_list();
|
|
reset_cursor();
|
|
|
|
/* Now get a keystroke and see if it's unjustify; if not, unget the keystroke
|
|
and return */
|
|
|
|
#ifndef DISABLE_MOUSE
|
|
#ifdef NCURSES_MOUSE_VERSION
|
|
|
|
/* If it was a mouse click, parse it with do_mouse and it might become
|
|
the unjustify key. Else give it back to the input stream. */
|
|
if ((kbinput = wgetch(edit)) == KEY_MOUSE)
|
|
do_mouse();
|
|
else
|
|
ungetch(kbinput);
|
|
#endif
|
|
#endif
|
|
|
|
if ((kbinput = wgetch(edit)) != NANO_UNJUSTIFY_KEY) {
|
|
ungetch(kbinput);
|
|
blank_statusbar_refresh();
|
|
} else {
|
|
/* Else restore the justify we just did (ungrateful user!) */
|
|
if (tmptop->prev != NULL)
|
|
tmptop->prev->next = tmpbot->next;
|
|
else
|
|
fileage = current;
|
|
tmpbot->next->prev = tmptop->prev;
|
|
current = tmpbot->next;
|
|
tmpbot->next = NULL;
|
|
do_uncut_text();
|
|
if (tmptop->prev == NULL)
|
|
edit_refresh();
|
|
|
|
/* Restore totsize from before justify */
|
|
totsize = totbak;
|
|
free_filestruct(tmptop);
|
|
blank_statusbar_refresh();
|
|
}
|
|
shortcut_init(0);
|
|
display_main_list();
|
|
free_filestruct(cutbuffer);
|
|
cutbuffer = cutbak;
|
|
|
|
return 1;
|
|
#endif
|
|
}
|
|
|
|
#ifndef DISABLE_HELP
|
|
/* This function allocates help_text, and stores the help string in it.
|
|
* help_text should be NULL initially. */
|
|
void help_init(void)
|
|
{
|
|
size_t allocsize = 1; /* space needed for help_text */
|
|
char *ptr = NULL;
|
|
#ifndef NANO_SMALL
|
|
const toggle *t;
|
|
#endif
|
|
const shortcut *s;
|
|
|
|
/* First set up the initial help text for the current function */
|
|
if (currshortcut == whereis_list || currshortcut == replace_list
|
|
|| currshortcut == replace_list_2)
|
|
ptr = _("Search Command Help Text\n\n "
|
|
"Enter the words or characters you would like to search "
|
|
"for, then hit enter. If there is a match for the text you "
|
|
"entered, the screen will be updated to the location of the "
|
|
"nearest match for the search string.\n\n "
|
|
"If using Pico Mode via the -p or --pico flags, the "
|
|
"Meta-P toggle, or a nanorc file, the previous search "
|
|
"string will be shown in brackets after the Search: prompt. "
|
|
"Hitting Enter without entering any text will perform the "
|
|
"previous search. Otherwise, the previous string will be "
|
|
"placed before the cursor, and can be edited or deleted "
|
|
"before hitting enter.\n\n The following function keys are "
|
|
"available in Search mode:\n\n");
|
|
else if (currshortcut == goto_list)
|
|
ptr = _("Go To Line Help Text\n\n "
|
|
"Enter the line number that you wish to go to and hit "
|
|
"Enter. If there are fewer lines of text than the "
|
|
"number you entered, you will be brought to the last line "
|
|
"of the file.\n\n The following function keys are "
|
|
"available in Go To Line mode:\n\n");
|
|
else if (currshortcut == insertfile_list)
|
|
ptr = _("Insert File Help Text\n\n "
|
|
"Type in the name of a file to be inserted into the current "
|
|
"file buffer at the current cursor location.\n\n "
|
|
"If you have compiled nano with multiple file buffer "
|
|
"support, and enable multiple buffers with the -F "
|
|
"or --multibuffer command line flags, the Meta-F toggle, or "
|
|
"a nanorc file, inserting a file will cause it to be "
|
|
"loaded into a separate buffer (use Meta-< and > to switch "
|
|
"between file buffers).\n\n If you need another blank "
|
|
"buffer, do not enter any filename, or type in a "
|
|
"nonexistent filename at the prompt and press "
|
|
"Enter.\n\n The following function keys are "
|
|
"available in Insert File mode:\n\n");
|
|
else if (currshortcut == writefile_list)
|
|
ptr = _("Write File Help Text\n\n "
|
|
"Type the name that you wish to save the current file "
|
|
"as and hit Enter to save the file.\n\n If you have "
|
|
"selected text with Ctrl-^, you will be prompted to "
|
|
"save only the selected portion to a separate file. To "
|
|
"reduce the chance of overwriting the current file with "
|
|
"just a portion of it, the current filename is not the "
|
|
"default in this mode.\n\n The following function keys "
|
|
"are available in Write File mode:\n\n");
|
|
#ifndef DISABLE_BROWSER
|
|
else if (currshortcut == browser_list)
|
|
ptr = _("File Browser Help Text\n\n "
|
|
"The file browser is used to visually browse the "
|
|
"directory structure to select a file for reading "
|
|
"or writing. You may use the arrow keys or Page Up/"
|
|
"Down to browse through the files, and S or Enter to "
|
|
"choose the selected file or enter the selected "
|
|
"directory. To move up one level, select the directory "
|
|
"called \"..\" at the top of the file list.\n\n The "
|
|
"following function keys are available in the file "
|
|
"browser:\n\n");
|
|
else if (currshortcut == gotodir_list)
|
|
ptr = _("Browser Go To Directory Help Text\n\n "
|
|
"Enter the name of the directory you would like to "
|
|
"browse to.\n\n If tab completion has not been disabled, "
|
|
"you can use the TAB key to (attempt to) automatically "
|
|
"complete the directory name.\n\n The following function "
|
|
"keys are available in Browser Go To Directory mode:\n\n");
|
|
#endif
|
|
else if (currshortcut == spell_list)
|
|
ptr = _("Spell Check Help Text\n\n "
|
|
"The spell checker checks the spelling of all text "
|
|
"in the current file. When an unknown word is "
|
|
"encountered, it is highlighted and a replacement can "
|
|
"be edited. It will then prompt to replace every "
|
|
"instance of the given misspelled word in the "
|
|
"current file.\n\n The following other functions are "
|
|
"available in Spell Check mode:\n\n");
|
|
#ifndef NANO_SMALL
|
|
else if (currshortcut == extcmd_list)
|
|
ptr = _("External Command Help Text\n\n "
|
|
"This menu allows you to insert the output of a command "
|
|
"run by the shell into the current buffer (or a new "
|
|
"buffer in multibuffer mode).\n\n The following keys are "
|
|
"available in this mode:\n\n");
|
|
#endif
|
|
else /* Default to the main help list */
|
|
ptr = _(" nano help text\n\n "
|
|
"The nano editor is designed to emulate the functionality and "
|
|
"ease-of-use of the UW Pico text editor. There are four main "
|
|
"sections of the editor: The top line shows the program "
|
|
"version, the current filename being edited, and whether "
|
|
"or not the file has been modified. Next is the main editor "
|
|
"window showing the file being edited. The status line is "
|
|
"the third line from the bottom and shows important messages. "
|
|
"The bottom two lines show the most commonly used shortcuts "
|
|
"in the editor.\n\n "
|
|
"The notation for shortcuts is as follows: Control-key "
|
|
"sequences are notated with a caret (^) symbol and are entered "
|
|
"with the Control (Ctrl) key. Escape-key sequences are notated "
|
|
"with the Meta (M) symbol and can be entered using either the "
|
|
"Esc, Alt or Meta key depending on your keyboard setup. The "
|
|
"following keystrokes are available in the main editor window. "
|
|
"Alternative keys are shown in parentheses:\n\n");
|
|
|
|
assert(currshortcut != NULL);
|
|
/* Compute the space needed for the shortcut lists */
|
|
for (s = currshortcut; s != NULL; s = s->next) {
|
|
assert(s->help != NULL);
|
|
/* Each shortcut has at most 24 chars for the shortcut keys, plus
|
|
* the help description, plus 1 for \n. */
|
|
allocsize += strlen(s->help) + 25;
|
|
}
|
|
|
|
#ifndef NANO_SMALL
|
|
/* If we're on the main list, we also count the toggle help text. */
|
|
if (currshortcut == main_list) {
|
|
for (t = toggles; t != NULL; t = t->next) {
|
|
size_t len;
|
|
|
|
assert(t->desc != NULL);
|
|
len = strlen(t->desc);
|
|
|
|
/* 6 for "M-%c\t\t\t", which fills 24 columns. */
|
|
allocsize += 6 + (len < COLS-24 ? len : COLS-24);
|
|
}
|
|
}
|
|
#endif /* !NANO_SMALL */
|
|
|
|
allocsize += strlen(ptr);
|
|
|
|
/* Other routines should have set help_text to NULL before */
|
|
assert(help_text == NULL);
|
|
|
|
/* Allocate space for the help text */
|
|
help_text = charalloc(allocsize);
|
|
|
|
/* Now add the text we want */
|
|
strcpy(help_text, ptr);
|
|
ptr = help_text + strlen(help_text);
|
|
|
|
/* Now add our shortcut info */
|
|
for (s = currshortcut; s != NULL; s = s->next) {
|
|
/* true if the character in s->altval is shown in first column */
|
|
int meta_shortcut = 0;
|
|
|
|
if (s->val > 0 && s->val < 32)
|
|
ptr += sprintf(ptr, "^%c", s->val + 64);
|
|
#ifndef NANO_SMALL
|
|
else if (s->val == NANO_CONTROL_SPACE)
|
|
ptr += snprintf(ptr, 8, "^%s", _("Space"));
|
|
else if (s->altval == NANO_ALT_SPACE) {
|
|
meta_shortcut = 1;
|
|
ptr += snprintf(ptr, 8, "M-%s", _("Space"));
|
|
}
|
|
#endif
|
|
else if (s->altval > 0) {
|
|
meta_shortcut = 1;
|
|
ptr += sprintf(ptr, "M-%c", s->altval -
|
|
(('A' <= s->altval && s->altval <= 'Z') ||
|
|
'a' <= s->altval ? 32 : 0));
|
|
}
|
|
/* Hack */
|
|
else if (s->val >= 'a') {
|
|
meta_shortcut = 1;
|
|
ptr += sprintf(ptr, "M-%c", s->val - 32);
|
|
}
|
|
|
|
*(ptr++) = '\t';
|
|
|
|
if (s->misc1 > KEY_F0 && s->misc1 <= KEY_F(64))
|
|
ptr += sprintf(ptr, "(F%d)", s->misc1 - KEY_F0);
|
|
|
|
*(ptr++) = '\t';
|
|
|
|
if (!meta_shortcut && s->altval > 0)
|
|
ptr += sprintf(ptr, "(M-%c)", s->altval -
|
|
(('A' <= s->altval && s->altval <= 'Z') || 'a' <= s->altval
|
|
? 32 : 0));
|
|
|
|
*(ptr++) = '\t';
|
|
|
|
ptr += sprintf(ptr, "%s\n", s->help);
|
|
}
|
|
|
|
#ifndef NANO_SMALL
|
|
/* And the toggles... */
|
|
if (currshortcut == main_list)
|
|
for (t = toggles; t != NULL; t = t->next) {
|
|
ptr += sprintf(ptr, "M-%c\t\t\t", t->val - 32);
|
|
ptr += snprintf(ptr, COLS-24, _("%s enable/disable\n"),
|
|
t->desc);
|
|
}
|
|
#endif /* !NANO_SMALL */
|
|
|
|
/* If all went well, we didn't overwrite the allocated space for
|
|
help_text. */
|
|
assert(strlen(help_text) < allocsize);
|
|
}
|
|
#endif
|
|
|
|
#ifndef NANO_SMALL
|
|
void do_toggle(toggle *which)
|
|
{
|
|
char *enabled = _("enabled");
|
|
char *disabled = _("disabled");
|
|
|
|
/* Even easier! */
|
|
TOGGLE(which->flag);
|
|
|
|
switch (which->val) {
|
|
case TOGGLE_PICOMODE_KEY:
|
|
shortcut_init(0);
|
|
SET(CLEAR_BACKUPSTRING);
|
|
display_main_list();
|
|
break;
|
|
case TOGGLE_SUSPEND_KEY:
|
|
signal_init();
|
|
break;
|
|
case TOGGLE_MOUSE_KEY:
|
|
mouse_init();
|
|
break;
|
|
case TOGGLE_NOHELP_KEY:
|
|
wclear(bottomwin);
|
|
wrefresh(bottomwin);
|
|
window_init();
|
|
fix_editbot();
|
|
edit_refresh();
|
|
display_main_list();
|
|
break;
|
|
case TOGGLE_DOS_KEY:
|
|
UNSET(MAC_FILE);
|
|
break;
|
|
case TOGGLE_MAC_KEY:
|
|
UNSET(DOS_FILE);
|
|
break;
|
|
}
|
|
|
|
if (!ISSET(which->flag)) {
|
|
if (which->val == TOGGLE_NOHELP_KEY ||
|
|
which->val == TOGGLE_WRAP_KEY)
|
|
statusbar("%s %s", which->desc, enabled);
|
|
else
|
|
statusbar("%s %s", which->desc, disabled);
|
|
} else {
|
|
if (which->val == TOGGLE_NOHELP_KEY ||
|
|
which->val == TOGGLE_WRAP_KEY)
|
|
statusbar("%s %s", which->desc, disabled);
|
|
else
|
|
statusbar("%s %s", which->desc, enabled);
|
|
}
|
|
}
|
|
#endif /* !NANO_SMALL */
|
|
|
|
/* If the NumLock key has made the keypad go awry, print an error
|
|
message; hopefully we can address it later. */
|
|
void print_numlock_warning(void)
|
|
{
|
|
static int didmsg = 0;
|
|
if (!didmsg) {
|
|
statusbar(_
|
|
("NumLock glitch detected. Keypad will malfunction with NumLock off"));
|
|
didmsg = 1;
|
|
}
|
|
}
|
|
|
|
/* This function returns the correct keystroke, given the A,B,C or D
|
|
input key. This is a common sequence of many terms which send
|
|
Esc-O-[A-D] or Esc-[-[A-D]. */
|
|
int ABCD(int input)
|
|
{
|
|
switch (input) {
|
|
case 'A':
|
|
case 'a':
|
|
return (KEY_UP);
|
|
case 'B':
|
|
case 'b':
|
|
return (KEY_DOWN);
|
|
case 'C':
|
|
case 'c':
|
|
return (KEY_RIGHT);
|
|
case 'D':
|
|
case 'd':
|
|
return (KEY_LEFT);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int optchr;
|
|
int kbinput; /* Input from keyboard */
|
|
int startline = 0; /* Line to try and start at */
|
|
int keyhandled; /* Have we handled the keystroke yet? */
|
|
int modify_control_seq;
|
|
char *argv0;
|
|
shortcut *s;
|
|
#ifndef NANO_SMALL
|
|
toggle *t;
|
|
#endif
|
|
|
|
#ifdef _POSIX_VDISABLE
|
|
struct termios term;
|
|
#endif
|
|
|
|
#ifdef HAVE_GETOPT_LONG
|
|
int option_index = 0;
|
|
struct option long_options[] = {
|
|
{"help", 0, 0, 'h'},
|
|
#ifdef ENABLE_MULTIBUFFER
|
|
{"multibuffer", 0, 0, 'F'},
|
|
#endif
|
|
{"keypad", 0, 0, 'K'},
|
|
#ifndef DISABLE_JUSTIFY
|
|
{"quotestr", 1, 0, 'Q'},
|
|
#endif
|
|
#ifdef HAVE_REGEX_H
|
|
{"regexp", 0, 0, 'R'},
|
|
#endif
|
|
{"tabsize", 1, 0, 'T'},
|
|
{"version", 0, 0, 'V'},
|
|
#ifdef ENABLE_COLOR
|
|
{"syntax", 1, 0, 'Y'},
|
|
#endif
|
|
{"const", 0, 0, 'c'},
|
|
{"nofollow", 0, 0, 'l'},
|
|
{"mouse", 0, 0, 'm'},
|
|
#ifndef DISABLE_OPERATINGDIR
|
|
{"operatingdir", 1, 0, 'o'},
|
|
#endif
|
|
{"pico", 0, 0, 'p'},
|
|
#ifndef DISABLE_WRAPJUSTIFY
|
|
{"fill", 1, 0, 'r'},
|
|
#endif
|
|
#ifndef DISABLE_SPELLER
|
|
{"speller", 1, 0, 's'},
|
|
#endif
|
|
{"tempfile", 0, 0, 't'},
|
|
{"view", 0, 0, 'v'},
|
|
{"nowrap", 0, 0, 'w'},
|
|
{"nohelp", 0, 0, 'x'},
|
|
{"suspend", 0, 0, 'z'},
|
|
#ifndef NANO_SMALL
|
|
{"backup", 0, 0, 'B'},
|
|
{"dos", 0, 0, 'D'},
|
|
{"mac", 0, 0, 'M'},
|
|
{"noconvert", 0, 0, 'N'},
|
|
{"smooth", 0, 0, 'S'},
|
|
{"autoindent", 0, 0, 'i'},
|
|
{"cut", 0, 0, 'k'},
|
|
#endif
|
|
{0, 0, 0, 0}
|
|
};
|
|
#endif
|
|
|
|
/* Flag inits... */
|
|
SET(FOLLOW_SYMLINKS);
|
|
|
|
#ifndef NANO_SMALL
|
|
#ifdef ENABLE_NLS
|
|
setlocale(LC_ALL, "");
|
|
bindtextdomain(PACKAGE, LOCALEDIR);
|
|
textdomain(PACKAGE);
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef ENABLE_NANORC
|
|
do_rcfile();
|
|
#endif /* ENABLE_NANORC */
|
|
|
|
#ifdef HAVE_GETOPT_LONG
|
|
while ((optchr = getopt_long(argc, argv, "h?BDFKMNQ:RST:VY:abcefgijklmo:pr:s:tvwxz",
|
|
long_options, &option_index)) != EOF) {
|
|
#else
|
|
while ((optchr =
|
|
getopt(argc, argv, "h?BDFKMNQ:RST:VY:abcefgijklmo:pr:s:tvwxz")) != EOF) {
|
|
#endif
|
|
|
|
switch (optchr) {
|
|
|
|
case 'h':
|
|
case '?':
|
|
usage();
|
|
exit(0);
|
|
case 'a':
|
|
case 'b':
|
|
case 'e':
|
|
case 'f':
|
|
case 'g':
|
|
case 'j':
|
|
/* Pico compatibility flags */
|
|
break;
|
|
#ifndef NANO_SMALL
|
|
case 'B':
|
|
SET(BACKUP_FILE);
|
|
break;
|
|
case 'D':
|
|
SET(DOS_FILE);
|
|
break;
|
|
#endif
|
|
#ifdef ENABLE_MULTIBUFFER
|
|
case 'F':
|
|
SET(MULTIBUFFER);
|
|
break;
|
|
#endif
|
|
case 'K':
|
|
SET(ALT_KEYPAD);
|
|
break;
|
|
#ifndef NANO_SMALL
|
|
case 'M':
|
|
SET(MAC_FILE);
|
|
break;
|
|
case 'N':
|
|
SET(NO_CONVERT);
|
|
break;
|
|
#endif
|
|
case 'Q':
|
|
#ifndef DISABLE_JUSTIFY
|
|
quotestr = optarg;
|
|
break;
|
|
#else
|
|
usage(); /* To stop bogus data for tab width */
|
|
finish(1);
|
|
#endif
|
|
#ifdef HAVE_REGEX_H
|
|
case 'R':
|
|
SET(USE_REGEXP);
|
|
break;
|
|
#endif
|
|
#ifndef NANO_SMALL
|
|
case 'S':
|
|
SET(SMOOTHSCROLL);
|
|
break;
|
|
#endif
|
|
case 'T':
|
|
tabsize = atoi(optarg);
|
|
if (tabsize <= 0) {
|
|
usage(); /* To stop bogus data for tab width */
|
|
finish(1);
|
|
}
|
|
break;
|
|
case 'V':
|
|
version();
|
|
exit(0);
|
|
#ifdef ENABLE_COLOR
|
|
case 'Y':
|
|
syntaxstr = mallocstrcpy(syntaxstr, optarg);
|
|
break;
|
|
#endif
|
|
case 'c':
|
|
SET(CONSTUPDATE);
|
|
break;
|
|
#ifndef NANO_SMALL
|
|
case 'i':
|
|
SET(AUTOINDENT);
|
|
break;
|
|
case 'k':
|
|
SET(CUT_TO_END);
|
|
break;
|
|
#endif
|
|
case 'l':
|
|
UNSET(FOLLOW_SYMLINKS);
|
|
break;
|
|
case 'm':
|
|
SET(USE_MOUSE);
|
|
break;
|
|
#ifndef DISABLE_OPERATINGDIR
|
|
case 'o':
|
|
operating_dir = mallocstrcpy(operating_dir, optarg);
|
|
|
|
/* make sure we're inside the operating directory */
|
|
if (check_operating_dir(".", 0)) {
|
|
if (chdir(operating_dir) == -1) {
|
|
free(operating_dir);
|
|
operating_dir = NULL;
|
|
}
|
|
}
|
|
break;
|
|
#endif
|
|
case 'p':
|
|
SET(PICO_MODE);
|
|
break;
|
|
case 'r':
|
|
#ifndef DISABLE_WRAPJUSTIFY
|
|
fill = atoi(optarg);
|
|
if (fill < 0)
|
|
wrap_at = fill;
|
|
else if (fill > 0)
|
|
wrap_at = 0;
|
|
else if (fill == 0) {
|
|
usage(); /* To stop bogus data (like a string) */
|
|
finish(1);
|
|
}
|
|
break;
|
|
#else
|
|
usage();
|
|
exit(0);
|
|
|
|
#endif
|
|
#ifndef DISABLE_SPELLER
|
|
case 's':
|
|
alt_speller = mallocstrcpy(alt_speller, optarg);
|
|
break;
|
|
#endif
|
|
case 't':
|
|
SET(TEMP_OPT);
|
|
break;
|
|
case 'v':
|
|
SET(VIEW_MODE);
|
|
break;
|
|
case 'w':
|
|
#ifdef DISABLE_WRAPPING
|
|
usage();
|
|
exit(0);
|
|
#else
|
|
SET(NO_WRAP);
|
|
break;
|
|
#endif /* DISABLE_WRAPPING */
|
|
case 'x':
|
|
SET(NO_HELP);
|
|
break;
|
|
case 'z':
|
|
SET(SUSPEND);
|
|
break;
|
|
default:
|
|
usage();
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
/* Clear the filename we'll be using */
|
|
filename = charalloc(1);
|
|
filename[0] = '\0';
|
|
|
|
/* See if we were invoked with the name "pico" */
|
|
argv0 = strrchr(argv[0], '/');
|
|
if ((argv0 && strstr(argv0, "pico"))
|
|
|| (!argv0 && strstr(argv[0], "pico")))
|
|
SET(PICO_MODE);
|
|
|
|
/* 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) {
|
|
/* Look for the +line flag... */
|
|
if (argv[optind][0] == '+') {
|
|
startline = atoi(&argv[optind][1]);
|
|
optind++;
|
|
if (argc > 1 && argc > optind)
|
|
filename = mallocstrcpy(filename, argv[optind]);
|
|
} else
|
|
filename = mallocstrcpy(filename, argv[optind]);
|
|
}
|
|
|
|
/* First back up the old settings so they can be restored, duh */
|
|
tcgetattr(0, &oldterm);
|
|
|
|
#ifdef _POSIX_VDISABLE
|
|
term = oldterm;
|
|
term.c_cc[VINTR] = _POSIX_VDISABLE;
|
|
term.c_cc[VQUIT] = _POSIX_VDISABLE;
|
|
term.c_lflag &= ~IEXTEN;
|
|
tcsetattr(0, TCSANOW, &term);
|
|
#endif
|
|
|
|
/* now ncurses init stuff... */
|
|
initscr();
|
|
savetty();
|
|
nonl();
|
|
cbreak();
|
|
noecho();
|
|
|
|
/* Set up some global variables */
|
|
global_init(0);
|
|
shortcut_init(0);
|
|
signal_init();
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, _("Main: set up windows\n"));
|
|
#endif
|
|
|
|
window_init();
|
|
mouse_init();
|
|
|
|
if (!ISSET(ALT_KEYPAD)) {
|
|
keypad(edit, TRUE);
|
|
keypad(bottomwin, TRUE);
|
|
}
|
|
|
|
#ifdef ENABLE_COLOR
|
|
do_colorinit();
|
|
|
|
#endif /* ENABLE_COLOR */
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, _("Main: bottom win\n"));
|
|
#endif
|
|
/* Set up bottom of window */
|
|
display_main_list();
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, _("Main: open file\n"));
|
|
#endif
|
|
|
|
/* Now we check to see if argv[optind] is non-null to determine if
|
|
we're dealing with a new file or not, not argc == 1... */
|
|
if (argv[optind] == NULL)
|
|
new_file();
|
|
else
|
|
open_file(filename, 0, 0);
|
|
|
|
titlebar(NULL);
|
|
|
|
if (startline > 0)
|
|
do_gotoline(startline, 0);
|
|
else
|
|
edit_update(fileage, CENTER);
|
|
|
|
/* return here after a sigwinch */
|
|
sigsetjmp(jmpbuf, 1);
|
|
|
|
/* Fix clobber-age */
|
|
kbinput = 0;
|
|
keyhandled = 0;
|
|
modify_control_seq = 0;
|
|
|
|
edit_refresh();
|
|
reset_cursor();
|
|
|
|
while (1) {
|
|
|
|
#ifndef DISABLE_MOUSE
|
|
currshortcut = main_list;
|
|
#endif
|
|
|
|
#ifndef _POSIX_VDISABLE
|
|
/* We're going to have to do it the old way, i.e. on cygwin */
|
|
raw();
|
|
#endif
|
|
|
|
kbinput = wgetch(edit);
|
|
#ifdef DEBUG
|
|
fprintf(stderr, _("AHA! %c (%d)\n"), kbinput, kbinput);
|
|
#endif
|
|
|
|
if (kbinput == 27) { /* Grab Alt-key stuff first */
|
|
switch (kbinput = wgetch(edit)) {
|
|
/* Alt-O, suddenly very important ;) */
|
|
case 'O':
|
|
kbinput = wgetch(edit);
|
|
if ((kbinput <= 'D' && kbinput >= 'A') ||
|
|
(kbinput <= 'd' && kbinput >= 'a'))
|
|
kbinput = ABCD(kbinput);
|
|
else if (kbinput <= 'z' && kbinput >= 'j')
|
|
print_numlock_warning();
|
|
else if (kbinput <= 'S' && kbinput >= 'P')
|
|
kbinput = KEY_F(kbinput - 79);
|
|
#ifdef DEBUG
|
|
else {
|
|
fprintf(stderr, _("I got Alt-O-%c! (%d)\n"),
|
|
kbinput, kbinput);
|
|
break;
|
|
}
|
|
#endif
|
|
break;
|
|
case 27:
|
|
/* If we get Alt-Alt, the next keystroke should be the same as a
|
|
control sequence */
|
|
modify_control_seq = 1;
|
|
keyhandled = 1;
|
|
break;
|
|
case '[':
|
|
switch (kbinput = wgetch(edit)) {
|
|
case '1': /* Alt-[-1-[0-5,7-9] = F1-F8 in X at least */
|
|
kbinput = wgetch(edit);
|
|
if (kbinput >= '1' && kbinput <= '5') {
|
|
kbinput = KEY_F(kbinput - 48);
|
|
wgetch(edit);
|
|
} else if (kbinput >= '7' && kbinput <= '9') {
|
|
kbinput = KEY_F(kbinput - 49);
|
|
wgetch(edit);
|
|
} else if (kbinput == '~')
|
|
kbinput = KEY_HOME;
|
|
|
|
#ifdef DEBUG
|
|
else {
|
|
fprintf(stderr, _("I got Alt-[-1-%c! (%d)\n"),
|
|
kbinput, kbinput);
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
break;
|
|
case '2': /* Alt-[-2-[0,1,3,4] = F9-F12 in many terms */
|
|
kbinput = wgetch(edit);
|
|
switch (kbinput) {
|
|
case '0':
|
|
kbinput = KEY_F(9);
|
|
wgetch(edit);
|
|
break;
|
|
case '1':
|
|
kbinput = KEY_F(10);
|
|
wgetch(edit);
|
|
break;
|
|
case '3':
|
|
kbinput = KEY_F(11);
|
|
wgetch(edit);
|
|
break;
|
|
case '4':
|
|
kbinput = KEY_F(12);
|
|
wgetch(edit);
|
|
break;
|
|
case '~':
|
|
goto do_insertkey;
|
|
#ifdef DEBUG
|
|
default:
|
|
fprintf(stderr, _("I got Alt-[-2-%c! (%d)\n"),
|
|
kbinput, kbinput);
|
|
break;
|
|
#endif
|
|
|
|
}
|
|
break;
|
|
case '3': /* Alt-[-3 = Delete? */
|
|
kbinput = NANO_DELETE_KEY;
|
|
wgetch(edit);
|
|
break;
|
|
case '4': /* Alt-[-4 = End? */
|
|
kbinput = NANO_END_KEY;
|
|
wgetch(edit);
|
|
break;
|
|
case '5': /* Alt-[-5 = Page Up */
|
|
kbinput = KEY_PPAGE;
|
|
wgetch(edit);
|
|
break;
|
|
case 'V': /* Alt-[-V = Page Up in Hurd Console */
|
|
case 'I': /* Alt-[-I = Page Up - FreeBSD Console */
|
|
kbinput = KEY_PPAGE;
|
|
break;
|
|
case '6': /* Alt-[-6 = Page Down */
|
|
kbinput = KEY_NPAGE;
|
|
wgetch(edit);
|
|
break;
|
|
case 'U': /* Alt-[-U = Page Down in Hurd Console */
|
|
case 'G': /* Alt-[-G = Page Down - FreeBSD Console */
|
|
kbinput = KEY_NPAGE;
|
|
break;
|
|
case '7':
|
|
kbinput = KEY_HOME;
|
|
wgetch(edit);
|
|
break;
|
|
case '8':
|
|
kbinput = KEY_END;
|
|
wgetch(edit);
|
|
break;
|
|
case '9': /* Alt-[-9 = Delete in Hurd Console */
|
|
kbinput = KEY_DC;
|
|
break;
|
|
case '@': /* Alt-[-@ = Insert in Hurd Console */
|
|
case 'L': /* Alt-[-L = Insert - FreeBSD Console */
|
|
goto do_insertkey;
|
|
case '[': /* Alt-[-[-[A-E], F1-F5 in Linux console */
|
|
kbinput = wgetch(edit);
|
|
if (kbinput >= 'A' && kbinput <= 'E')
|
|
kbinput = KEY_F(kbinput - 64);
|
|
break;
|
|
case 'A':
|
|
case 'B':
|
|
case 'C':
|
|
case 'D':
|
|
case 'a':
|
|
case 'b':
|
|
case 'c':
|
|
case 'd':
|
|
kbinput = ABCD(kbinput);
|
|
break;
|
|
case 'H':
|
|
kbinput = KEY_HOME;
|
|
break;
|
|
case 'F':
|
|
case 'Y': /* End Key in Hurd Console */
|
|
kbinput = KEY_END;
|
|
break;
|
|
default:
|
|
#ifdef DEBUG
|
|
fprintf(stderr, _("I got Alt-[-%c! (%d)\n"),
|
|
kbinput, kbinput);
|
|
#endif
|
|
break;
|
|
}
|
|
break;
|
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
|
case NANO_OPENPREV_KEY:
|
|
case NANO_OPENPREV_ALTKEY:
|
|
open_prevfile(0);
|
|
keyhandled = 1;
|
|
break;
|
|
case NANO_OPENNEXT_KEY:
|
|
case NANO_OPENNEXT_ALTKEY:
|
|
open_nextfile(0);
|
|
keyhandled = 1;
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
/* Check for the altkey defs.... */
|
|
for (s = main_list; s != NULL; s = s->next)
|
|
if (kbinput == s->altval ||
|
|
kbinput == s->altval - 32) {
|
|
if (ISSET(VIEW_MODE) && !s->viewok)
|
|
print_view_warning();
|
|
else
|
|
s->func();
|
|
keyhandled = 1;
|
|
break;
|
|
}
|
|
#ifndef NANO_SMALL
|
|
/* And for toggle switches */
|
|
for (t = toggles; t != NULL && !keyhandled; t = t->next)
|
|
if (kbinput == t->val ||
|
|
(t->val > 'a' &&
|
|
kbinput == t->val - 32)) {
|
|
do_toggle(t);
|
|
keyhandled = 1;
|
|
break;
|
|
}
|
|
#endif
|
|
#ifdef DEBUG
|
|
fprintf(stderr, _("I got Alt-%c! (%d)\n"), kbinput,
|
|
kbinput);
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
/* If the modify_control_seq is set, we received an Alt-Alt
|
|
sequence before this, so we make this key a control sequence
|
|
by subtracting 64 or 96, depending on its value. */
|
|
if (!keyhandled && modify_control_seq) {
|
|
if (kbinput >= 'A' && kbinput < 'a')
|
|
kbinput -= 64;
|
|
else if (kbinput >= 'a' && kbinput <= 'z')
|
|
kbinput -= 96;
|
|
|
|
modify_control_seq = 0;
|
|
}
|
|
|
|
/* Look through the main shortcut list to see if we've hit a
|
|
shortcut key */
|
|
|
|
#if !defined(DISABLE_BROWSER) || !defined(DISABLE_MOUSE) || !defined (DISABLE_HELP)
|
|
for (s = currshortcut; s != NULL && !keyhandled; s = s->next) {
|
|
#else
|
|
for (s = main_list; s != NULL && !keyhandled; s = s->next) {
|
|
#endif
|
|
if (kbinput == s->val ||
|
|
(s->misc1 && kbinput == s->misc1) ||
|
|
(s->misc2 && kbinput == s->misc2)) {
|
|
if (ISSET(VIEW_MODE) && !s->viewok)
|
|
print_view_warning();
|
|
else
|
|
s->func();
|
|
keyhandled = 1;
|
|
}
|
|
}
|
|
/* If we're in raw mode or using Alt-Alt-x, we have to catch
|
|
Control-S and Control-Q */
|
|
if (kbinput == 17 || kbinput == 19)
|
|
keyhandled = 1;
|
|
|
|
/* Catch ^Z by hand when triggered also
|
|
407 == ^Z in Linux console when keypad() is used? */
|
|
if (kbinput == 26 || kbinput == 407) {
|
|
if (ISSET(SUSPEND))
|
|
do_suspend(0);
|
|
keyhandled = 1;
|
|
}
|
|
|
|
|
|
#ifndef USE_SLANG
|
|
/* Hack, make insert key do something useful, like insert file */
|
|
if (kbinput == KEY_IC) {
|
|
#else
|
|
if (0) {
|
|
#endif
|
|
do_insertkey:
|
|
|
|
#ifdef ENABLE_MULTIBUFFER
|
|
/* do_insertfile_void() contains the logic needed to
|
|
handle view mode with the view mode/multibuffer
|
|
exception, so use it here */
|
|
do_insertfile_void();
|
|
#else
|
|
print_view_warning();
|
|
#endif
|
|
|
|
keyhandled = 1;
|
|
}
|
|
|
|
/* Last gasp, stuff that's not in the main lists */
|
|
if (!keyhandled)
|
|
switch (kbinput) {
|
|
#ifndef DISABLE_MOUSE
|
|
#ifdef NCURSES_MOUSE_VERSION
|
|
case KEY_MOUSE:
|
|
do_mouse();
|
|
break;
|
|
#endif
|
|
#endif
|
|
|
|
case 0: /* Erg */
|
|
case -1: /* Stuff that we don't want to do squat */
|
|
case 410: /* Must ignore this, it gets sent when we resize */
|
|
case 29: /* Ctrl-] */
|
|
#ifdef PDCURSES
|
|
case 541: /* ???? */
|
|
case 542: /* Control and alt in Windows *shrug* */
|
|
case 543: /* Right ctrl key */
|
|
case 544:
|
|
case 545: /* Right alt key */
|
|
#endif
|
|
|
|
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(DISABLE_CURPOS))
|
|
UNSET(DISABLE_CURPOS);
|
|
else if (ISSET(CONSTUPDATE))
|
|
do_cursorpos(1);
|
|
|
|
reset_cursor();
|
|
wrefresh(edit);
|
|
keyhandled = 0;
|
|
}
|
|
|
|
getchar();
|
|
finish(0);
|
|
|
|
}
|