/* Calculate the displayable length of the help-text line starting at ptr. */ size_t help_line_len(const char *ptr) { size_t wrapping_point = (COLS > 24) ? COLS - 1 : 24; /* The target width for wrapping long lines. */ ssize_t wrap_location; /* Actual position where the line can be wrapped. */ size_t length = 0; /* Full length of the line, until the first newline. */ /* Avoid overwide paragraphs in the introductory text. */ if (ptr < end_of_intro && COLS > 74) wrapping_point = 74; wrap_location = break_line(ptr, wrapping_point, TRUE); /* Get the length of the entire line up to a null or a newline. */ while (*(ptr + length) != '\0' && *(ptr + length) != '\n') length = move_mbright(ptr, length); /* If the entire line will just fit the screen, don't wrap it. */ if (strnlenpt(ptr, length) <= wrapping_point + 1) return length; else if (wrap_location > 0) return wrap_location; else return 0; }
/* 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); }
/* Return the placewewant associated with current_x, i.e. the zero-based * column position of the cursor. The value will be no smaller than * current_x. */ size_t xplustabs(void) { if (openfile->current) return strnlenpt(openfile->current->data, openfile->current_x); else return 0; }
/* Return the placewewant associated with statusbar_x, i.e. the * zero-based column position of the cursor. The value will be no * smaller than statusbar_x. */ size_t statusbar_xplustabs(void) { return strnlenpt(answer, statusbar_x); }
/* Step through each replace word and prompt user before replacing. * Parameters real_current and real_current_x are needed in order to * allow the cursor position to be updated when a word before the cursor * is replaced by a shorter word. * * needle is the string to seek. We replace it with answer. Return -1 * if needle isn't found, else the number of replacements performed. If * canceled isn't NULL, set it to TRUE if we canceled. */ ssize_t do_replace_loop( #ifndef DISABLE_SPELLER bool whole_word_only, #endif bool *canceled, const linestruct *real_current, size_t *real_current_x, const char *needle) { ssize_t numreplaced = -1; size_t match_len; bool replaceall = FALSE; #ifndef NANO_TINY bool old_mark_set = openfile->mark_set; linestruct *top, *bot; size_t top_x, bot_x; bool right_side_up = FALSE; /* TRUE if (mark_begin, mark_begin_x) is the top of the mark, * FALSE if (current, current_x) is. */ if (old_mark_set) { /* If the mark is on, frame the region, and turn the mark off. */ mark_order((const linestruct **)&top, &top_x, (const linestruct **)&bot, &bot_x, &right_side_up); openfile->mark_set = FALSE; /* Start either at the top or the bottom of the marked region. */ if (!ISSET(BACKWARDS_SEARCH)) { openfile->current = top; openfile->current_x = (top_x == 0 ? 0 : top_x - 1); } else { openfile->current = bot; openfile->current_x = bot_x; } } #endif /* !NANO_TINY */ if (canceled != NULL) *canceled = FALSE; findnextstr_wrap_reset(); while (findnextstr( #ifndef DISABLE_SPELLER whole_word_only, #endif real_current, *real_current_x, needle, &match_len)) { int i = 0; #ifndef NANO_TINY if (old_mark_set) { /* When we've found an occurrence outside of the marked region, * stop the fanfare. */ if (openfile->current->lineno > bot->lineno || openfile->current->lineno < top->lineno || (openfile->current == bot && openfile->current_x > bot_x) || (openfile->current == top && openfile->current_x < top_x)) break; } #endif /* Indicate that we found the search string. */ if (numreplaced == -1) numreplaced = 0; if (!replaceall) { size_t xpt = xplustabs(); char *exp_word = display_string(openfile->current->data, xpt, strnlenpt(openfile->current->data, openfile->current_x + match_len) - xpt, FALSE); edit_refresh(); curs_set(0); do_replace_highlight(TRUE, exp_word); /* TRANSLATORS: This is a prompt. */ i = do_yesno_prompt(TRUE, _("Replace this instance?")); do_replace_highlight(FALSE, exp_word); free(exp_word); curs_set(1); if (i == -1) { /* We canceled the replace. */ if (canceled != NULL) *canceled = TRUE; break; } } if (i > 0 || replaceall) { /* Yes, replace it!!!! */ char *copy; size_t length_change; #ifndef NANO_TINY add_undo(REPLACE); #endif if (i == 2) replaceall = TRUE; copy = replace_line(needle); length_change = strlen(copy) - strlen(openfile->current->data); #ifndef NANO_TINY /* If the mark was on and (mark_begin, mark_begin_x) was the * top of it, don't change mark_begin_x. */ if (!old_mark_set || !right_side_up) { /* Keep mark_begin_x in sync with the text changes. */ if (openfile->current == openfile->mark_begin && openfile->mark_begin_x > openfile->current_x) { if (openfile->mark_begin_x < openfile->current_x + match_len) openfile->mark_begin_x = openfile->current_x; else openfile->mark_begin_x += length_change; bot_x = openfile->mark_begin_x; } } /* If the mark was on and (current, current_x) was the top * of it, don't change real_current_x. */ if (!old_mark_set || right_side_up) { #endif /* Keep real_current_x in sync with the text changes. */ if (openfile->current == real_current && openfile->current_x <= *real_current_x) { if (*real_current_x < openfile->current_x + match_len) *real_current_x = openfile->current_x + match_len; *real_current_x += length_change; #ifndef NANO_TINY bot_x = *real_current_x; } #endif } /* Set the cursor at the last character of the replacement * text, so searching will resume after the replacement * text. Note that current_x might be set to (size_t)-1 * here. */ #ifndef NANO_TINY if (!ISSET(BACKWARDS_SEARCH)) #endif openfile->current_x += match_len + length_change - 1; /* Clean up. */ openfile->totsize += mbstrlen(copy) - mbstrlen(openfile->current->data); free(openfile->current->data); openfile->current->data = copy; #ifndef DISABLE_COLOR /* Reset the precalculated multiline-regex hints only when * the first replacement has been made. */ if (numreplaced == 0) reset_multis(openfile->current, TRUE); #endif if (!replaceall) { #ifndef DISABLE_COLOR /* If color syntaxes are available and turned on, we * need to call edit_refresh(). */ if (openfile->colorstrings != NULL && !ISSET(NO_COLOR_SYNTAX)) edit_refresh(); else #endif update_line(openfile->current, openfile->current_x); } set_modified(); numreplaced++; } } if (numreplaced == -1) not_found_msg(needle); #ifndef NANO_TINY if (old_mark_set) openfile->mark_set = TRUE; #endif /* If the NO_NEWLINES flag isn't set, and text has been added to the * magicline, make a new magicline. */ if (!ISSET(NO_NEWLINES) && openfile->filebot->data[0] != '\0') new_magicline(); return numreplaced; }
/* A strlen() with tabs and multicolumn characters factored in, similar * to xplustabs(). How many columns wide is s? */ size_t strlenpt(const char *s) { return strnlenpt(s, (size_t)-1); }
/* Return the placewewant associated with current_x, i.e. the zero-based * column position of the cursor. The value will be no smaller than * current_x. */ size_t xplustabs(void) { return strnlenpt(openfile->current->data, 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]); } } }
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); }
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; }
/* 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; }