/* Move up one page. */ void do_page_up(void) { int i, skipped = 0; /* If there's less than a page of text left on the screen, put the * cursor at the beginning of the first line of the file, and then * update the edit window. */ if (openfile->current->lineno == 1 || ( #ifndef NANO_TINY !ISSET(SOFTWRAP) && #endif openfile->current->lineno <= editwinrows - 2)) { do_first_line(); return; } /* If we're not in smooth scrolling mode, put the cursor at the * beginning of the top line of the edit window, as Pico does. */ #ifndef NANO_TINY if (!ISSET(SMOOTH_SCROLL)) { #endif openfile->current = openfile->edittop; openfile->placewewant = openfile->current_y = 0; #ifndef NANO_TINY } #endif for (i = editwinrows - 2; i - skipped > 0 && openfile->current != openfile->fileage; i--) { openfile->current = openfile->current->prev; #ifndef NANO_TINY if (ISSET(SOFTWRAP) && openfile->current) { skipped += strlenpt(openfile->current->data) / COLS; #ifdef DEBUG fprintf(stderr, "do_page_up: i = %d, skipped = %d based on line %ld len %lu\n", i, skipped, (long)openfile->current->lineno, (unsigned long)strlenpt(openfile->current->data)); #endif } #endif } openfile->current_x = actual_x(openfile->current->data, openfile->placewewant); #ifdef DEBUG fprintf(stderr, "do_page_up: openfile->current->lineno = %lu, skipped = %d\n", (unsigned long)openfile->current->lineno, skipped); #endif /* Scroll the edit window up a page. */ edit_update(NONE); }
/* Move right one character. */ void do_right(void) { size_t was_column = xplustabs(); assert(openfile->current_x <= strlen(openfile->current->data)); if (openfile->current->data[openfile->current_x] != '\0') openfile->current_x = move_mbright(openfile->current->data, openfile->current_x); else if (openfile->current != openfile->filebot) { openfile->current_x = 0; #ifndef NANO_TINY if (ISSET(SOFTWRAP)) openfile->current_y -= strlenpt(openfile->current->data) / editwincols; #endif } openfile->placewewant = xplustabs(); if (need_horizontal_scroll(was_column, openfile->placewewant)) update_line(openfile->current, openfile->current_x); if (openfile->current_x == 0) do_down_void(); else ensure_line_is_visible(); }
/* Handle a mouse click on the statusbar prompt or the shortcut list. */ int do_statusbar_mouse(void) { int mouse_x, mouse_y; int retval = get_mouseinput(&mouse_x, &mouse_y, TRUE); /* We can click on the statusbar window text to move the cursor. */ if (retval == 0 && wmouse_trafo(bottomwin, &mouse_y, &mouse_x, FALSE)) { size_t start_col; assert(prompt != NULL); start_col = strlenpt(prompt) + 2; /* Move to where the click occurred. */ if (mouse_x >= start_col && mouse_y == 0) { size_t pww_save = statusbar_pww; statusbar_x = actual_x(answer, get_statusbar_page_start(start_col, start_col + statusbar_xplustabs()) + mouse_x - start_col); statusbar_pww = statusbar_xplustabs(); if (need_statusbar_update(pww_save)) update_statusbar_line(answer, statusbar_x); } } return retval; }
/* Move edittop to put it in range of current, keeping current in the * same place. location determines how we move it: if it's CENTER, we * center current, and if it's NONE, we put current current_y lines * below edittop. */ void edit_update(UpdateType location) { filestruct *foo = openfile->current; int goal; /* If location is CENTER, we move edittop up (editwinrows / 2) * lines. This puts current at the center of the screen. If * location is NONE, we move edittop up current_y lines if current_y * is in range of the screen, 0 lines if current_y is less than 0, * or (editwinrows - 1) lines if current_y is greater than * (editwinrows - 1). This puts current at the same place on the * screen as before, or at the top or bottom of the screen if * edittop is beyond either. */ if (location == CENTER) { goal = editwinrows / 2; } else { goal = openfile->current_y; /* Limit goal to (editwinrows - 1) lines maximum. */ if (goal > editwinrows - 1) { goal = editwinrows - 1; } } for (; goal > 0 && foo->prev != NULL; goal--) { foo = foo->prev; if (ISSET(SOFTWRAP) && foo) { goal -= strlenpt(foo->data) / COLS; } } openfile->edittop = foo; DEBUG_LOG("edit_udpate(), setting edittop to lineno " << openfile->edittop->lineno); compute_maxrows(); edit_refresh_needed = true; }
/* Repaint the statusbar when getting a character in * get_prompt_string(). The statusbar text line will be displayed * starting with curranswer[index]. */ void update_statusbar_line(const char *curranswer, size_t index) { size_t start_col, page_start; char *expanded; assert(prompt != NULL && index <= strlen(curranswer)); start_col = strlenpt(prompt) + 2; index = strnlenpt(curranswer, index); page_start = get_statusbar_page_start(start_col, start_col + index); if (interface_color_pair[TITLE_BAR].bright) wattron(bottomwin, A_BOLD); wattron(bottomwin, interface_color_pair[TITLE_BAR].pairnum); blank_statusbar(); mvwaddnstr(bottomwin, 0, 0, prompt, actual_x(prompt, COLS - 2)); waddch(bottomwin, ':'); waddch(bottomwin, (page_start == 0) ? ' ' : '$'); expanded = display_string(curranswer, page_start, COLS - start_col - 1, FALSE); waddstr(bottomwin, expanded); free(expanded); wattroff(bottomwin, A_BOLD); wattroff(bottomwin, interface_color_pair[TITLE_BAR].pairnum); statusbar_pww = statusbar_xplustabs(); reset_statusbar_cursor(); wnoutrefresh(bottomwin); }
/* Reset current_y, based on the position of current, and put the cursor * in the edit window at (current_y, current_x). */ void reset_cursor(void) { size_t xpt; /* If we haven't opened any files yet, put the cursor in the top * left corner of the edit window and get out. */ if (openfiles.size() == 0) { wmove(edit, 0, 0); return; } xpt = xplustabs(); if (ISSET(SOFTWRAP)) { filestruct *tmp; openfile->current_y = 0; for (tmp = openfile->edittop; tmp && tmp != openfile->current; tmp = tmp->next) { openfile->current_y += 1 + strlenpt(tmp->data) / COLS; } openfile->current_y += xplustabs() / COLS; if (openfile->current_y < editwinrows) { wmove(edit, openfile->current_y, xpt % COLS); } } else { openfile->current_y = openfile->current->lineno - openfile->edittop->lineno; if (openfile->current_y < editwinrows) { wmove(edit, openfile->current_y, xpt - get_page_start(xpt)); } } }
/* Return TRUE if we need an update after moving the cursor, and FALSE * otherwise. We need an update if pww_save and statusbar_pww are on * different pages. */ bool need_statusbar_update(size_t pww_save) { size_t start_col = strlenpt(prompt) + 2; return get_statusbar_page_start(start_col, start_col + pww_save) != get_statusbar_page_start(start_col, start_col + statusbar_pww); }
/* Put the cursor in the statusbar prompt at statusbar_x. */ void reset_statusbar_cursor(void) { size_t start_col = strlenpt(prompt) + 2; size_t xpt = statusbar_xplustabs(); wmove(bottomwin, 0, start_col + xpt - get_statusbar_page_start(start_col, start_col + xpt)); }
/* Make sure that the current line, when it is partially scrolled off the * screen in softwrap mode, is scrolled fully into view. */ void ensure_line_is_visible(void) { #ifndef NANO_TINY if (ISSET(SOFTWRAP) && strlenpt(openfile->current->data) / editwincols + openfile->current_y >= editwinrows) { adjust_viewport(ISSET(SMOOTH_SCROLL) ? FLOWING : CENTERING); refresh_needed = TRUE; } #endif }
/* Move up one page. */ void do_page_up(void) { int i, mustmove, skipped = 0; /* If the cursor is less than a page away from the top of the file, * put it at the beginning of the first line. */ if (openfile->current->lineno == 1 || (!ISSET(SOFTWRAP) && openfile->current->lineno <= editwinrows - 2)) { do_first_line(); return; } /* If we're not in smooth scrolling mode, put the cursor at the * beginning of the top line of the edit window, as Pico does. */ if (!ISSET(SMOOTH_SCROLL)) { openfile->current = openfile->edittop; openfile->placewewant = openfile->current_y = 0; } mustmove = (editwinrows < 3) ? 1 : editwinrows - 2; for (i = mustmove; i - skipped > 0 && openfile->current != openfile->fileage; i--) { openfile->current = openfile->current->prev; #ifndef NANO_TINY if (ISSET(SOFTWRAP) && openfile->current) { skipped += strlenpt(openfile->current->data) / editwincols; #ifdef DEBUG fprintf(stderr, "paging up: i = %d, skipped = %d based on line %ld len %lu\n", i, skipped, (long)openfile->current->lineno, (unsigned long)strlenpt(openfile->current->data)); #endif } #endif } openfile->current_x = actual_x(openfile->current->data, openfile->placewewant); /* Scroll the edit window up a page. */ adjust_viewport(STATIONARY); refresh_needed = TRUE; }
/* Display a message on the statusbar, and set disable_cursorpos to * true, so that the message won't be immediately overwritten if * constant cursor position display is on. */ void statusbar(const char *msg, ...) { va_list ap; char *bar, *foo; size_t start_x; bool old_whitespace; va_start(ap, msg); /* Curses mode is turned off. If we use wmove() now, it will muck * up the terminal settings. So we just use vfprintf(). */ if (isendwin()) { vfprintf(stderr, msg, ap); va_end(ap); return; } blank_statusbar(); old_whitespace = ISSET(WHITESPACE_DISPLAY); UNSET(WHITESPACE_DISPLAY); bar = charalloc(mb_cur_max() * (COLS - 3)); vsnprintf(bar, mb_cur_max() * (COLS - 3), msg, ap); va_end(ap); foo = display_string(bar, 0, COLS - 4, false); free(bar); if (old_whitespace) { SET(WHITESPACE_DISPLAY); } start_x = (COLS - strlenpt(foo) - 4) / 2; wmove(bottomwin, 0, start_x); set_color(bottomwin, interface_colors[STATUS_BAR]); waddstr(bottomwin, "[ "); waddstr(bottomwin, foo); free(foo); waddstr(bottomwin, " ]"); clear_color(bottomwin, interface_colors[STATUS_BAR]); wnoutrefresh(bottomwin); reset_cursor(); wnoutrefresh(edit); /* Leave the cursor at its position in the edit window, not in * the statusbar. */ disable_cursorpos = true; /* If we're doing quick statusbar blanking, and constant cursor * position display is off, blank the statusbar after only one * keystroke. Otherwise, blank it after twenty-six keystrokes, as * Pico does. */ statusblank = ISSET(QUICK_BLANK) && !ISSET(CONST_UPDATE) ? 1 : 26; }
/* If constant is true, we display the current cursor position only if * disable_cursorpos is false. Otherwise, we display it * unconditionally and set disable_cursorpos to false. If constant is * true and disable_cursorpos is true, we also set disable_cursorpos to * false, so that we leave the current statusbar alone this time, and * display the current cursor position next time. */ void do_cursorpos(bool constant) { filestruct *f; char c; size_t i, cur_xpt = xplustabs() + 1; size_t cur_lenpt = strlenpt(openfile->current->data) + 1; int linepct, colpct, charpct; assert(openfile->fileage != NULL && openfile->current != NULL); f = openfile->current->next; c = openfile->current->data[openfile->current_x]; openfile->current->next = NULL; openfile->current->data[openfile->current_x] = '\0'; i = get_totsize(openfile->fileage, openfile->current); openfile->current->data[openfile->current_x] = c; openfile->current->next = f; if (constant && disable_cursorpos) { disable_cursorpos = false; return; } /* Display the current cursor position on the statusbar, and set * disable_cursorpos to false. */ linepct = 100 * openfile->current->lineno / openfile->filebot->lineno; colpct = 100 * cur_xpt / cur_lenpt; charpct = (openfile->totsize == 0) ? 0 : 100 * i / openfile->totsize; statusbar( _("line %ld/%ld (%d%%), col %lu/%lu (%d%%), char %lu/%lu (%d%%)"), (long)openfile->current->lineno, (long)openfile->filebot->lineno, linepct, (unsigned long)cur_xpt, (unsigned long)cur_lenpt, colpct, (unsigned long)i, (unsigned long)openfile->totsize, charpct); disable_cursorpos = false; }
/* When edittop changes, try and figure out how many lines * we really have to work with (i.e. set maxrows) */ void compute_maxrows(void) { int n; filestruct *foo = openfile->edittop; if (!ISSET(SOFTWRAP)) { maxrows = editwinrows; return; } maxrows = 0; for (n = 0; n < editwinrows && foo; n++) { maxrows ++; n += strlenpt(foo->data) / COLS; foo = foo->next; } if (n < editwinrows) { maxrows += editwinrows - n; } DEBUG_LOG("compute_maxrows(): maxrows = " << maxrows); }
/* Highlight the current word being replaced or spell checked. We * expect word to have tabs and control characters expanded. */ void do_replace_highlight(bool highlight, const char *word) { size_t y = xplustabs(), word_len = strlenpt(word); y = get_page_start(y) + COLS - y; /* Now y is the number of columns that we can display on this * line. */ assert(y > 0); if (word_len > y) { y--; } reset_cursor(); wnoutrefresh(edit); if (highlight) { wattron(edit, highlight_attribute); } /* This is so we can show zero-length matches. */ if (word_len == 0) { waddch(edit, ' '); } else { waddnstr(edit, word, actual_x(word, y)); } if (word_len > y) { waddch(edit, '$'); } if (highlight) { wattroff(edit, highlight_attribute); } }
/* Just update one line in the edit buffer. This is basically a wrapper * for edit_draw(). The line will be displayed starting with * fileptr->data[index]. Likely arguments are current_x or zero. * Returns: Number of additiona lines consumed (needed for SOFTWRAP) */ int update_line(filestruct *fileptr, size_t index) { int line = 0; int extralinesused = 0; /* The line in the edit window that we want to update. */ char *converted; /* fileptr->data converted to have tabs and control characters * expanded. */ size_t page_start; filestruct *tmp; assert(fileptr != NULL); if (ISSET(SOFTWRAP)) { for (tmp = openfile->edittop; tmp && tmp != fileptr; tmp = tmp->next) { line += 1 + (strlenpt(tmp->data) / COLS); } } else { line = fileptr->lineno - openfile->edittop->lineno; } if (line < 0 || line >= editwinrows) { return 1; } /* First, blank out the line. */ blank_line(edit, line, 0, COLS); /* Next, convert variables that index the line to their equivalent * positions in the expanded line. */ if (ISSET(SOFTWRAP)) { index = 0; } else { index = strnlenpt(fileptr->data, index); } page_start = get_page_start(index); /* Expand the line, replacing tabs with spaces, and control * characters with their displayed forms. */ converted = display_string(fileptr->data, page_start, COLS, !ISSET(SOFTWRAP)); #ifdef DEBUG if (ISSET(SOFTWRAP) && strlen(converted) >= COLS - 2) { fprintf(stderr, "update_line(): converted(1) line = %s\n", converted); } #endif /* Paint the line. */ edit_draw(fileptr, converted, line, page_start); free(converted); if (!ISSET(SOFTWRAP)) { if (page_start > 0) { mvwaddch(edit, line, 0, '$'); } if (strlenpt(fileptr->data) > page_start + COLS) { mvwaddch(edit, line, COLS - 1, '$'); } } else { int full_length = strlenpt(fileptr->data); for (index += COLS; index <= full_length && line < editwinrows; index += COLS) { line++; DEBUG_LOG("update_line(): Softwrap code, moving to " << line << " index " << index); blank_line(edit, line, 0, COLS); /* Expand the line, replacing tabs with spaces, and control * characters with their displayed forms. */ converted = display_string(fileptr->data, index, COLS, !ISSET(SOFTWRAP)); if (ISSET(SOFTWRAP) && strlen(converted) >= COLS - 2) { DEBUG_LOG("update_line(): converted(2) line == " << converted); } /* Paint the line. */ edit_draw(fileptr, converted, line, index); free(converted); extralinesused++; } } return extralinesused; }
/* Scroll the edit window in the given direction and the given number * of lines, and draw new lines on the blank lines left after the * scrolling. direction is the direction to scroll, either UPWARD or * DOWNWARD, and nlines is the number of lines to scroll. We change * edittop, and assume that current and current_x are up to date. We * also assume that scrollok(edit) is false. */ void edit_scroll(ScrollDir direction, ssize_t nlines) { filestruct *foo; ssize_t i; bool do_redraw = need_screen_update(0); /* Don't bother scrolling less than one line. */ if (nlines < 1) { return; } /* Part 1: nlines is the number of lines we're going to scroll the * text of the edit window. */ /* Move the top line of the edit window up or down (depending on the * value of direction) nlines lines, or as many lines as we can if * there are fewer than nlines lines available. */ for (i = nlines; i > 0; i--) { if (direction == UPWARD) { if (openfile->edittop == openfile->fileage) { break; } openfile->edittop = openfile->edittop->prev; } else { if (openfile->edittop == openfile->filebot) { break; } openfile->edittop = openfile->edittop->next; } /* Don't over-scroll on long lines */ if (ISSET(SOFTWRAP) && (direction == UPWARD)) { ssize_t len = strlenpt(openfile->edittop->data) / COLS; i -= len; if (len > 0) { do_redraw = true; } } } /* Limit nlines to the number of lines we could scroll. */ nlines -= i; /* Don't bother scrolling zero lines or more than the number of * lines in the edit window minus one; in both cases, get out, and * call edit_refresh() beforehand if we need to. */ if (nlines == 0 || do_redraw || nlines >= editwinrows) { if (do_redraw || nlines >= editwinrows) { edit_refresh_needed = true; } return; } /* Scroll the text of the edit window up or down nlines lines, * depending on the value of direction. */ scrollok(edit, true); wscrl(edit, (direction == UPWARD) ? -nlines : nlines); scrollok(edit, false); /* Part 2: nlines is the number of lines in the scrolled region of * the edit window that we need to draw. */ /* If the top or bottom line of the file is now visible in the edit * window, we need to draw the entire edit window. */ if ((direction == UPWARD && openfile->edittop == openfile->fileage) || (direction == DOWNWARD && openfile->edittop->lineno + editwinrows - 1 >= openfile->filebot->lineno)) { nlines = editwinrows; } /* If the scrolled region contains only one line, and the line * before it is visible in the edit window, we need to draw it too. * If the scrolled region contains more than one line, and the lines * before and after the scrolled region are visible in the edit * window, we need to draw them too. */ nlines += (nlines == 1) ? 1 : 2; if (nlines > editwinrows) { nlines = editwinrows; } /* If we scrolled up, we're on the line before the scrolled region. */ foo = openfile->edittop; /* If we scrolled down, move down to the line before the scrolled region. */ if (direction == DOWNWARD) { for (i = editwinrows - nlines; i > 0 && foo != NULL; i--) { foo = foo->next; } } /* Draw new lines on any blank lines before or inside the scrolled * region. If we scrolled down and we're on the top line, or if we * scrolled up and we're on the bottom line, the line won't be * blank, so we don't need to draw it unless the mark is on or we're * not on the first page. */ for (i = nlines; i > 0 && foo != NULL; i--) { if ((i == nlines && direction == DOWNWARD) || (i == 1 && direction == UPWARD)) { if (do_redraw) update_line(foo, (foo == openfile->current) ? openfile->current_x : 0); } else { update_line(foo, (foo == openfile->current) ? openfile->current_x : 0); } foo = foo->next; } compute_maxrows(); }
/* If scroll_only is FALSE, move down one line. If scroll_only is TRUE, * scroll down one line without scrolling the cursor. */ void do_down( #ifndef NANO_TINY bool scroll_only #else void #endif ) { #ifndef NANO_TINY int amount = 0, enough; filestruct *topline; #endif /* If we're at the bottom of the file, get out. */ if (openfile->current == openfile->filebot || !openfile->current->next) return; assert(ISSET(SOFTWRAP) || openfile->current_y == openfile->current->lineno - openfile->edittop->lineno); /* Move the current line of the edit window down. */ openfile->current = openfile->current->next; openfile->current_x = actual_x(openfile->current->data, openfile->placewewant); #ifndef NANO_TINY if (ISSET(SOFTWRAP)) { /* Compute the amount to scroll. */ amount = (strlenpt(openfile->current->data) / COLS + openfile->current_y + 2 + strlenpt(openfile->current->prev->data) / COLS - editwinrows); topline = openfile->edittop; /* Reduce the amount when there are overlong lines at the top. */ for (enough = 1; enough < amount; enough++) { if (amount <= strlenpt(topline->data) / COLS) { amount = enough; break; } amount -= strlenpt(topline->data) / COLS; topline = topline->next; } } #endif /* If scroll_only is FALSE and if we're on the last line of the * edit window, scroll the edit window down one line if we're in * smooth scrolling mode, or down half a page if we're not. If * scroll_only is TRUE, scroll the edit window down one line * unconditionally. */ if (openfile->current_y == editwinrows - 1 #ifndef NANO_TINY || amount > 0 || scroll_only #endif ) { #ifndef NANO_TINY if (amount < 1 || scroll_only) amount = 1; #endif edit_scroll(DOWNWARD, #ifndef NANO_TINY (ISSET(SMOOTH_SCROLL) || scroll_only) ? amount : #endif editwinrows / 2 + 1); edit_refresh_needed = TRUE; } /* If we're above the last line of the edit window, update the line * we were on before and the line we're on now. The former needs to * be redrawn if we're not on the first page, and the latter needs * to be drawn unconditionally. */ if (openfile->current_y < editwinrows - 1 #ifndef NANO_TINY || ISSET(SOFTWRAP) #endif ) { if (need_screen_update(0)) update_line(openfile->current->prev, 0); update_line(openfile->current, openfile->current_x); } }
char *display_string(const char *buf, size_t start_col, size_t len, bool dollars) { size_t start_index; /* Index in buf of the first character shown. */ size_t column; /* Screen column that start_index corresponds to. */ size_t alloc_len; /* The length of memory allocated for converted. */ char *converted; /* The string we return. */ size_t index; /* Current position in converted. */ char *buf_mb; int buf_mb_len; /* If dollars is true, make room for the "$" at the end of the * line. */ if (dollars && len > 0 && strlenpt(buf) > start_col + len) { len--; } if (len == 0) { return mallocstrcpy(NULL, ""); } buf_mb = charalloc(mb_cur_max()); start_index = actual_x(buf, start_col); column = strnlenpt(buf, start_index); assert(column <= start_col); /* Make sure there's enough room for the initial character, whether * it's a multibyte control character, a non-control multibyte * character, a tab character, or a null terminator. Rationale: * * multibyte control character followed by a null terminator: * 1 byte ('^') + mb_cur_max() bytes + 1 byte ('\0') * multibyte non-control character followed by a null terminator: * mb_cur_max() bytes + 1 byte ('\0') * tab character followed by a null terminator: * mb_cur_max() bytes + (tabsize - 1) bytes + 1 byte ('\0') * * Since tabsize has a minimum value of 1, it can substitute for 1 * byte above. */ alloc_len = (mb_cur_max() + tabsize + 1) * MAX_BUF_SIZE; converted = charalloc(alloc_len); index = 0; if (buf[start_index] != '\0' && buf[start_index] != '\t' && (column < start_col || (dollars && column > 0))) { /* We don't display all of buf[start_index] since it starts to * the left of the screen. */ buf_mb_len = parse_mbchar(buf + start_index, buf_mb, NULL); if (is_cntrl_mbchar(buf_mb)) { if (column < start_col) { char *ctrl_buf_mb = charalloc(mb_cur_max()); int ctrl_buf_mb_len, i; ctrl_buf_mb = control_mbrep(buf_mb, ctrl_buf_mb, &ctrl_buf_mb_len); for (i = 0; i < ctrl_buf_mb_len; i++) { converted[index++] = ctrl_buf_mb[i]; } start_col += mbwidth(ctrl_buf_mb); free(ctrl_buf_mb); start_index += buf_mb_len; } } else if (using_utf8() && mbwidth(buf_mb) == 2) { if (column >= start_col) { converted[index++] = ' '; start_col++; } converted[index++] = ' '; start_col++; start_index += buf_mb_len; } } while (buf[start_index] != '\0') { buf_mb_len = parse_mbchar(buf + start_index, buf_mb, NULL); /* Make sure there's enough room for the next character, whether * it's a multibyte control character, a non-control multibyte * character, a tab character, or a null terminator. */ if (index + mb_cur_max() + tabsize + 1 >= alloc_len - 1) { alloc_len += (mb_cur_max() + tabsize + 1) * MAX_BUF_SIZE; converted = charealloc(converted, alloc_len); } /* If buf contains a tab character, interpret it. */ if (*buf_mb == '\t') { if (ISSET(WHITESPACE_DISPLAY)) { int i; for (i = 0; i < whitespace_len[0]; i++) { converted[index++] = whitespace[i]; } } else { converted[index++] = ' '; } start_col++; while (start_col % tabsize != 0) { converted[index++] = ' '; start_col++; } } else if (is_cntrl_mbchar(buf_mb)) { /* If buf contains a control character, interpret it. */ char *ctrl_buf_mb = charalloc(mb_cur_max()); int ctrl_buf_mb_len, i; converted[index++] = '^'; start_col++; ctrl_buf_mb = control_mbrep(buf_mb, ctrl_buf_mb, &ctrl_buf_mb_len); for (i = 0; i < ctrl_buf_mb_len; i++) { converted[index++] = ctrl_buf_mb[i]; } start_col += mbwidth(ctrl_buf_mb); free(ctrl_buf_mb); /* If buf contains a space character, interpret it. */ } else if (*buf_mb == ' ') { if (ISSET(WHITESPACE_DISPLAY)) { int i; for (i = whitespace_len[0]; i < whitespace_len[0] + whitespace_len[1]; i++) { converted[index++] = whitespace[i]; } } else { converted[index++] = ' '; } start_col++; } else { /* If buf contains a non-control character, interpret it. If buf * contains an invalid multibyte sequence, display it as such. */ char *nctrl_buf_mb = charalloc(mb_cur_max()); int nctrl_buf_mb_len, i; /* Make sure an invalid sequence-starter byte is properly * terminated, so that it doesn't pick up lingering bytes * of any previous content. */ null_at(&buf_mb, buf_mb_len); nctrl_buf_mb = mbrep(buf_mb, nctrl_buf_mb, &nctrl_buf_mb_len); for (i = 0; i < nctrl_buf_mb_len; i++) { converted[index++] = nctrl_buf_mb[i]; } start_col += mbwidth(nctrl_buf_mb); free(nctrl_buf_mb); } start_index += buf_mb_len; } free(buf_mb); assert(alloc_len >= index + 1); /* Null-terminate converted. */ converted[index] = '\0'; /* Make sure converted takes up no more than len columns. */ index = actual_x(converted, len); null_at(&converted, index); return converted; }
/* Set up the system variables for a search or replace. If use_answer * is TRUE, only set backupstring to answer. Return -2 to run the * opposite program (search -> replace, replace -> search), return -1 if * the search should be canceled (due to Cancel, a blank search string, * Go to Line, or a failed regcomp()), return 0 on success, and return 1 * on rerun calling program. * * replacing is TRUE if we call from do_replace(), and FALSE if called * from do_search(). */ int search_init(bool replacing, bool use_answer) { int i = 0; char *buf; static char *backupstring = NULL; /* The search string we'll be using. */ /* If backupstring doesn't exist, initialize it to "". */ if (backupstring == NULL) backupstring = mallocstrcpy(NULL, ""); /* If use_answer is TRUE, set backupstring to answer and get out. */ if (use_answer) { backupstring = mallocstrcpy(backupstring, answer); return 0; } /* We display the search prompt below. If the user types a partial * search string and then Replace or a toggle, we will return to * do_search() or do_replace() and be called again. In that case, * we should put the same search string back up. */ focusing = TRUE; if (last_search[0] != '\0') { char *disp = display_string(last_search, 0, COLS / 3, FALSE); buf = charalloc(strlen(disp) + 7); /* We use (COLS / 3) here because we need to see more on the line. */ sprintf(buf, " [%s%s]", disp, (strlenpt(last_search) > COLS / 3) ? "..." : ""); free(disp); } else buf = mallocstrcpy(NULL, ""); /* This is now one simple call. It just does a lot. */ i = do_prompt(FALSE, #ifndef DISABLE_TABCOMP TRUE, #endif replacing ? MREPLACE : MWHEREIS, backupstring, #ifndef DISABLE_HISTORIES &search_history, #endif /* TRANSLATORS: This is the main search prompt. */ edit_refresh, "%s%s%s%s%s%s", _("Search"), #ifndef NANO_TINY /* TRANSLATORS: The next three strings are modifiers of the search prompt. */ ISSET(CASE_SENSITIVE) ? _(" [Case Sensitive]") : #endif "", #ifdef HAVE_REGEX_H ISSET(USE_REGEXP) ? _(" [Regexp]") : #endif "", #ifndef NANO_TINY ISSET(BACKWARDS_SEARCH) ? _(" [Backwards]") : #endif "", replacing ? #ifndef NANO_TINY /* TRANSLATORS: The next two strings are modifiers of the search prompt. */ openfile->mark_set ? _(" (to replace) in selection") : #endif _(" (to replace)") : "", buf); fflush(stderr); /* Release buf now that we don't need it anymore. */ free(buf); free(backupstring); backupstring = NULL; /* Cancel any search, or just return with no previous search. */ if (i == -1 || (i < 0 && *last_search == '\0') || (!replacing && i == 0 && *answer == '\0')) { statusbar(_("Cancelled")); return -1; } else { functionptrtype func = func_from_key(&i); if (i == -2 || i == 0 ) { #ifdef HAVE_REGEX_H /* Use last_search if answer is an empty string, or * answer if it isn't. */ if (ISSET(USE_REGEXP) && !regexp_init((i == -2) ? last_search : answer)) return -1; #endif ; #ifndef NANO_TINY } else if (func == case_sens_void) { TOGGLE(CASE_SENSITIVE); backupstring = mallocstrcpy(backupstring, answer); return 1; } else if (func == backwards_void) { TOGGLE(BACKWARDS_SEARCH); backupstring = mallocstrcpy(backupstring, answer); return 1; #endif #ifdef HAVE_REGEX_H } else if (func == regexp_void) { TOGGLE(USE_REGEXP); backupstring = mallocstrcpy(backupstring, answer); return 1; #endif } else if (func == do_replace || func == flip_replace_void) { backupstring = mallocstrcpy(backupstring, answer); return -2; /* Call the opposite search function. */ } else if (func == do_gotolinecolumn_void) { do_gotolinecolumn(openfile->current->lineno, openfile->placewewant + 1, TRUE, TRUE); /* Put answer up on the statusbar and * fall through. */ return 3; } else { return -1; } } return 0; }
/* Parse the rcfile, once it has been opened successfully at rcstream, * and close it afterwards. If syntax_only is TRUE, only allow the file * to contain color syntax commands: syntax, color, and icolor. */ void parse_rcfile(FILE *rcstream #ifdef ENABLE_COLOR , bool syntax_only #endif ) { char *buf = NULL; ssize_t len; size_t n; while ((len = getline(&buf, &n, rcstream)) > 0) { char *ptr, *keyword, *option; int set = 0; size_t i; /* Ignore the newline. */ if (buf[len - 1] == '\n') buf[len - 1] = '\0'; lineno++; ptr = buf; while (isblank(*ptr)) ptr++; /* If we have a blank line or a comment, skip to the next * line. */ if (*ptr == '\0' || *ptr == '#') continue; /* Otherwise, skip to the next space. */ keyword = ptr; ptr = parse_next_word(ptr); /* Try to parse the keyword. */ if (strcasecmp(keyword, "set") == 0) { #ifdef ENABLE_COLOR if (syntax_only) rcfile_error( N_("Command \"%s\" not allowed in included file"), keyword); else #endif set = 1; } else if (strcasecmp(keyword, "unset") == 0) { #ifdef ENABLE_COLOR if (syntax_only) rcfile_error( N_("Command \"%s\" not allowed in included file"), keyword); else #endif set = -1; } #ifdef ENABLE_COLOR else if (strcasecmp(keyword, "include") == 0) { if (syntax_only) rcfile_error( N_("Command \"%s\" not allowed in included file"), keyword); else parse_include(ptr); } else if (strcasecmp(keyword, "syntax") == 0) { if (endsyntax != NULL && endcolor == NULL) rcfile_error(N_("Syntax \"%s\" has no color commands"), endsyntax->desc); parse_syntax(ptr); } else if (strcasecmp(keyword, "header") == 0) parse_headers(ptr); else if (strcasecmp(keyword, "color") == 0) parse_colors(ptr, FALSE); else if (strcasecmp(keyword, "icolor") == 0) parse_colors(ptr, TRUE); else if (strcasecmp(keyword, "bind") == 0) parse_keybinding(ptr); #endif /* ENABLE_COLOR */ else rcfile_error(N_("Command \"%s\" not understood"), keyword); if (set == 0) continue; if (*ptr == '\0') { rcfile_error(N_("Missing flag")); continue; } option = ptr; ptr = parse_next_word(ptr); for (i = 0; rcopts[i].name != NULL; i++) { if (strcasecmp(option, rcopts[i].name) == 0) { #ifdef DEBUG fprintf(stderr, "parse_rcfile(): name = \"%s\"\n", rcopts[i].name); #endif if (set == 1) { if (rcopts[i].flag != 0) /* This option has a flag, so it doesn't take an * argument. */ SET(rcopts[i].flag); else { /* This option doesn't have a flag, so it takes * an argument. */ if (*ptr == '\0') { rcfile_error( N_("Option \"%s\" requires an argument"), rcopts[i].name); break; } option = ptr; if (*option == '"') option++; ptr = parse_argument(ptr); option = mallocstrcpy(NULL, option); #ifdef DEBUG fprintf(stderr, "option = \"%s\"\n", option); #endif /* Make sure option is a valid multibyte * string. */ if (!is_valid_mbstring(option)) { rcfile_error( N_("Option is not a valid multibyte string")); break; } #ifndef DISABLE_OPERATINGDIR if (strcasecmp(rcopts[i].name, "operatingdir") == 0) operating_dir = option; else #endif #ifndef DISABLE_WRAPJUSTIFY if (strcasecmp(rcopts[i].name, "fill") == 0) { if (!parse_num(option, &wrap_at)) { rcfile_error( N_("Requested fill size \"%s\" is invalid"), option); wrap_at = -CHARS_FROM_EOL; } else free(option); } else #endif #ifndef NANO_TINY if (strcasecmp(rcopts[i].name, "matchbrackets") == 0) { matchbrackets = option; if (has_blank_mbchars(matchbrackets)) { rcfile_error( N_("Non-blank characters required")); free(matchbrackets); matchbrackets = NULL; } } else if (strcasecmp(rcopts[i].name, "whitespace") == 0) { whitespace = option; if (mbstrlen(whitespace) != 2 || strlenpt(whitespace) != 2) { rcfile_error( N_("Two single-column characters required")); free(whitespace); whitespace = NULL; } else { whitespace_len[0] = parse_mbchar(whitespace, NULL, NULL); whitespace_len[1] = parse_mbchar(whitespace + whitespace_len[0], NULL, NULL); } } else #endif #ifndef DISABLE_JUSTIFY if (strcasecmp(rcopts[i].name, "punct") == 0) { punct = option; if (has_blank_mbchars(punct)) { rcfile_error( N_("Non-blank characters required")); free(punct); punct = NULL; } } else if (strcasecmp(rcopts[i].name, "brackets") == 0) { brackets = option; if (has_blank_mbchars(brackets)) { rcfile_error( N_("Non-blank characters required")); free(brackets); brackets = NULL; } } else if (strcasecmp(rcopts[i].name, "quotestr") == 0) quotestr = option; else #endif #ifndef NANO_TINY if (strcasecmp(rcopts[i].name, "backupdir") == 0) backup_dir = option; else #endif #ifndef DISABLE_SPELLER if (strcasecmp(rcopts[i].name, "speller") == 0) alt_speller = option; else #endif if (strcasecmp(rcopts[i].name, "tabsize") == 0) { if (!parse_num(option, &tabsize) || tabsize <= 0) { rcfile_error( N_("Requested tab size \"%s\" is invalid"), option); tabsize = -1; } else free(option); } else assert(FALSE); } #ifdef DEBUG fprintf(stderr, "flag = %ld\n", rcopts[i].flag); #endif } else if (rcopts[i].flag != 0) UNSET(rcopts[i].flag); else rcfile_error(N_("Cannot unset flag \"%s\""), rcopts[i].name); break; } } if (rcopts[i].name == NULL) rcfile_error(N_("Unknown flag \"%s\""), option); } #ifdef ENABLE_COLOR if (endsyntax != NULL && endcolor == NULL) rcfile_error(N_("Syntax \"%s\" has no color commands"), endsyntax->desc); #endif free(buf); fclose(rcstream); lineno = 0; check_vitals_mapped(); return; }
/* Set width to the number of files that we can display per line, if * necessary, and display the list of files. */ void browser_refresh(void) { size_t i; int line = 0, col = 0; /* The current line and column while the list is getting displayed. */ char *foo; /* The additional information that we'll display about a file. */ /* Perhaps window dimensions have changed; reinitialize the browser. */ browser_init(path_save, opendir(path_save)); qsort(filelist, filelist_len, sizeof(char *), diralphasort); /* Make sure the selected file is within range. */ if (selected >= filelist_len) selected = filelist_len - 1; titlebar(path_save); blank_edit(); wmove(edit, 0, 0); i = width * editwinrows * ((selected / width) / editwinrows); for (; i < filelist_len && line < editwinrows; i++) { struct stat st; const char *filetail = tail(filelist[i]); /* The filename we display, minus the path. */ size_t filetaillen = strlenpt(filetail); /* The length of the filename in columns. */ size_t foolen; /* The length of the file information in columns. */ int foomaxlen = 7; /* The maximum length of the file information in * columns: seven for "--", "(dir)", or the file size, * and 12 for "(parent dir)". */ bool dots = (COLS >= 15 && filetaillen >= longest - foomaxlen); /* Do we put an ellipsis before the filename? Don't set * this to TRUE if we have fewer than 15 columns (i.e. * one column for padding, plus seven columns for a * filename other than ".."). */ char *disp = display_string(filetail, dots ? filetaillen - longest + foomaxlen + 4 : 0, longest, FALSE); /* If we put an ellipsis before the filename, reserve * one column for padding, plus seven columns for "--", * "(dir)", or the file size, plus three columns for the * ellipsis. */ /* Start highlighting the currently selected file or directory. */ if (i == selected) wattron(edit, hilite_attribute); blank_line(edit, line, col, longest); /* If dots is TRUE, we will display something like "...ename". */ if (dots) mvwaddstr(edit, line, col, "..."); mvwaddstr(edit, line, dots ? col + 3 : col, disp); free(disp); col += longest; /* Show information about the file. We don't want to report * file sizes for links, so we use lstat(). */ if (lstat(filelist[i], &st) == -1 || S_ISLNK(st.st_mode)) { /* If the file doesn't exist (i.e. it's been deleted while * the file browser is open), or it's a symlink that doesn't * point to a directory, display "--". */ if (stat(filelist[i], &st) == -1 || !S_ISDIR(st.st_mode)) foo = mallocstrcpy(NULL, "--"); /* If the file is a symlink that points to a directory, * display it as a directory. */ else /* TRANSLATORS: Try to keep this at most 7 characters. */ foo = mallocstrcpy(NULL, _("(dir)")); } else if (S_ISDIR(st.st_mode)) { /* If the file is a directory, display it as such. */ if (strcmp(filetail, "..") == 0) { /* TRANSLATORS: Try to keep this at most 12 characters. */ foo = mallocstrcpy(NULL, _("(parent dir)")); foomaxlen = 12; } else foo = mallocstrcpy(NULL, _("(dir)")); } else { unsigned long result = st.st_size; char modifier; foo = charalloc(foomaxlen + 1); if (st.st_size < (1 << 10)) modifier = ' '; /* bytes */ else if (st.st_size < (1 << 20)) { result >>= 10; modifier = 'K'; /* kilobytes */ } else if (st.st_size < (1 << 30)) { result >>= 20; modifier = 'M'; /* megabytes */ } else {
/* Set filelist to the list of files contained in the directory path, * set filelist_len to the number of files in that list, set longest to * the width in columns of the longest filename in that list (between 15 * and COLS), and set width to the number of files that we can display * per line. longest needs to be at least 15 columns in order to * display ".. (parent dir)", as Pico does. Assume path exists and is a * directory. */ void browser_init(const char *path, DIR *dir) { const struct dirent *nextdir; size_t i = 0, path_len = strlen(path); int col = 0; /* The maximum number of columns that the filenames will take * up. */ int line = 0; /* The maximum number of lines that the filenames will take * up. */ int filesperline = 0; /* The number of files that we can display per line. */ assert(path != NULL && path[strlen(path) - 1] == '/' && dir != NULL); /* Set longest to zero, just before we initialize it. */ longest = 0; while ((nextdir = readdir(dir)) != NULL) { size_t d_len; /* Don't show the "." entry. */ if (strcmp(nextdir->d_name, ".") == 0) continue; d_len = strlenpt(nextdir->d_name); if (d_len > longest) longest = (d_len > COLS) ? COLS : d_len; i++; } rewinddir(dir); /* Put 10 columns' worth of blank space between columns of filenames * in the list whenever possible, as Pico does. */ longest += 10; if (filelist != NULL) free_chararray(filelist, filelist_len); filelist_len = i; filelist = (char **)nmalloc(filelist_len * sizeof(char *)); i = 0; while ((nextdir = readdir(dir)) != NULL && i < filelist_len) { /* Don't show the "." entry. */ if (strcmp(nextdir->d_name, ".") == 0) continue; filelist[i] = charalloc(path_len + strlen(nextdir->d_name) + 1); sprintf(filelist[i], "%s%s", path, nextdir->d_name); i++; } /* Maybe the number of files in the directory changed between the * first time we scanned and the second. i is the actual length of * filelist, so record it. */ filelist_len = i; closedir(dir); /* Make sure longest is between 15 and COLS. */ if (longest < 15) longest = 15; if (longest > COLS) longest = COLS; /* Set width to zero, just before we initialize it. */ width = 0; for (i = 0; i < filelist_len && line < editwinrows; i++) { /* Calculate the number of columns one filename will take up. */ col += longest; filesperline++; /* Add some space between the columns. */ col += 2; /* If the next entry isn't going to fit on the current line, * move to the next line. */ if (col > COLS - longest) { line++; col = 0; /* If width isn't initialized yet, and we've taken up more * than one line, it means that width is equal to * filesperline. */ if (width == 0) width = filesperline; } } /* If width isn't initialized yet, and we've taken up only one line, * it means that width is equal to longest. */ if (width == 0) width = longest; }
/* Set filelist to the list of files contained in the directory path, * set longest to the width in columns of the longest filename in that * list (between 15 and COLS), and set width to the number of files that * we can display per line. longest needs to be at least 15 columns in * order to display ".. (parent dir)", as Pico does. Assume path exists * and is a directory. */ void browser_init(const std::string& path, DIR *dir) { const struct dirent *nextdir; int col = 0; /* The maximum number of columns that the filenames will take * up. */ int line = 0; /* The maximum number of lines that the filenames will take * up. */ int filesperline = 0; /* The number of files that we can display per line. */ assert(path.length() > 0 && path.back() == '/' && dir != NULL); /* Set longest to zero, just before we initialize it. */ longest = 0; while ((nextdir = readdir(dir)) != NULL) { size_t d_len; /* Don't show the "." entry. */ if (strcmp(nextdir->d_name, ".") == 0) { continue; } d_len = strlenpt(nextdir->d_name); if (d_len > longest) { longest = (d_len > COLS) ? COLS : d_len; } } rewinddir(dir); /* Put 10 columns' worth of blank space between columns of filenames * in the list whenever possible, as Pico does. */ longest += 10; filelist.clear(); while ((nextdir = readdir(dir)) != NULL) { /* Don't show the "." entry. */ if (strcmp(nextdir->d_name, ".") == 0) { continue; } filelist.push_back(std::string(path) + std::string(nextdir->d_name)); } closedir(dir); /* Make sure longest is between 15 and COLS. */ if (longest < 15) { longest = 15; } if (longest > COLS) { longest = COLS; } /* Set width to zero, just before we initialize it. */ width = 0; for (size_t i = 0; i < filelist.size() && line < editwinrows; i++) { /* Calculate the number of columns one filename will take up. */ col += longest; filesperline++; /* Add some space between the columns. */ col += 2; /* If the next entry isn't going to fit on the current line, * move to the next line. */ if (col > COLS - longest) { line++; col = 0; /* If width isn't initialized yet, and we've taken up more * than one line, it means that width is equal to * filesperline. */ if (width == 0) { width = filesperline; } } } /* If width isn't initialized yet, and we've taken up only one line, * it means that width is equal to longest. */ if (width == 0) { width = longest; } }
void titlebar(const char *path) { int space = COLS; /* The space we have available for display. */ size_t verlen = strlenpt(PACKAGE_STRING) + 1; /* The length of the version message in columns, plus one for * padding. */ const char *prefix; /* "DIR:", "File:", or "New Buffer". Goes before filename. */ size_t prefixlen; /* The length of the prefix in columns, plus one for padding. */ const char *state; /* "Modified", "View", or "". Shows the state of this * buffer. */ ssize_t statelen = 0; /* The length of the state in columns, or the length of * "Modified" if the state is blank and we're not in the file * browser. */ char *exppath = NULL; /* The filename, expanded for display. */ bool newfie = false; /* Do we say "New Buffer"? */ bool dots = false; /* Do we put an ellipsis before the path? */ set_color(topwin, interface_colors[TITLE_BAR]); blank_titlebar(); /* space has to be at least 4: two spaces before the version message, * at least one character of the version message, and one space * after the version message. */ if (space < 4) { space = 0; } else { /* Limit verlen to 1/3 the length of the screen in columns, * minus three columns for spaces. */ if (verlen > (COLS / 3) - 3) { verlen = (COLS / 3) - 3; } } if (space >= 4) { /* Add a space after the version message, and account for both * it and the two spaces before it. */ mvwaddnstr(topwin, 0, 2, PACKAGE_STRING, actual_x(PACKAGE_STRING, verlen)); verlen += 3; /* Account for the full length of the version message. */ space -= verlen; } /* Don't display the state if we're in the file browser. */ if (path != NULL) { state = ""; } else { state = openfile->modified ? _("Modified") : ISSET(VIEW_MODE) ? _("View") : ""; } statelen = strlenpt((*state == '\0' && path == NULL) ? _("Modified") : state); /* If possible, add a space before state. */ if (space > 0 && statelen < space) { statelen++; } else { goto the_end; } /* path should be a directory if we're in the file browser. */ if (path != NULL) { prefix = _("DIR:"); } else { if (openfile->filename[0] == '\0') { prefix = _("New Buffer"); newfie = true; } else { prefix = _("File:"); } } prefixlen = strnlenpt(prefix, space - statelen) + 1; /* If newfie is false, add a space after prefix. */ if (!newfie && prefixlen + statelen < space) { prefixlen++; } /* If we're not in the file browser, set path to the current * filename. */ if (path == NULL) { path = openfile->filename.c_str(); } /* Account for the full lengths of the prefix and the state. */ if (space >= prefixlen + statelen) { space -= prefixlen + statelen; } else { space = 0; } /* space is now the room we have for the filename. */ if (!newfie) { size_t lenpt = strlenpt(path), start_col; /* Don't set dots to true if we have fewer than eight columns * (i.e. one column for padding, plus seven columns for a * filename). */ dots = (space >= 8 && lenpt >= space); if (dots) { start_col = lenpt - space + 3; space -= 3; } else { start_col = 0; } exppath = display_string(path, start_col, space, false); } /* If dots is true, we will display something like "File: * ...ename". */ if (dots) { mvwaddnstr(topwin, 0, verlen - 1, prefix, actual_x(prefix, prefixlen)); if (space <= -3 || newfie) { goto the_end; } waddch(topwin, ' '); waddnstr(topwin, "...", space + 3); if (space <= 0) { goto the_end; } waddstr(topwin, exppath); } else { size_t exppathlen = newfie ? 0 : strlenpt(exppath); /* The length of the expanded filename. */ /* There is room for the whole filename, so we center it. */ mvwaddnstr(topwin, 0, verlen + ((space - exppathlen) / 3), prefix, actual_x(prefix, prefixlen)); if (!newfie) { waddch(topwin, ' '); waddstr(topwin, exppath); } } the_end: free(exppath); if (state[0] != '\0') { if (statelen >= COLS - 1) { mvwaddnstr(topwin, 0, 0, state, actual_x(state, COLS)); } else { assert(COLS - statelen - 1 >= 0); mvwaddnstr(topwin, 0, COLS - statelen - 1, state, actual_x(state, statelen)); } } clear_color(topwin, interface_colors[TITLE_BAR]); wnoutrefresh(topwin); reset_cursor(); wnoutrefresh(edit); }
/* If scroll_only is FALSE, move down one line. If scroll_only is TRUE, * scroll down one line without scrolling the cursor. */ void do_down(bool scroll_only) { #ifndef NANO_TINY int amount = 0, enough; filestruct *topline; #endif size_t was_column = xplustabs(); /* If we're at the bottom of the file, get out. */ if (openfile->current == openfile->filebot) return; assert(ISSET(SOFTWRAP) || openfile->current_y == openfile->current->lineno - openfile->edittop->lineno); assert(openfile->current->next != NULL); #ifndef NANO_TINY if (ISSET(SOFTWRAP)) { /* Compute the number of lines to scroll. */ amount = strlenpt(openfile->current->data) / editwincols - xplustabs() / editwincols + strlenpt(openfile->current->next->data) / editwincols + openfile->current_y - editwinrows + 2; topline = openfile->edittop; /* Reduce the amount when there are overlong lines at the top. */ for (enough = 1; enough < amount; enough++) { amount -= strlenpt(topline->data) / editwincols; if (amount > 0) topline = topline->next; if (amount < enough) { amount = enough; break; } } } #endif /* Move to the next line in the file. */ openfile->current = openfile->current->next; openfile->current_x = actual_x(openfile->current->data, openfile->placewewant); /* If scroll_only is FALSE and if we're on the last line of the * edit window, scroll the edit window down one line if we're in * smooth scrolling mode, or down half a page if we're not. If * scroll_only is TRUE, scroll the edit window down one line * unconditionally. */ #ifndef NANO_TINY if (openfile->current_y == editwinrows - 1 || amount > 0 || scroll_only) { if (amount < 1 || scroll_only) amount = 1; edit_scroll(DOWNWARD, (ISSET(SMOOTH_SCROLL) || scroll_only) ? amount : editwinrows / 2 + 1); if (ISSET(SOFTWRAP)) { refresh_needed = TRUE; return; } } #else if (openfile->current_y == editwinrows - 1) edit_scroll(DOWNWARD, editwinrows / 2 + 1); #endif /* If the lines weren't already redrawn, see if they need to be. */ if (openfile->current_y < editwinrows - 1) { /* Redraw the prior line if it was horizontally scrolled. */ if (need_horizontal_scroll(was_column, 0)) update_line(openfile->current->prev, 0); /* Redraw the current line if it needs to be horizontally scrolled. */ if (need_horizontal_scroll(0, xplustabs())) update_line(openfile->current, openfile->current_x); } }
/* edit_draw() takes care of the job of actually painting a line into * the edit window. fileptr is the line to be painted, at row line of * the window. converted is the actual string to be written to the * window, with tabs and control characters replaced by strings of * regular characters. start is the column number of the first * character of this page. That is, the first character of converted * corresponds to character number actual_x(fileptr->data, start) of the * line. */ void edit_draw(filestruct *fileptr, const char *converted, int line, size_t start) { size_t startpos = actual_x(fileptr->data, start); /* The position in fileptr->data of the leftmost character * that displays at least partially on the window. */ size_t endpos = actual_x(fileptr->data, start + COLS - 1) + 1; /* The position in fileptr->data of the first character that is * completely off the window to the right. * * Note that endpos might be beyond the null terminator of the * string. */ assert(openfile != openfiles.end() && fileptr != NULL && converted != NULL); assert(strlenpt(converted) <= COLS); /* Just paint the string in any case (we'll add color or reverse on * just the text that needs it). */ mvwaddstr(edit, line, 0, converted); /* Tell ncurses to really redraw the line without trying to optimize * for what it thinks is already there, because it gets it wrong in * the case of a wide character in column zero. */ #ifndef USE_SLANG wredrawln(edit, line, 1); #endif /* If color syntaxes are available and turned on, we need to display * them. */ if (!openfile->colorstrings.empty() && !ISSET(NO_COLOR_SYNTAX)) { /* Set up multi-line color data for this line if it's not yet calculated */ if (fileptr->multidata.empty() && openfile->syntax && openfile->syntax->nmultis > 0) { fileptr->multidata.resize(openfile->syntax->nmultis, -1); // assume that '-1' applies until we know otherwise } for (auto tmpcolor : openfile->colorstrings) { int x_start; /* Starting column for mvwaddnstr. Zero-based. */ int paintlen = 0; /* Number of chars to paint on this line. There are * COLS characters on a whole line. */ size_t index; /* Index in converted where we paint. */ regmatch_t startmatch; /* Match position for start_regex. */ regmatch_t endmatch; /* Match position for end_regex. */ if (tmpcolor->bright) { wattron(edit, A_BOLD); } if (tmpcolor->underline) { wattron(edit, A_UNDERLINE); } wattron(edit, COLOR_PAIR(tmpcolor->pairnum)); /* Two notes about regexec(). A return value of zero means * that there is a match. Also, rm_eo is the first * non-matching character after the match. */ /* First case,tmpcolor is a single-line expression. */ if (tmpcolor->end == NULL) { size_t k = 0; /* We increment k by rm_eo, to move past the end of the * last match. Even though two matches may overlap, we * want to ignore them, so that we can highlight e.g. C * strings correctly. */ while (k < endpos) { /* Note the fifth parameter to regexec(). It says * not to match the beginning-of-line character * unless k is zero. If regexec() returns * REG_NOMATCH, there are no more matches in the * line. */ if (regexec(tmpcolor->start, &fileptr->data[k], 1, &startmatch, (k == 0) ? 0 : REG_NOTBOL) == REG_NOMATCH) { break; } /* Translate the match to the beginning of the * line. */ startmatch.rm_so += k; startmatch.rm_eo += k; /* Skip over a zero-length regex match. */ if (startmatch.rm_so == startmatch.rm_eo) { startmatch.rm_eo++; } else if (startmatch.rm_so < endpos && startmatch.rm_eo > startpos) { x_start = (startmatch.rm_so <= startpos) ? 0 : strnlenpt(fileptr->data, startmatch.rm_so) - start; index = actual_x(converted, x_start); paintlen = actual_x(converted + index, strnlenpt(fileptr->data, startmatch.rm_eo) - start - x_start); assert(0 <= x_start && 0 <= paintlen); mvwaddnstr(edit, line, x_start, converted + index, paintlen); } k = startmatch.rm_eo; } } else if (!fileptr->multidata.empty() && fileptr->multidata[tmpcolor->id] != CNONE) { /* This is a multi-line regex. There are two steps. * First, we have to see if the beginning of the line is * colored by a start on an earlier line, and an end on * this line or later. * * We find the first line before fileptr matching the * start. If every match on that line is followed by an * end, then go to step two. Otherwise, find the next * line after start_line matching the end. If that line * is not before fileptr, then paint the beginning of * this line. */ const filestruct *start_line = fileptr->prev; /* The first line before fileptr matching start. */ regoff_t start_col; /* Where it starts in that line. */ const filestruct *end_line; short md = fileptr->multidata[tmpcolor->id]; if (md == -1) { fileptr->multidata[tmpcolor->id] = CNONE; /* until we find out otherwise */ } else if (md == CNONE) { unset_formatting(tmpcolor); continue; } else if (md == CWHOLELINE) { mvwaddnstr(edit, line, 0, converted, -1); unset_formatting(tmpcolor); continue; } else if (md == CBEGINBEFORE) { regexec(tmpcolor->end, fileptr->data, 1, &endmatch, 0); paintlen = actual_x(converted, strnlenpt(fileptr->data, endmatch.rm_eo) - start); mvwaddnstr(edit, line, 0, converted, paintlen); unset_formatting(tmpcolor); continue; } while (start_line != NULL && regexec(tmpcolor->start, start_line->data, 1, &startmatch, 0) == REG_NOMATCH) { /* If there is an end on this line, there is no need * to look for starts on earlier lines. */ if (regexec(tmpcolor->end, start_line->data, 0, NULL, 0) == 0) { goto step_two; } start_line = start_line->prev; } /* If the found start has been qualified as an end earlier, believe it and skip to the next step. */ if (start_line != NULL && !start_line->multidata.empty() && start_line->multidata[tmpcolor->id] == CBEGINBEFORE) { goto step_two; } /* Skip over a zero-length regex match. */ if (startmatch.rm_so == startmatch.rm_eo) { startmatch.rm_eo++; } else { /* No start found, so skip to the next step. */ if (start_line == NULL) { goto step_two; } /* Now start_line is the first line before fileptr * containing a start match. Is there a start on * this line not followed by an end on this line? */ start_col = 0; while (true) { start_col += startmatch.rm_so; startmatch.rm_eo -= startmatch.rm_so; if (regexec(tmpcolor->end, start_line->data + start_col + startmatch.rm_eo, 0, NULL, (start_col + startmatch.rm_eo == 0) ? 0 : REG_NOTBOL) == REG_NOMATCH) { /* No end found after this start. */ break; } start_col++; if (regexec(tmpcolor->start, start_line->data + start_col, 1, &startmatch, REG_NOTBOL) == REG_NOMATCH) { /* No later start on this line. */ goto step_two; } } /* Indeed, there is a start not followed on this * line by an end. */ /* We have already checked that there is no end * before fileptr and after the start. Is there an * end after the start at all? We don't paint * unterminated starts. */ end_line = fileptr; while (end_line != NULL && regexec(tmpcolor->end, end_line->data, 1, &endmatch, 0) == REG_NOMATCH) { end_line = end_line->next; } /* No end found, or it is too early. */ if (end_line == NULL || (end_line == fileptr && endmatch.rm_eo <= startpos)) { goto step_two; } /* Now paint the start of fileptr. If the start of * fileptr is on a different line from the end, * paintlen is -1, meaning that everything on the * line gets painted. Otherwise, paintlen is the * expanded location of the end of the match minus * the expanded location of the beginning of the * page. */ if (end_line != fileptr) { paintlen = -1; fileptr->multidata[tmpcolor->id] = CWHOLELINE; } else { paintlen = actual_x(converted, strnlenpt(fileptr->data, endmatch.rm_eo) - start); fileptr->multidata[tmpcolor->id] = CBEGINBEFORE; } mvwaddnstr(edit, line, 0, converted, paintlen); /* If the whole line has been painted, don't bother looking for any more starts. */ if (paintlen < 0) { continue; } step_two: /* Second step, we look for starts on this line. */ start_col = 0; while (start_col < endpos) { if (regexec(tmpcolor->start, fileptr->data + start_col, 1, &startmatch, (start_col == 0) ? 0 : REG_NOTBOL) == REG_NOMATCH || start_col + startmatch.rm_so >= endpos) { /* No more starts on this line. */ break; } /* Translate the match to be relative to the * beginning of the line. */ startmatch.rm_so += start_col; startmatch.rm_eo += start_col; x_start = (startmatch.rm_so <= startpos) ? 0 : strnlenpt(fileptr->data, startmatch.rm_so) - start; index = actual_x(converted, x_start); if (regexec(tmpcolor->end, fileptr->data + startmatch.rm_eo, 1, &endmatch, (startmatch.rm_eo == 0) ? 0 : REG_NOTBOL) == 0) { /* Translate the end match to be relative to the beginning of the line. */ endmatch.rm_so += startmatch.rm_eo; endmatch.rm_eo += startmatch.rm_eo; /* There is an end on this line. But does * it appear on this page, and is the match * more than zero characters long? */ if (endmatch.rm_eo > startpos && endmatch.rm_eo > startmatch.rm_so) { paintlen = actual_x(converted + index, strnlenpt(fileptr->data, endmatch.rm_eo) - start - x_start); assert(0 <= x_start && x_start < COLS); mvwaddnstr(edit, line, x_start, converted + index, paintlen); if (paintlen > 0) { fileptr->multidata[tmpcolor->id] = CSTARTENDHERE; } } } else { /* There is no end on this line. But we * haven't yet looked for one on later * lines. */ end_line = fileptr->next; while (end_line != NULL && regexec(tmpcolor->end, end_line->data, 0, NULL, 0) == REG_NOMATCH) { end_line = end_line->next; } if (end_line != NULL) { assert(0 <= x_start && x_start < COLS); mvwaddnstr(edit, line, x_start, converted + index, -1); /* We painted to the end of the line, so * don't bother checking any more * starts. */ fileptr->multidata[tmpcolor->id] = CENDAFTER; break; } } start_col = startmatch.rm_so + 1; } } } unset_formatting(tmpcolor); } } /* If the mark is on, we need to display it. */ if (openfile->mark_set && (fileptr->lineno <= openfile->mark_begin->lineno || fileptr->lineno <= openfile->current->lineno) && (fileptr->lineno >= openfile->mark_begin->lineno || fileptr->lineno >= openfile->current->lineno)) { /* fileptr is at least partially selected. */ const filestruct *top; /* Either current or mark_begin, whichever is first. */ size_t top_x; /* current_x or mark_begin_x, corresponding to top. */ const filestruct *bot; size_t bot_x; int x_start; /* Starting column for mvwaddnstr(). Zero-based. */ int paintlen; /* Number of characters to paint on this line. There are * COLS characters on a whole line. */ size_t index; /* Index in converted where we paint. */ mark_order(&top, &top_x, &bot, &bot_x, NULL); if (top->lineno < fileptr->lineno || top_x < startpos) { top_x = startpos; } if (bot->lineno > fileptr->lineno || bot_x > endpos) { bot_x = endpos; } /* The selected bit of fileptr is on this page. */ if (top_x < endpos && bot_x > startpos) { assert(startpos <= top_x); /* x_start is the expanded location of the beginning of the * mark minus the beginning of the page. */ x_start = strnlenpt(fileptr->data, top_x) - start; /* If the end of the mark is off the page, paintlen is -1, * meaning that everything on the line gets painted. * Otherwise, paintlen is the expanded location of the end * of the mark minus the expanded location of the beginning * of the mark. */ if (bot_x >= endpos) { paintlen = -1; } else paintlen = strnlenpt(fileptr->data, bot_x) - (x_start + start); /* If x_start is before the beginning of the page, shift * paintlen x_start characters to compensate, and put * x_start at the beginning of the page. */ if (x_start < 0) { paintlen += x_start; x_start = 0; } assert(x_start >= 0 && x_start <= strlen(converted)); index = actual_x(converted, x_start); if (paintlen > 0) { paintlen = actual_x(converted + index, paintlen); } set_color(edit, interface_colors[FUNCTION_TAG]); mvwaddnstr(edit, line, x_start, converted + index, paintlen); clear_color(edit, interface_colors[FUNCTION_TAG]); } } }