/* $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, ¤t->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