nano/winio.c
2002-09-19 23:54:53 +00:00

1923 lines
44 KiB
C
Raw Blame History

/* $Id$ */
/**************************************************************************
* winio.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 <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.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
static int statblank = 0; /* Number of keystrokes left after
we call statusbar(), before we
actually blank the statusbar */
int do_first_line(void)
{
current = fileage;
placewewant = 0;
current_x = 0;
edit_update(current, CENTER);
return 1;
}
int do_last_line(void)
{
current = filebot;
placewewant = 0;
current_x = 0;
edit_update(current, CENTER);
return 1;
}
/* Like xplustabs, but for a specific index of a specific filestruct */
int xpt(const filestruct *fileptr, int index)
{
int i, tabs = 0;
if (fileptr == NULL || fileptr->data == NULL)
return 0;
for (i = 0; i < index && fileptr->data[i] != 0; i++) {
tabs++;
if (fileptr->data[i] == NANO_CONTROL_I) {
if (tabs % tabsize == 0);
else
tabs += tabsize - (tabs % tabsize);
} else if (is_cntrl_char((int)fileptr->data[i]))
tabs++;
else if (fileptr->data[i] & 0x80)
/* Make 8 bit chars only 1 column! */
;
}
return tabs;
}
/* Return the placewewant associated with current_x. That is, xplustabs
* is the zero-based column position of the cursor. Value is no smaller
* than current_x. */
size_t xplustabs(void)
{
return strnlenpt(current->data, current_x);
}
/* Return what current_x should be, given xplustabs() for the line. */
size_t actual_x(const filestruct *fileptr, size_t xplus)
{
size_t i = 0;
/* the position in fileptr->data, returned */
size_t length = 0;
/* the screen display width to data[i] */
char *c;
/* fileptr->data + i */
assert(fileptr != NULL && fileptr->data != NULL);
for (c = fileptr->data; length < xplus && *c != '\0'; i++, c++) {
if (*c == '\t')
length += tabsize - length % tabsize;
else if (is_cntrl_char((int)*c))
length += 2;
else
length++;
}
assert(length == strnlenpt(fileptr->data, i));
assert(i <= strlen(fileptr->data));
if (length > xplus)
i--;
#ifdef DEBUG
fprintf(stderr, _("actual_x for xplus=%d returns %d\n"), xplus, i);
#endif
return i;
}
/* A strlen with tabs factored in, similar to xplustabs(). */
size_t strnlenpt(const char *buf, size_t size)
{
size_t length = 0;
if (buf != NULL)
for (; *buf != '\0' && size != 0; size--, buf++) {
if (*buf == '\t')
length += tabsize - (length % tabsize);
else if (is_cntrl_char((int)*buf))
length += 2;
else
length++;
}
return length;
}
size_t strlenpt(const char *buf)
{
return strnlenpt(buf, -1);
}
void blank_bottombars(void)
{
if (!no_help()) {
mvwaddstr(bottomwin, 1, 0, hblank);
mvwaddstr(bottomwin, 2, 0, hblank);
}
}
void blank_bottomwin(void)
{
if (ISSET(NO_HELP))
return;
mvwaddstr(bottomwin, 1, 0, hblank);
mvwaddstr(bottomwin, 2, 0, hblank);
}
void blank_edit(void)
{
int i;
for (i = 0; i <= editwinrows - 1; i++)
mvwaddstr(edit, i, 0, hblank);
}
void blank_statusbar(void)
{
mvwaddstr(bottomwin, 0, 0, hblank);
}
void blank_statusbar_refresh(void)
{
blank_statusbar();
wrefresh(bottomwin);
}
void check_statblank(void)
{
if (statblank > 1)
statblank--;
else if (statblank == 1 && !ISSET(CONSTUPDATE)) {
statblank--;
blank_statusbar_refresh();
}
}
/* Repaint the statusbar when getting a character in nanogetstr. buf
* should be no longer than COLS - 4.
*
* Note that we must turn on A_REVERSE here, since do_help turns it
* off! */
void nanoget_repaint(const char *buf, const char *inputbuf, int x)
{
int len = strlen(buf) + 2;
int wid = COLS - len;
assert(wid >= 2);
assert(0 <= x && x <= strlen(inputbuf));
wattron(bottomwin, A_REVERSE);
blank_statusbar();
mvwaddstr(bottomwin, 0, 0, buf);
waddch(bottomwin, ':');
waddch(bottomwin, x < wid ? ' ' : '$');
waddnstr(bottomwin, &inputbuf[wid * (x / wid)], wid);
wmove(bottomwin, 0, (x % wid) + len);
wattroff(bottomwin, A_REVERSE);
}
/* Get the input from the kb; this should only be called from
* statusq(). */
int nanogetstr(int allowtabs, const char *buf, const char *def,
const shortcut *s
#ifndef DISABLE_TABCOMP
, int *list
#endif
)
{
int kbinput;
int x;
/* the cursor position in 'answer' */
int xend;
/* length of 'answer', the status bar text */
int tabbed = 0;
/* used by input_tab() */
const shortcut *t;
xend = strlen(def);
x = xend;
answer = (char *)nrealloc(answer, xend + 1);
if (xend > 0)
strcpy(answer, def);
else
answer[0] = '\0';
#if !defined(DISABLE_HELP) || !defined(DISABLE_MOUSE)
currshortcut = s;
#endif
/* Get the input! */
nanoget_repaint(buf, answer, x);
/* Make sure any editor screen updates are displayed before getting
input */
wrefresh(edit);
while ((kbinput = wgetch(bottomwin)) != 13) {
for (t = s; t != NULL; t = t->next) {
#ifdef DEBUG
fprintf(stderr, _("Aha! \'%c\' (%d)\n"), kbinput, kbinput);
#endif
if (kbinput == t->val && kbinput < 32) {
#ifndef DISABLE_HELP
/* Have to do this here, it would be too late to do it
in statusq() */
if (kbinput == NANO_HELP_KEY || kbinput == NANO_HELP_FKEY) {
do_help();
break;
}
#endif
return t->val;
}
}
assert(0 <= x && x <= xend && xend == strlen(answer));
if (kbinput != '\t')
tabbed = 0;
switch (kbinput) {
/* Stuff we want to equate with <enter>, ASCII 13 */
case 343:
ungetch(13); /* Enter on iris-ansi $TERM, sometimes */
break;
/* Stuff we want to ignore */
#ifdef PDCURSES
case 541:
case 542:
case 543: /* Right ctrl again */
case 544:
case 545: /* Right alt again */
break;
#endif
#ifndef DISABLE_MOUSE
#ifdef NCURSES_MOUSE_VERSION
case KEY_MOUSE:
do_mouse();
break;
#endif
#endif
case NANO_HOME_KEY:
case KEY_HOME:
x = 0;
break;
case NANO_END_KEY:
case KEY_END:
x = xend;
break;
case KEY_RIGHT:
case NANO_FORWARD_KEY:
if (x < xend)
x++;
break;
case NANO_CONTROL_D:
if (x < xend) {
memmove(answer + x, answer + x + 1, xend - x);
xend--;
}
break;
case NANO_CONTROL_K:
case NANO_CONTROL_U:
null_at(&answer, 0);
xend = 0;
x = 0;
break;
case KEY_BACKSPACE:
case 127:
case NANO_CONTROL_H:
if (x > 0) {
memmove(answer + x - 1, answer + x, xend - x + 1);
x--;
xend--;
}
break;
#ifndef DISABLE_TABCOMP
case NANO_CONTROL_I:
if (allowtabs) {
int shift = 0;
answer = input_tab(answer, x, &tabbed, &shift, list);
xend = strlen(answer);
x += shift;
if (x > xend)
x = xend;
}
break;
#endif
case KEY_LEFT:
case NANO_BACK_KEY:
if (x > 0)
x--;
break;
case KEY_UP:
case KEY_DOWN:
break;
case KEY_DC:
goto do_deletekey;
case 27:
switch (kbinput = wgetch(edit)) {
case 'O':
switch (kbinput = wgetch(edit)) {
case 'F':
x = xend;
break;
case 'H':
x = 0;
break;
}
break;
case '[':
switch (kbinput = wgetch(edit)) {
case 'C':
if (x < xend)
x++;
break;
case 'D':
if (x > 0)
x--;
break;
case '1':
case '7':
x = 0;
goto skip_tilde;
case '3':
do_deletekey:
if (x < xend) {
memmove(answer + x, answer + x + 1, xend - x);
xend--;
}
goto skip_tilde;
case '4':
case '8':
x = xend;
goto skip_tilde;
skip_tilde:
nodelay(edit, TRUE);
kbinput = wgetch(edit);
if (kbinput == '~' || kbinput == ERR)
kbinput = -1;
nodelay(edit, FALSE);
break;
}
break;
default:
for (t = s; t != NULL; t = t->next) {
#ifdef DEBUG
fprintf(stderr, _("Aha! \'%c\' (%d)\n"), kbinput,
kbinput);
#endif
if (kbinput == t->val || kbinput == t->val - 32) {
/* We hit an Alt key. Do like above. We don't
just ungetch the letter and let it get caught
above cause that screws the keypad... */
return t->val;
}
}
}
break;
default:
if (kbinput < 32)
break;
answer = nrealloc(answer, xend + 2);
memmove(answer + x + 1, answer + x, xend - x + 1);
xend++;
answer[x] = kbinput;
x++;
#ifdef DEBUG
fprintf(stderr, _("input \'%c\' (%d)\n"), kbinput, kbinput);
#endif
}
nanoget_repaint(buf, answer, x);
wrefresh(bottomwin);
} /* while (kbinput ...) */
/* In Pico mode, just check for a blank answer here */
if (ISSET(PICO_MODE) && answer[0] == '\0')
return -2;
else
return 0;
}
/* If modified is not already set, set it and update titlebar. */
void set_modified(void)
{
if (!ISSET(MODIFIED)) {
SET(MODIFIED);
titlebar(NULL);
wrefresh(topwin);
}
}
void titlebar(const char *path)
{
int namelen, space;
const char *what = path;
if (path == NULL)
what = filename;
wattron(topwin, A_REVERSE);
mvwaddstr(topwin, 0, 0, hblank);
mvwaddnstr(topwin, 0, 2, VERMSG, COLS - 3);
space = COLS - sizeof(VERMSG) - 22;
namelen = strlen(what);
if (space > 0) {
if (what[0] == '\0')
mvwaddnstr(topwin, 0, COLS / 2 - 6, _("New Buffer"),
COLS / 2 + COLS % 2 - 6);
else if (namelen > space) {
if (path == NULL)
waddstr(topwin, _(" File: ..."));
else
waddstr(topwin, _(" DIR: ..."));
waddstr(topwin, &what[namelen - space]);
} else {
if (path == NULL)
mvwaddstr(topwin, 0, COLS / 2 - (namelen / 2 + 1),
_("File: "));
else
mvwaddstr(topwin, 0, COLS / 2 - (namelen / 2 + 1),
_(" DIR: "));
waddstr(topwin, what);
}
} /* If we don't have space, we shouldn't bother */
if (ISSET(MODIFIED))
mvwaddnstr(topwin, 0, COLS - 11, _(" Modified "), 11);
else if (ISSET(VIEW_MODE))
mvwaddnstr(topwin, 0, COLS - 11, _(" View "), 11);
wattroff(topwin, A_REVERSE);
wrefresh(topwin);
reset_cursor();
}
void bottombars(const shortcut *s)
{
int i, j, numcols;
char keystr[4];
int slen;
if (ISSET(NO_HELP))
return;
if (s == main_list) {
slen = MAIN_VISIBLE;
assert(MAIN_VISIBLE <= length_of_list(s));
} else
slen = length_of_list(s);
/* There will be this many columns of shortcuts */
numcols = (slen + (slen % 2)) / 2;
blank_bottomwin();
for (i = 0; i < numcols; i++) {
for (j = 0; j <= 1; j++) {
wmove(bottomwin, 1 + j, i * (COLS / numcols));
#ifndef NANO_SMALL
if (s->val == NANO_CONTROL_SPACE)
strcpy(keystr, "^ ");
else
#endif /* !NANO_SMALL */
if (s->val > 0) {
if (s->val < 64)
sprintf(keystr, "^%c", s->val + 64);
else
sprintf(keystr, "M-%c", s->val - 32);
} else if (s->altval > 0)
sprintf(keystr, "M-%c", s->altval);
onekey(keystr, s->desc, COLS / numcols);
s = s->next;
if (s == NULL)
goto break_completely_out;
}
}
break_completely_out:
wrefresh(bottomwin);
}
/* Write a shortcut key to the help area at the bottom of the window.
* keystroke is e.g. "^G" and desc is e.g. "Get Help".
* We are careful to write exactly len characters, even if len is
* very small and keystroke and desc are long. */
void onekey(const char *keystroke, const char *desc, int len)
{
wattron(bottomwin, A_REVERSE);
waddnstr(bottomwin, keystroke, len);
wattroff(bottomwin, A_REVERSE);
len -= strlen(keystroke);
if (len > 0) {
waddch(bottomwin, ' ');
len--;
waddnstr(bottomwin, desc, len);
len -= strlen(desc);
for (; len > 0; len--)
waddch(bottomwin, ' ');
}
}
/* And so start the display update routines. Given a column, this
* returns the "page" it is on. "page", in the case of the display
* columns, means which set of 80 characters is viewable (e.g. page 1
* shows from 1 to COLS). */
int get_page_from_virtual(int virtual)
{
int page = 2;
if (virtual <= COLS - 2)
return 1;
virtual -= (COLS - 2);
while (virtual > COLS - 2 - 7) {
virtual -= (COLS - 2 - 7);
page++;
}
return page;
}
/* The inverse of the above function */
int get_page_start_virtual(int page)
{
int virtual;
virtual = --page * (COLS - 7);
if (page)
virtual -= 2 * page - 1;
return virtual;
}
int get_page_end_virtual(int page)
{
return get_page_start_virtual(page) + COLS - 1;
}
int get_page_start(int column)
{
assert(COLS > 9);
return column < COLS - 1 ? 0 : column - 7 - (column - 8) % (COLS - 9);
}
/* Resets current_y, based on the position of current, and puts the
* cursor at (current_y, current_x). */
void reset_cursor(void)
{
const filestruct *ptr = edittop;
size_t x;
/* Yuck. This condition can be true after open_file when opening the
* first file. */
if (edittop == NULL)
return;
current_y = 0;
while (ptr != current && ptr != editbot && ptr->next != NULL) {
ptr = ptr->next;
current_y++;
}
x = xplustabs();
wmove(edit, current_y, x - get_page_start(x));
}
#ifndef NANO_SMALL
/* This takes care of the case where there is a mark that covers only
* the current line. It expects a line with no tab characters (i.e.
* the type that edit_add() deals with. */
void add_marked_sameline(int begin, int end, filestruct *fileptr, int y,
int virt_cur_x, int this_page)
{
/*
* The general idea is to break the line up into 3 sections: before
* the mark, the mark, and after the mark. We then paint each in
* turn (for those that are currently visible, of course)
*
* 3 start points: 0 -> begin, begin->end, end->strlen(data)
* in data : pre sel post
*/
int this_page_start = get_page_start_virtual(this_page),
this_page_end = get_page_end_virtual(this_page);
/* likewise, 3 data lengths */
int pre_data_len = begin, sel_data_len = end - begin, post_data_len = 0; /* Determined from the other two */
/* now fix the start locations & lengths according to the cursor's
* position (i.e.: our page) */
if (pre_data_len < this_page_start)
pre_data_len = 0;
else
pre_data_len -= this_page_start;
if (begin < this_page_start)
begin = this_page_start;
if (end < this_page_start)
end = this_page_start;
if (begin > this_page_end)
begin = this_page_end;
if (end > this_page_end)
end = this_page_end;
/* Now calculate the lengths */
sel_data_len = end - begin;
post_data_len = this_page_end - end;
wattron(edit, A_REVERSE);
mvwaddnstr(edit, y, begin - this_page_start,
&fileptr->data[begin], sel_data_len);
wattroff(edit, A_REVERSE);
}
#endif
/* edit_add() takes care of the job of actually painting a line into
* the edit window. Called only from update_line(). Expects a
* converted-to-not-have-tabs line. */
void edit_add(filestruct *fileptr, int yval, int start, int virt_cur_x,
int virt_mark_beginx, int this_page)
{
#ifdef ENABLE_COLOR
const colortype *tmpcolor = NULL;
int k, paintlen;
filestruct *e, *s;
regoff_t ematch, smatch;
#endif
/* Just paint the string in any case (we'll add color or reverse on
just the text that needs it */
mvwaddnstr(edit, yval, 0, &fileptr->data[start],
get_page_end_virtual(this_page) - start + 1);
#ifdef ENABLE_COLOR
if (colorstrings != NULL)
for (tmpcolor = colorstrings; tmpcolor != NULL;
tmpcolor = tmpcolor->next) {
if (tmpcolor->end == NULL) {
/* First, highlight all single-line regexes */
k = start;
regcomp(&color_regexp, tmpcolor->start, REG_EXTENDED);
while (!regexec(&color_regexp, &fileptr->data[k], 1,
colormatches, 0)) {
if (colormatches[0].rm_eo - colormatches[0].rm_so < 1) {
statusbar(_("Refusing 0 length regex match"));
break;
}
#ifdef DEBUG
fprintf(stderr, _("Match! (%d chars) \"%s\"\n"),
colormatches[0].rm_eo - colormatches[0].rm_so,
&fileptr->data[k + colormatches[0].rm_so]);
#endif
if (colormatches[0].rm_so < COLS - 1) {
if (tmpcolor->bright)
wattron(edit, A_BOLD);
wattron(edit, COLOR_PAIR(tmpcolor->pairnum));
if (colormatches[0].rm_eo + k <= COLS) {
paintlen =
colormatches[0].rm_eo - colormatches[0].rm_so;
#ifdef DEBUG
fprintf(stderr, _("paintlen (%d) = eo (%d) - so (%d)\n"),
paintlen, colormatches[0].rm_eo, colormatches[0].rm_so);
#endif
}
else {
paintlen = COLS - k - colormatches[0].rm_so - 1;
#ifdef DEBUG
fprintf(stderr, _("paintlen (%d) = COLS (%d) - k (%d), - rm.so (%d) - 1\n"),
paintlen, COLS, k, colormatches[0].rm_so);
#endif
}
mvwaddnstr(edit, yval, colormatches[0].rm_so + k,
&fileptr->data[k + colormatches[0].rm_so],
paintlen);
}
if (tmpcolor->bright)
wattroff(edit, A_BOLD);
wattroff(edit, COLOR_PAIR(tmpcolor->pairnum));
k += colormatches[0].rm_eo;
}
regfree(&color_regexp);
}
/* Now, if there's an 'end' somewhere below, and a 'start'
somewhere above, things get really fun. We have to look
down for an end, make sure there's not a start before
the end after us, and then look up for a start,
and see if there's an end after the start, before us :) */
else {
s = fileptr;
while (s != NULL) {
regcomp(&color_regexp, tmpcolor->start, REG_EXTENDED);
if (!regexec
(&color_regexp, s->data, 1, colormatches, 0)) {
regfree(&color_regexp);
break;
}
s = s->prev;
regfree(&color_regexp);
}
if (s != NULL) {
/* We found a start, mark it */
smatch = colormatches[0].rm_so;
e = s;
while (e != NULL && e != fileptr) {
regcomp(&color_regexp, tmpcolor->end, REG_EXTENDED);
if (!regexec
(&color_regexp, e->data, 1, colormatches, 0)) {
regfree(&color_regexp);
break;
}
e = e->next;
regfree(&color_regexp);
}
if (e != fileptr)
continue; /* There's an end before us */
else { /* Keep looking for an end */
while (e != NULL) {
regcomp(&color_regexp, tmpcolor->end, REG_EXTENDED);
if (!regexec
(&color_regexp, e->data, 1, colormatches,
0)) {
regfree(&color_regexp);
break;
}
e = e->next;
regfree(&color_regexp);
}
if (e == NULL)
continue; /* There's no start before the end :) */
else { /* Okay, we found an end, mark it! */
ematch = colormatches[0].rm_eo;
while (e != NULL) {
regcomp(&color_regexp, tmpcolor->end, REG_EXTENDED);
if (!regexec
(&color_regexp, e->data, 1,
colormatches, 0)) {
regfree(&color_regexp);
break;
} e = e->next;
regfree(&color_regexp);
}
if (e == NULL)
continue; /* No end, oh well :) */
/* Didn't find another end, we must be in the
middle of a highlighted bit */
if (tmpcolor->bright)
wattron(edit, A_BOLD);
wattron(edit, COLOR_PAIR(tmpcolor->pairnum));
if (s == fileptr && e == fileptr && ematch < COLS) {
mvwaddnstr(edit, yval, start + smatch,
&fileptr->data[start + smatch],
ematch - smatch);
#ifdef DEBUG
fprintf(stderr, _("start = %d, smatch = %d, ematch = %d\n"), start,
smatch, ematch);
#endif
} else if (s == fileptr)
mvwaddnstr(edit, yval, start + smatch,
&fileptr->data[start + smatch],
COLS - smatch);
else if (e == fileptr)
mvwaddnstr(edit, yval, start,
&fileptr->data[start],
COLS - start);
else
mvwaddnstr(edit, yval, start,
&fileptr->data[start],
COLS);
if (tmpcolor->bright)
wattroff(edit, A_BOLD);
wattroff(edit, COLOR_PAIR(tmpcolor->pairnum));
}
}
/* Else go to the next string, yahoo! =) */
}
}
}
#endif /* ENABLE_COLOR */
#ifndef NANO_SMALL
/* There are quite a few cases that could take place; we'll deal
* with them each in turn */
if (ISSET(MARK_ISSET) &&
!((fileptr->lineno > mark_beginbuf->lineno
&& fileptr->lineno > current->lineno)
|| (fileptr->lineno < mark_beginbuf->lineno
&& fileptr->lineno < current->lineno))) {
/* If we get here we are on a line that is at least
* partially selected. The lineno checks above determined
* that */
if (fileptr != mark_beginbuf && fileptr != current) {
/* We are on a completely marked line, paint it all
* inverse */
wattron(edit, A_REVERSE);
mvwaddnstr(edit, yval, 0, fileptr->data, COLS);
wattroff(edit, A_REVERSE);
} else if (fileptr == mark_beginbuf && fileptr == current) {
/* Special case, we're still on the same line we started
* marking -- so we call our helper function */
if (virt_cur_x < virt_mark_beginx) {
/* To the right of us is marked */
add_marked_sameline(virt_cur_x, virt_mark_beginx,
fileptr, yval, virt_cur_x, this_page);
} else {
/* To the left of us is marked */
add_marked_sameline(virt_mark_beginx, virt_cur_x,
fileptr, yval, virt_cur_x, this_page);
}
} else if (fileptr == mark_beginbuf) {
/*
* We're updating the line that was first marked,
* but we're not currently on it. So we want to
* figure out which half to invert based on our
* relative line numbers.
*
* I.e. if we're above the "beginbuf" line, we want to
* mark the left side. Otherwise, we're below, so we
* mark the right.
*/
int target;
if (mark_beginbuf->lineno > current->lineno) {
wattron(edit, A_REVERSE);
target =
(virt_mark_beginx <
COLS - 1) ? virt_mark_beginx : COLS - 1;
mvwaddnstr(edit, yval, 0, fileptr->data, target);
wattroff(edit, A_REVERSE);
}
if (mark_beginbuf->lineno < current->lineno) {
wattron(edit, A_REVERSE);
target = (COLS - 1) - virt_mark_beginx;
if (target < 0)
target = 0;
mvwaddnstr(edit, yval, virt_mark_beginx,
&fileptr->data[virt_mark_beginx], target);
wattroff(edit, A_REVERSE);
}
} else if (fileptr == current) {
/* We're on the cursor's line, but it's not the first
* one we marked. Similar to the previous logic. */
int this_page_start = get_page_start_virtual(this_page),
this_page_end = get_page_end_virtual(this_page);
if (mark_beginbuf->lineno < current->lineno) {
wattron(edit, A_REVERSE);
if (virt_cur_x > COLS - 2) {
mvwaddnstr(edit, yval, 0,
&fileptr->data[this_page_start],
virt_cur_x - this_page_start);
} else
mvwaddnstr(edit, yval, 0, fileptr->data, virt_cur_x);
wattroff(edit, A_REVERSE);
}
if (mark_beginbuf->lineno > current->lineno) {
wattron(edit, A_REVERSE);
if (virt_cur_x > COLS - 2)
mvwaddnstr(edit, yval, virt_cur_x - this_page_start,
&fileptr->data[virt_cur_x],
this_page_end - virt_cur_x);
else
mvwaddnstr(edit, yval, virt_cur_x,
&fileptr->data[virt_cur_x],
COLS - virt_cur_x);
wattroff(edit, A_REVERSE);
}
}
}
#endif
}
/*
* Just update one line in the edit buffer. Basically a wrapper for
* edit_add(). index gives us a place in the string to update starting
* from. Likely args are current_x or 0.
*/
void update_line(filestruct *fileptr, int index)
{
filestruct *filetmp;
int line = 0, col = 0;
int virt_cur_x = current_x, virt_mark_beginx = mark_beginx;
char *realdata, *tmp;
int i, pos, len, page;
if (!fileptr)
return;
/* First, blank out the line (at a minimum) */
for (filetmp = edittop; filetmp != fileptr && filetmp != editbot;
filetmp = filetmp->next)
line++;
mvwaddstr(edit, line, 0, hblank);
/* Next, convert all the tabs to spaces, so everything else is easy */
index = xpt(fileptr, index);
realdata = fileptr->data;
len = strlen(realdata);
fileptr->data = charalloc(xpt(fileptr, len) + 1);
pos = 0;
for (i = 0; i < len; i++) {
if (realdata[i] == '\t') {
do {
fileptr->data[pos++] = ' ';
if (i < current_x)
virt_cur_x++;
if (i < mark_beginx)
virt_mark_beginx++;
} while (pos % tabsize);
/* must decrement once to account for tab-is-one-character */
if (i < current_x)
virt_cur_x--;
if (i < mark_beginx)
virt_mark_beginx--;
} else if (realdata[i] == 127) {
/* Treat delete characters (ASCII 127's) as ^?'s */
fileptr->data[pos++] = '^';
fileptr->data[pos++] = '?';
if (i < current_x)
virt_cur_x++;
if (i < mark_beginx)
virt_mark_beginx++;
} else if (realdata[i] == 10) {
/* Treat newlines (ASCII 10's) embedded in a line as encoded
nulls (ASCII 0's); the line in question should be run
through unsunder() before reaching here */
fileptr->data[pos++] = '^';
fileptr->data[pos++] = '@';
if (i < current_x)
virt_cur_x++;
if (i < mark_beginx)
virt_mark_beginx++;
} else if (is_cntrl_char(realdata[i])) {
/* Treat control characters as ^symbol's */
fileptr->data[pos++] = '^';
fileptr->data[pos++] = realdata[i] + 64;
if (i < current_x)
virt_cur_x++;
if (i < mark_beginx)
virt_mark_beginx++;
} else {
fileptr->data[pos++] = realdata[i];
}
}
fileptr->data[pos] = '\0';
/* Now, paint the line */
if (current == fileptr && index > COLS - 2) {
/* This handles when the current line is beyond COLS */
/* It requires figuring out what page we're on */
page = get_page_from_virtual(index);
col = get_page_start_virtual(page);
edit_add(filetmp, line, col, virt_cur_x, virt_mark_beginx, page);
mvwaddch(edit, line, 0, '$');
if (strlenpt(fileptr->data) > get_page_end_virtual(page) + 1)
mvwaddch(edit, line, COLS - 1, '$');
} else {
/* It's not the current line means that it's at x=0 and page=1 */
/* If it is the current line, then we're in the same boat */
edit_add(filetmp, line, 0, virt_cur_x, virt_mark_beginx, 1);
if (strlenpt(&filetmp->data[col]) > COLS)
mvwaddch(edit, line, COLS - 1, '$');
}
/* Clean up our mess */
tmp = fileptr->data;
fileptr->data = realdata;
free(tmp);
}
/* This function updates current, based on where current_y is;
* reset_cursor() does the opposite. */
void update_cursor(void)
{
int i = 0;
#ifdef DEBUG
fprintf(stderr, _("Moved to (%d, %d) in edit buffer\n"), current_y,
current_x);
#endif
current = edittop;
while (i < current_y && current->next != NULL) {
current = current->next;
i++;
}
#ifdef DEBUG
fprintf(stderr, _("current->data = \"%s\"\n"), current->data);
#endif
}
void center_cursor(void)
{
current_y = editwinrows / 2;
wmove(edit, current_y, current_x);
}
/* Refresh the screen without changing the position of lines. */
void edit_refresh(void)
{
static int noloop = 0;
int nlines = 0, currentcheck = 0;
/* Neither of these conditions should occur, but they do. edittop is
* NULL when you open an existing file on the command line, and
* ENABLE_COLOR is defined. Yuck. */
if (current == NULL)
return;
if (edittop == NULL)
edittop = current;
/* Don't make the cursor jump around the scrrn whilst updating */
leaveok(edit, TRUE);
editbot = edittop;
while (nlines < editwinrows) {
update_line(editbot, current_x);
if (editbot == current)
currentcheck = 1;
nlines++;
if (editbot->next == NULL)
break;
editbot = editbot->next;
}
/* If noloop == 1, then we already did an edit_update without finishing
this function. So we don't run edit_update again */
if (!currentcheck && !noloop) {
/* Then current has run off the screen... */
edit_update(current, CENTER);
noloop = 1;
} else if (noloop)
noloop = 0;
while (nlines < editwinrows) {
mvwaddstr(edit, nlines, 0, hblank);
nlines++;
}
/* What the hell are we expecting to update the screen if this isn't
here? Luck?? */
wrefresh(edit);
leaveok(edit, FALSE);
}
/*
* Same as above, but touch the window first, so everything is redrawn.
*/
void edit_refresh_clearok(void)
{
clearok(edit, TRUE);
edit_refresh();
clearok(edit, FALSE);
}
/*
* Nice generic routine to update the edit buffer, given a pointer to the
* file struct =)
*/
void edit_update(filestruct *fileptr, topmidbotnone location)
{
if (fileptr == NULL)
return;
if (location != TOP) {
int goal = location == NONE ? current_y - 1 : editwinrows / 2;
for (; goal >= 0 && fileptr->prev != NULL; goal--)
fileptr = fileptr->prev;
}
edittop = fileptr;
fix_editbot();
edit_refresh();
}
/*
* Ask a question on the statusbar. Answer will be stored in answer
* global. Returns -1 on aborted enter, -2 on a blank string, and 0
* otherwise, the valid shortcut key caught. Def is any editable text we
* want to put up by default.
*
* New arg tabs tells whether or not to allow tab completion.
*/
int statusq(int tabs, const shortcut *s, const char *def,
const char *msg, ...)
{
va_list ap;
char *foo = charalloc(COLS - 3);
int ret;
#ifndef DISABLE_TABCOMP
int list = 0;
#endif
bottombars(s);
va_start(ap, msg);
vsnprintf(foo, COLS - 4, msg, ap);
va_end(ap);
foo[COLS - 4] = '\0';
#ifndef DISABLE_TABCOMP
ret = nanogetstr(tabs, foo, def, s, &list);
#else
ret = nanogetstr(tabs, foo, def, s);
#endif
free(foo);
switch (ret) {
case NANO_FIRSTLINE_KEY:
do_first_line();
break;
case NANO_LASTLINE_KEY:
do_last_line();
break;
case NANO_CANCEL_KEY:
ret = -1;
break;
default:
blank_statusbar();
}
#ifdef DEBUG
fprintf(stderr, _("I got \"%s\"\n"), answer);
#endif
#ifndef DISABLE_TABCOMP
/* if we've done tab completion, there might be a list of
filename matches on the edit window at this point; make sure
they're cleared off */
if (list)
edit_refresh();
#endif
return ret;
}
/*
* Ask a simple yes/no question on the statusbar. Returns 1 for Y, 0
* for N, 2 for All (if all is non-zero when passed in) and -1 for
* abort (^C).
*/
int do_yesno(int all, int leavecursor, const char *msg, ...)
{
va_list ap;
char foo[133];
int kbinput, ok = -1, i;
const char *yesstr; /* String of yes characters accepted */
const char *nostr; /* Same for no */
const char *allstr; /* And all, surprise! */
#ifndef DISABLE_MOUSE
#ifdef NCURSES_MOUSE_VERSION
MEVENT mevent;
#endif
#endif
/* Yes, no and all are strings of any length. Each string consists of
all characters accepted as a valid character for that value.
The first value will be the one displayed in the shortcuts. */
yesstr = _("Yy");
nostr = _("Nn");
allstr = _("Aa");
/* Write the bottom of the screen */
blank_bottomwin();
/* Remove gettext call for keybindings until we clear the thing up */
if (!ISSET(NO_HELP)) {
char shortstr[3]; /* Temp string for Y, N, A */
wmove(bottomwin, 1, 0);
sprintf(shortstr, " %c", yesstr[0]);
onekey(shortstr, _("Yes"), 16);
if (all) {
shortstr[1] = allstr[0];
onekey(shortstr, _("All"), 16);
}
wmove(bottomwin, 2, 0);
shortstr[1] = nostr[0];
onekey(shortstr, _("No"), 16);
onekey("^C", _("Cancel"), 16);
}
va_start(ap, msg);
vsnprintf(foo, 132, msg, ap);
va_end(ap);
wattron(bottomwin, A_REVERSE);
blank_statusbar();
mvwaddstr(bottomwin, 0, 0, foo);
wattroff(bottomwin, A_REVERSE);
wrefresh(bottomwin);
if (leavecursor == 1)
reset_cursor();
while (ok == -1) {
kbinput = wgetch(edit);
switch (kbinput) {
#ifndef DISABLE_MOUSE
#ifdef NCURSES_MOUSE_VERSION
case KEY_MOUSE:
/* Look ma! We get to duplicate lots of code from do_mouse!! */
if (getmouse(&mevent) == ERR)
break;
if (!wenclose(bottomwin, mevent.y, mevent.x) || ISSET(NO_HELP))
break;
mevent.y -= editwinrows + 3;
if (mevent.y < 0)
break;
else {
/* Rather than a bunch of if statements, set up a matrix
of possible return keystrokes based on the x and y
values */
char yesnosquare[2][2];
yesnosquare[0][0] = yesstr[0];
if (all)
yesnosquare[0][1] = allstr[0];
else
yesnosquare[0][1] = '\0';
yesnosquare[1][0] = nostr[0];
yesnosquare[1][1] = NANO_CONTROL_C;
ungetch(yesnosquare[mevent.y][mevent.x / (COLS / 6)]);
}
break;
#endif
#endif
case NANO_CONTROL_C:
ok = -2;
break;
default:
/* Look for the kbinput in the yes, no and (optimally) all str */
for (i = 0; yesstr[i] != 0 && yesstr[i] != kbinput; i++);
if (yesstr[i] != 0) {
ok = 1;
break;
}
for (i = 0; nostr[i] != 0 && nostr[i] != kbinput; i++);
if (nostr[i] != 0) {
ok = 0;
break;
}
if (all) {
for (i = 0; allstr[i] != 0 && allstr[i] != kbinput; i++);
if (allstr[i] != 0) {
ok = 2;
break;
}
}
}
}
/* Then blank the screen */
blank_statusbar_refresh();
if (ok == -2)
return -1;
else
return ok;
}
int total_refresh(void)
{
clearok(edit, TRUE);
clearok(topwin, TRUE);
clearok(bottomwin, TRUE);
wnoutrefresh(edit);
wnoutrefresh(topwin);
wnoutrefresh(bottomwin);
doupdate();
clearok(edit, FALSE);
clearok(topwin, FALSE);
clearok(bottomwin, FALSE);
edit_refresh();
titlebar(NULL);
return 1;
}
void display_main_list(void)
{
bottombars(main_list);
}
void statusbar(const char *msg, ...)
{
va_list ap;
char *foo;
int start_x = 0;
size_t foo_len;
assert(COLS >= 4);
foo = charalloc(COLS - 3);
va_start(ap, msg);
vsnprintf(foo, COLS - 3, msg, ap);
va_end(ap);
foo[COLS - 4] = '\0';
foo_len = strlen(foo);
start_x = (COLS - foo_len - 4) / 2;
/* Blank out line */
blank_statusbar();
wmove(bottomwin, 0, start_x);
wattron(bottomwin, A_REVERSE);
waddstr(bottomwin, "[ ");
waddstr(bottomwin, foo);
free(foo);
waddstr(bottomwin, " ]");
wattroff(bottomwin, A_REVERSE);
wrefresh(bottomwin);
if (ISSET(CONSTUPDATE))
statblank = 1;
else
statblank = 25;
}
int do_cursorpos(int constant)
{
filestruct *fileptr;
float linepct = 0.0, bytepct = 0.0, colpct = 0.0;
long i = 0, j = 0;
static long old_i = -1, old_totsize = -1;
if (current == NULL || fileage == NULL)
return 0;
if (old_i == -1)
old_i = i;
if (old_totsize == -1)
old_totsize = totsize;
colpct = 100 * (xplustabs() + 1) / (strlenpt(current->data) + 1);
for (fileptr = fileage; fileptr != current && fileptr != NULL;
fileptr = fileptr->next)
i += strlen(fileptr->data) + 1;
if (fileptr == NULL)
return -1;
i += current_x;
j = totsize;
if (totsize > 0)
bytepct = 100 * i / totsize;
if (totlines > 0)
linepct = 100 * current->lineno / totlines;
#ifdef DEBUG
fprintf(stderr, _("do_cursorpos: linepct = %f, bytepct = %f\n"),
linepct, bytepct);
#endif
/* if constant is zero, display the position on the statusbar
unconditionally; otherwise, only display the position when the
character values have changed */
if (!constant || (old_i != i || old_totsize != totsize)) {
statusbar(_
("line %d/%d (%.0f%%), col %ld/%ld (%.0f%%), char %ld/%ld (%.0f%%)"),
current->lineno, totlines, linepct, xplustabs() + 1,
strlenpt(current->data) + 1, colpct, i, j, bytepct);
}
old_i = i;
old_totsize = totsize;
reset_cursor();
return 1;
}
int do_cursorpos_void(void)
{
return do_cursorpos(0);
}
/* Our shortcut-list-compliant help function, which is
* better than nothing, and dynamic! */
int do_help(void)
{
#ifndef DISABLE_HELP
int i, j, row = 0, page = 1, kbinput = 0, no_more = 0, kp, kp2;
int no_help_flag = 0;
const shortcut *oldshortcut;
blank_edit();
curs_set(0);
wattroff(bottomwin, A_REVERSE);
blank_statusbar();
/* set help_text as the string to display */
help_init();
assert(help_text != NULL);
oldshortcut = currshortcut;
currshortcut = help_list;
kp = keypad_on(edit, 1);
kp2 = keypad_on(bottomwin, 1);
if (ISSET(NO_HELP)) {
/* Well, if we're going to do this, we should at least
do it the right way */
no_help_flag = 1;
UNSET(NO_HELP);
window_init();
bottombars(help_list);
} else
bottombars(help_list);
do {
const char *ptr = help_text;
switch (kbinput) {
#ifndef DISABLE_MOUSE
#ifdef NCURSES_MOUSE_VERSION
case KEY_MOUSE:
do_mouse();
break;
#endif
#endif
case 27:
kbinput = wgetch(edit);
switch(kbinput) {
case '[':
kbinput = wgetch(edit);
switch(kbinput) {
case '5': /* Alt-[-5 = Page Up */
wgetch(edit);
goto do_pageupkey;
break;
case 'V': /* Alt-[-V = Page Up in Hurd Console */
case 'I': /* Alt-[-I = Page Up - FreeBSD Console */
goto do_pageupkey;
break;
case '6': /* Alt-[-6 = Page Down */
wgetch(edit);
goto do_pagedownkey;
break;
case 'U': /* Alt-[-U = Page Down in Hurd Console */
case 'G': /* Alt-[-G = Page Down - FreeBSD Console */
goto do_pagedownkey;
break;
}
break;
}
break;
case NANO_NEXTPAGE_KEY:
case NANO_NEXTPAGE_FKEY:
case KEY_NPAGE:
do_pagedownkey:
if (!no_more) {
blank_edit();
page++;
}
break;
case NANO_PREVPAGE_KEY:
case NANO_PREVPAGE_FKEY:
case KEY_PPAGE:
do_pageupkey:
if (page > 1) {
no_more = 0;
blank_edit();
page--;
}
break;
}
/* Calculate where in the text we should be, based on the page */
for (i = 1; i < page; i++) {
row = 0;
j = 0;
while (row < editwinrows - 2 && *ptr != '\0') {
if (*ptr == '\n' || j == COLS - 5) {
j = 0;
row++;
}
ptr++;
j++;
}
}
i = 0;
j = 0;
while (i < editwinrows && *ptr != '\0') {
const char *end = ptr;
while (*end != '\n' && *end != '\0' && j != COLS - 5) {
end++;
j++;
}
if (j == COLS - 5) {
/* Don't print half a word if we've run out of space */
while (*end != ' ' && *end != '\0') {
end--;
j--;
}
}
mvwaddnstr(edit, i, 0, ptr, j);
j = 0;
i++;
if (*end == '\n')
end++;
ptr = end;
}
if (*ptr == '\0') {
no_more = 1;
continue;
}
} while ((kbinput = wgetch(edit)) != NANO_EXIT_KEY &&
kbinput != NANO_EXIT_FKEY);
currshortcut = oldshortcut;
if (no_help_flag) {
blank_bottombars();
wrefresh(bottomwin);
SET(NO_HELP);
window_init();
} else
bottombars(currshortcut);
curs_set(1);
edit_refresh();
kp = keypad_on(edit, kp);
kp2 = keypad_on(bottomwin, kp2);
/* The help_init() at the beginning allocated help_text, which has
now been written to screen. */
free(help_text);
help_text = NULL;
#elif defined(DISABLE_HELP)
nano_disabled_msg();
#endif
return 1;
}
int keypad_on(WINDOW * win, int newval)
{
/* This is taken right from aumix. Don't sue me. */
#ifdef HAVE_USEKEYPAD
int old = win->_use_keypad;
keypad(win, newval);
return old;
#else
keypad(win, newval);
return 1;
#endif /* HAVE_USEKEYPAD */
}
/* Highlight the current word being replaced or spell checked. */
void do_replace_highlight(int highlight_flag, const char *word)
{
char *highlight_word = NULL;
int x, y, word_len;
highlight_word =
mallocstrcpy(highlight_word, &current->data[current_x]);
#ifdef HAVE_REGEX_H
if (ISSET(USE_REGEXP))
/* if we're using regexps, the highlight is the length of the
search result, not the length of the regexp string */
word_len = regmatches[0].rm_eo - regmatches[0].rm_so;
else
#endif
word_len = strlen(word);
highlight_word[word_len] = '\0';
/* adjust output when word extends beyond screen */
x = xplustabs();
y = get_page_start(x) + COLS;
if ((COLS - (y - x) + word_len) > COLS) {
highlight_word[y - x - 1] = '$';
highlight_word[y - x] = '\0';
}
/* OK display the output */
reset_cursor();
if (highlight_flag)
wattron(edit, A_REVERSE);
waddstr(edit, highlight_word);
if (highlight_flag)
wattroff(edit, A_REVERSE);
free(highlight_word);
}
/* Fix editbot, based on the assumption that edittop is correct. */
void fix_editbot(void)
{
int i;
editbot = edittop;
for (i = 0; i < editwinrows && editbot->next != NULL; i++)
editbot = editbot->next;
}
#ifdef DEBUG
/* Dump the current file structure to stderr */
void dump_buffer(const filestruct *inptr) {
if (inptr == fileage)
fprintf(stderr, _("Dumping file buffer to stderr...\n"));
else if (inptr == cutbuffer)
fprintf(stderr, _("Dumping cutbuffer to stderr...\n"));
else
fprintf(stderr, _("Dumping a buffer to stderr...\n"));
while (inptr != NULL) {
fprintf(stderr, "(%d) %s\n", inptr->lineno, inptr->data);
inptr = inptr->next;
}
}
#endif /* DEBUG */
#ifdef DEBUG
void dump_buffer_reverse(void) {
const filestruct *fileptr = filebot;
while (fileptr != NULL) {
fprintf(stderr, "(%d) %s\n", fileptr->lineno, fileptr->data);
fileptr = fileptr->prev;
}
}
#endif /* DEBUG */
#ifdef NANO_EXTRA
#define CREDIT_LEN 52
#define XLCREDIT_LEN 8
void do_credits(void)
{
int i, j = 0, k, place = 0, start_x;
const char *what;
const char *xlcredits[XLCREDIT_LEN];
const char *credits[CREDIT_LEN] = {
"0", /* "The nano text editor" */
"1", /* "version" */
VERSION,
"",
"2", /* "Brought to you by:" */
"Chris Allegretta",
"Jordi Mallach",
"Adam Rogoyski",
"Rob Siemborski",
"Rocco Corsi",
"David Lawrence Ramsey",
"Ken Tyler",
"Sven Guckes",
"Florian K<>nig",
"Pauli Virtanen",
"Daniele Medri",
"Clement Laforet",
"Tedi Heriyanto",
"Bill Soudan",
"Christian Weisgerber",
"Erik Andersen",
"Big Gaute",
"Joshua Jensen",
"Ryan Krebs",
"Albert Chin",
"",
"3", /* "Special thanks to:" */
"Plattsburgh State University",
"Benet Laboratories",
"Amy Allegretta",
"Linda Young",
"Jeremy Robichaud",
"Richard Kolb II",
"4", /* "The Free Software Foundation" */
"Linus Torvalds",
"5", /* "For ncurses:" */
"Thomas Dickey",
"Pavel Curtis",
"Zeyd Ben-Halim",
"Eric S. Raymond",
"6", /* "and anyone else we forgot..." */
"7", /* "Thank you for using nano!\n" */
"", "", "", "",
"(c) 1999-2002 Chris Allegretta",
"", "", "", "",
"www.nano-editor.org"
};
xlcredits[0] = _("The nano text editor");
xlcredits[1] = _("version ");
xlcredits[2] = _("Brought to you by:");
xlcredits[3] = _("Special thanks to:");
xlcredits[4] = _("The Free Software Foundation");
xlcredits[5] = _("For ncurses:");
xlcredits[6] = _("and anyone else we forgot...");
xlcredits[7] = _("Thank you for using nano!\n");
curs_set(0);
nodelay(edit, TRUE);
blank_bottombars();
mvwaddstr(topwin, 0, 0, hblank);
blank_edit();
wrefresh(edit);
wrefresh(bottomwin);
wrefresh(topwin);
while (wgetch(edit) == ERR) {
for (k = 0; k <= 1; k++) {
blank_edit();
for (i = editwinrows / 2 - 1; i >= (editwinrows / 2 - 1 - j);
i--) {
mvwaddstr(edit, i * 2 - k, 0, hblank);
if (place - (editwinrows / 2 - 1 - i) < CREDIT_LEN) {
what = credits[place - (editwinrows / 2 - 1 - i)];
/* God I've missed hacking. If what is exactly
1 char long, it's a sentinel for a translated
string, so use that instead. This means no
thanking people with 1 character long names ;-) */
if (strlen(what) == 1)
what = xlcredits[atoi(what)];
} else
what = "";
start_x = COLS / 2 - strlen(what) / 2 - 1;
mvwaddstr(edit, i * 2 - k, start_x, what);
}
usleep(700000);
wrefresh(edit);
}
if (j < editwinrows / 2 - 1)
j++;
place++;
if (place >= CREDIT_LEN + editwinrows / 2)
break;
}
nodelay(edit, FALSE);
curs_set(1);
display_main_list();
total_refresh();
}
#endif