From 0c8fa23e4e2b4eec8ecc58bfc8740a866494482a Mon Sep 17 00:00:00 2001 From: rexy712 Date: Sat, 1 Apr 2023 14:02:35 -0700 Subject: [PATCH 3/7] Modify signatures of functions to be modular for further folding patches. The bracket search functions 'find_a_bracket' is very useful for searching for region to fold when no selection is made. To make this possible, the function needed to be modularized so it could be reused in a way not originally intended. The functions 'do_prev_word' and 'do_next_word' were interesting in that sometimes it is desirable to get the corresponding word within a folded segment and sometimes it was better to skip folded segments. So a boolean parameter seemed like a good way to not duplicate functionality while making the function work for both cases. The 'update_line' function only worked in relation to edittop from the given line's lineno. This obviously won't get the correct position if there are folded lines. So I added a new function 'update_line_at' to allow specifying where in the window to update when calling the function. The original 'update_line' still exists to not modify existing code utilizing it. Signed-off-by: rexy712 --- src/cut.c | 4 +- src/move.c | 50 +++++++++-- src/prototypes.h | 8 +- src/search.c | 220 ++++++++++++++++++++++++++++++++++++----------- src/text.c | 2 +- src/winio.c | 86 +++++++++++++++--- 6 files changed, 295 insertions(+), 75 deletions(-) diff --git a/src/cut.c b/src/cut.c index dcb28f79..0fec39c2 100644 --- a/src/cut.c +++ b/src/cut.c @@ -196,7 +196,7 @@ void chop_word(bool forward) * on the edge of the original line, then put the cursor on that * edge instead, so that lines will not be joined unexpectedly. */ if (!forward) { - do_prev_word(); + do_prev_word(ALLOW_FOLDED); if (openfile->current != is_current) { if (is_current_x > 0) { openfile->current = is_current; @@ -205,7 +205,7 @@ void chop_word(bool forward) openfile->current_x = strlen(openfile->current->data); } } else { - do_next_word(ISSET(AFTER_ENDS)); + do_next_word(ISSET(AFTER_ENDS), ALLOW_FOLDED); if (openfile->current != is_current && is_current->data[is_current_x] != '\0') { openfile->current = is_current; diff --git a/src/move.c b/src/move.c index d6d36dd8..c2356be5 100644 --- a/src/move.c +++ b/src/move.c @@ -274,19 +274,35 @@ void to_next_block(void) #endif } -/* Move to the previous word. */ -void do_prev_word(void) +/* Move to the previous word. + * When allow_folded is true, will move to previous word even if inside + * a folded segment. */ +void do_prev_word(bool allow_folded) { bool punctuation_as_letters = ISSET(WORD_BOUNDS); bool seen_a_word = FALSE, step_forward = FALSE; +#ifdef ENABLE_FOLDING + if (!allow_folded && openfile->current->folded) { + openfile->current = get_prev_visible_line(openfile->current); + openfile->current_x = strlen(openfile->current->data); + } +#endif + /* Move backward until we pass over the start of a word. */ while (TRUE) { /* If at the head of a line, move to the end of the preceding one. */ if (openfile->current_x == 0) { if (openfile->current->prev == NULL) break; - openfile->current = openfile->current->prev; +#ifdef ENABLE_FOLDING + if (!allow_folding) { + openfile->current = get_prev_visible_line(openfile->current); + if (openfile->current->folded) + openfile->current = openfile->current->prev; + } else +#endif + openfile->current = openfile->current->prev; openfile->current_x = strlen(openfile->current->data); } @@ -319,8 +335,9 @@ void do_prev_word(void) } /* Move to the next word. If after_ends is TRUE, stop at the ends of words - * instead of at their beginnings. Return TRUE if we started on a word. */ -bool do_next_word(bool after_ends) + * instead of at their beginnings. If allow_folded is true, go to next word + * even if it's inside a folded segment. Return TRUE if we started on a word. */ +bool do_next_word(bool after_ends, bool allow_folded) { bool punctuation_as_letters = ISSET(WORD_BOUNDS); bool started_on_word = is_word_char(openfile->current->data + @@ -330,6 +347,12 @@ bool do_next_word(bool after_ends) bool seen_word = started_on_word; #endif +#ifdef ENABLE_FOLDING + if (!allow_folded & openfile->current->folded) { + openfile->current = get_next_visible_line(openfile->current); + openfile->current_x = 0; + } +#endif /* Move forward until we reach the start of a word. */ while (TRUE) { /* If at the end of a line, move to the beginning of the next one. */ @@ -337,7 +360,18 @@ bool do_next_word(bool after_ends) /* When at end of file, stop. */ if (openfile->current->next == NULL) break; - openfile->current = openfile->current->next; +#ifdef ENABLE_FOLDING + if (!allow_folding) { + openfile->current = get_next_visible_line(openfile->current); + if (openfile->current->folded) { + /* get_next_visible_line() gives the start of a folded segment, + * so skip to the end. */ + openfile->current = get_end_of_folded_segment(openfile->current); + openfile->current = openfile->current->next; + } + } else +#endif + openfile->current = openfile->current->next; openfile->current_x = 0; seen_space = TRUE; } else { @@ -387,7 +421,7 @@ void to_prev_word(void) { linestruct *was_current = openfile->current; - do_prev_word(); + do_prev_word(DISALLOW_FOLDED); edit_redraw(was_current, FLOWING); } @@ -398,7 +432,7 @@ void to_next_word(void) { linestruct *was_current = openfile->current; - do_next_word(ISSET(AFTER_ENDS)); + do_next_word(ISSET(AFTER_ENDS), DISALLOW_FOLDED); edit_redraw(was_current, FLOWING); } diff --git a/src/prototypes.h b/src/prototypes.h index 0c86f6a4..d0a374af 100644 --- a/src/prototypes.h +++ b/src/prototypes.h @@ -365,8 +365,8 @@ void to_para_end(void); #endif void to_prev_block(void); void to_next_block(void); -void do_prev_word(void); -bool do_next_word(bool after_ends); +void do_prev_word(bool allow_folded); +bool do_next_word(bool after_ends, bool allow_folded); void to_prev_word(void); void to_next_word(void); void do_home(void); @@ -476,6 +476,9 @@ void goto_line_and_column(ssize_t line, ssize_t column, bool retain_answer, bool interactive); void do_gotolinecolumn(void); #ifndef NANO_TINY +int find_matching_bracket_pos(linestruct **line, size_t *xpos); +bool find_bracketed_region(linestruct *in_region, + linestruct **top, linestruct **bot); void do_find_bracket(void); void put_or_lift_anchor(void); void to_prev_anchor(void); @@ -612,6 +615,7 @@ void warn_and_briefly_pause(const char *msg); void bottombars(int menu); void post_one_key(const char *keystroke, const char *tag, int width); void place_the_cursor(void); +int update_line_at(linestruct *line, size_t index, int row); int update_line(linestruct *line, size_t index); #ifndef NANO_TINY int update_softwrapped_line(linestruct *line); diff --git a/src/search.c b/src/search.c index 3874f703..274b63e5 100644 --- a/src/search.c +++ b/src/search.c @@ -877,23 +877,110 @@ void do_gotolinecolumn(void) } #ifndef NANO_TINY +/* Search backwards for a line ending with a bracket, including the current line. + * top is an in/out parameter. The passed value is the start line for searching. + * xpos is an out parameter. It is the position in the line where the bracket is + * found. bracket_half is the halfway mark in the matchbrackets array. This way in + * a loop the halfway value isn't recomputed. */ +bool find_start_of_bracketed_region(linestruct **top, size_t *xpos, size_t bracket_half) +{ + /* Search backwards for a line ending with a bracket, including the current line */ + for (;(*top) != NULL;*top = (*top)->prev) { + char *found; + const char *bracket; + int len = strlen((*top)->data); + + /* Find the last bracket on the search line */ + found = mbrevstrpbrk((*top)->data, matchbrackets, (*top)->data + len); + if (found == NULL) + continue; + + *xpos = found - (*top)->data; + bracket = mbstrchr(matchbrackets, found); + + /* We're looking for opening braces, not closing */ + if (bracket >= matchbrackets + bracket_half) + continue; + + /* Check if this is the last character in the line */ + char *next = (*top)->data + step_right((*top)->data, *xpos); + if (*next == '\0') + return TRUE; + } + *xpos = 0; + return FALSE; +} + +/* Get the halfway point in matchbrackets, where open and close + * brackets meet. */ +size_t get_matchbrackets_halfway(void) +{ + size_t charcount = mbstrlen(matchbrackets) / 2; + size_t halfway = 0; + + for (size_t i = 0;i < charcount;++i) + halfway += char_length(matchbrackets + halfway); + return halfway; +} +/* Get a region surrounding the line in_region. A region here is defined + * as a block of text starting with an opening bracket at the end of a line, + * and ending with a matching closing bracket. + * top and bot are out parameters. Returns the top/bottom line of the region. + * top_pos and bot_pos are out parameters. Returns the position within the + * top and bottom lines where the brackets are found. */ +bool find_bracketed_region(linestruct *in_region, + linestruct **top, linestruct **bot) +{ + size_t bracket_half = get_matchbrackets_halfway(); + size_t top_pos; + size_t bot_pos; + linestruct *was_top = *top; + linestruct *was_bot = *bot; + *top = in_region; + + for (;find_start_of_bracketed_region(top, &top_pos, bracket_half) + ;*top = (*top)->prev) + { + *bot = *top; + bot_pos = top_pos; + + if (!find_matching_bracket_pos(bot, &bot_pos)) { + if ((*bot)->lineno < in_region->lineno) + continue; + + int linedist = (*bot)->lineno - (*top)->lineno; + + if (linedist >= 2) { + *top = (*top)->next; + *bot = (*bot)->prev; + return TRUE; + } + } + } + *top = was_top; + *bot = was_bot; + return FALSE; +} + + /* Search, starting from the current position, for any of the two characters * in bracket_pair. If reverse is TRUE, search backwards, otherwise forwards. * Return TRUE when one of the brackets was found, and FALSE otherwise. */ -bool find_a_bracket(bool reverse, const char *bracket_pair) +bool find_a_bracket(bool reverse, const char *bracket_pair, + linestruct **inout_line, size_t *xpos) { - linestruct *line = openfile->current; + linestruct *line = *inout_line; const char *pointer, *found; if (reverse) { /* First step away from the current bracket. */ - if (openfile->current_x == 0) { + if (*xpos == 0) { line = line->prev; if (line == NULL) return FALSE; pointer = line->data + strlen(line->data); } else - pointer = line->data + step_left(line->data, openfile->current_x); + pointer = line->data + step_left(line->data, *xpos); /* Now seek for any of the two brackets we are interested in. */ while (!(found = mbrevstrpbrk(line->data, bracket_pair, pointer))) { @@ -903,7 +990,7 @@ bool find_a_bracket(bool reverse, const char *bracket_pair) pointer = line->data + strlen(line->data); } } else { - pointer = line->data + step_right(line->data, openfile->current_x); + pointer = line->data + step_right(line->data, *xpos); while (!(found = mbstrpbrk(pointer, bracket_pair))) { line = line->next; @@ -913,45 +1000,24 @@ bool find_a_bracket(bool reverse, const char *bracket_pair) } } - /* Set the current position to the found bracket. */ - openfile->current = line; - openfile->current_x = found - line->data; + /* Set the found position to the found bracket. */ + *inout_line = line; + *xpos = found - line->data; return TRUE; } -/* Search for a match to the bracket at the current cursor position, if - * there is one. */ -void do_find_bracket(void) +/* Search for the given bracket's compliment within matchbrackets. + * ch must be a pointer to within matchbrackets. + * reverse is true if the found compliment came before ch in matchbrackets. */ +const char *get_bracket_compliment(const char *ch, bool *reverse) { - linestruct *was_current = openfile->current; - size_t was_current_x = openfile->current_x; - /* The current cursor position, in case we don't find a complement. */ - const char *ch; - /* The location in matchbrackets of the bracket under the cursor. */ - int ch_len; - /* The length of ch in bytes. */ + size_t charcount = mbstrlen(matchbrackets) / 2; + /* Half the number of characters in matchbrackets. */ const char *wanted_ch; /* The location in matchbrackets of the complementing bracket. */ - int wanted_ch_len; - /* The length of wanted_ch in bytes. */ - char bracket_pair[MAXCHARLEN * 2 + 1]; - /* The pair of characters in ch and wanted_ch. */ size_t halfway = 0; /* The index in matchbrackets where the closing brackets start. */ - size_t charcount = mbstrlen(matchbrackets) / 2; - /* Half the number of characters in matchbrackets. */ - size_t balance = 1; - /* The initial bracket count. */ - bool reverse; - /* The direction we search. */ - - ch = mbstrchr(matchbrackets, openfile->current->data + openfile->current_x); - - if (ch == NULL) { - statusline(AHEM, _("Not a bracket")); - return; - } /* Find the halfway point in matchbrackets, where the closing ones start. */ for (size_t i = 0; i < charcount; i++) @@ -959,19 +1025,35 @@ void do_find_bracket(void) /* When on a closing bracket, we have to search backwards for a matching * opening bracket; otherwise, forward for a matching closing bracket. */ - reverse = (ch >= (matchbrackets + halfway)); + *reverse = (ch >= (matchbrackets + halfway)); /* Step half the number of total characters either backwards or forwards * through matchbrackets to find the wanted complementary bracket. */ wanted_ch = ch; while (charcount-- > 0) { - if (reverse) + if (*reverse) wanted_ch = matchbrackets + step_left(matchbrackets, wanted_ch - matchbrackets); else wanted_ch += char_length(wanted_ch); } + return wanted_ch; +} + +/* Create a bracket pair string from the given bracket in ch, assigning + * the result to bracket_pair. Reverse is set to true if we need to + * search backward for a match. */ +void create_bracket_pair(char *bracket_pair, const char *ch, + int ch_len, bool* reverse) +{ + const char *wanted_ch; + /* The location in matchbrackets of the complementing bracket. */ + int wanted_ch_len; + /* The length of wanted_ch in bytes. */ + + wanted_ch = get_bracket_compliment(ch, reverse); + ch_len = char_length(ch); wanted_ch_len = char_length(wanted_ch); @@ -979,25 +1061,65 @@ void do_find_bracket(void) strncpy(bracket_pair, ch, ch_len); strncpy(bracket_pair + ch_len, wanted_ch, wanted_ch_len); bracket_pair[ch_len + wanted_ch_len] = '\0'; +} - while (find_a_bracket(reverse, bracket_pair)) { - /* Increment/decrement balance for an identical/other bracket. */ - balance += (strncmp(openfile->current->data + openfile->current_x, - ch, ch_len) == 0) ? 1 : -1; +/* Get the bracket match position for the character currently under the cursor. + * If there is no bracket beneath the cursor, return NOT_A_BRACKET. If there is + * no match found, return NOT_FOUND_BRACKET. Otherwise returns FOUND_BRACKET + * and sets line and xpos to the found line and offset respectively. */ +int find_matching_bracket_pos(linestruct **line, size_t *xpos) +{ + const char *br_ch; + /* The location in matchbrackets of the bracket under the cursor. */ + int br_ch_len; + /* The length of br_ch in bytes. */ + char bracket_pair[MAXCHARLEN * 2 + 1]; + /* The pair of characters in ch and wanted_ch. */ + size_t balance = 1; + /* The initial bracket count. */ + bool reversed; + /* The direction we search. */ + + br_ch = mbstrchr(matchbrackets, (*line)->data + (*xpos)); + + if (br_ch == NULL) + return NOT_A_BRACKET; + + br_ch_len = char_length(br_ch); + create_bracket_pair(bracket_pair, br_ch, br_ch_len, &reversed); + + + while (find_a_bracket(reversed, bracket_pair, line, xpos)) { + balance += (strncmp((*line)->data + *xpos, + br_ch, br_ch_len) == 0) ? 1 : -1; + if (balance == 0) + return FOUND_BRACKET; - /* When balance reached zero, we've found the complementary bracket. */ - if (balance == 0) { - UNFOLD_SEGMENT(openfile->current); - edit_redraw(was_current, FLOWING); - return; - } } + return NOT_FOUND_BRACKET; +} - statusline(AHEM, _("No matching bracket")); +/* Search for a match to the bracket at the current cursor position, if + * there is one. */ +void do_find_bracket(void) +{ + linestruct *was_current = openfile->current; + size_t was_current_x = openfile->current_x; + + int res = find_matching_bracket_pos(&openfile->current, + &openfile->current_x); + if (res == FOUND_BRACKET) { + UNFOLD_SEGMENT(openfile->current); + edit_redraw(was_current, FLOWING); + return; + } - /* Restore the cursor position. */ openfile->current = was_current; openfile->current_x = was_current_x; + + statusline(AHEM, res == NOT_FOUND_BRACKET ? + _("No matching bracket") : + _("Not a bracket")); } /* Place an anchor at the current line when none exists, otherwise remove it. */ diff --git a/src/text.c b/src/text.c index 96b47f6e..67caa20c 100644 --- a/src/text.c +++ b/src/text.c @@ -3007,7 +3007,7 @@ void count_lines_words_and_characters(void) * incrementing the word count for each successful step. */ while (openfile->current->lineno < botline->lineno || (openfile->current == botline && openfile->current_x < bot_x)) { - if (do_next_word(FALSE)) + if (do_next_word(FALSE, ALLOW_FOLDED)) words++; } diff --git a/src/winio.c b/src/winio.c index c1d84e04..3235a866 100644 --- a/src/winio.c +++ b/src/winio.c @@ -2565,17 +2565,20 @@ void place_the_cursor(void) /* The number of bytes after which to stop painting, to avoid major slowdowns. */ #define PAINT_LIMIT 2000 -/* Draw the given text on the given row of the edit window. line is the - * line to be drawn, and converted is the actual string to be written with - * tabs and control characters replaced by strings of regular characters. - * from_col is the column number of the first character of this "page". */ -void draw_row(int row, const char *converted, linestruct *line, size_t from_col) -{ #ifdef ENABLE_LINENUMBERS +/* Draw the line number for the given line, but only if line numbering is on. + * For folded segments, draws a '-' instead of a line number. + * Also draws a mark indicating where anchors are set. */ +void draw_linenumber(int row, const linestruct* line, size_t from_col){ /* If line numbering is switched on, put a line number in front of * the text -- but only for the parts that are not softwrapped. */ if (margin > 0) { wattron(midwin, interface_color_pair[LINE_NUMBER]); +#ifdef ENABLE_FOLDING + if (line->folded) + mvwprintw(midwin, row, 0, "%*c", margin - 1, '-'); + else +#endif #ifndef NANO_TINY if (ISSET(SOFTWRAP) && from_col != 0) mvwprintw(midwin, row, 0, "%*s", margin - 1, " "); @@ -2595,8 +2598,20 @@ void draw_row(int row, const char *converted, linestruct *line, size_t from_col) #endif wprintw(midwin, " "); } + +} #endif /* ENABLE_LINENUMBERS */ +/* Draw the given text on the given row of the edit window. line is the + * line to be drawn, and converted is the actual string to be written with + * tabs and control characters replaced by strings of regular characters. + * from_col is the column number of the first character of this "page". */ +void draw_row(int row, const char *converted, linestruct *line, + size_t from_col) +{ +#ifdef ENABLE_LINENUMBERS + draw_linenumber(row, line, from_col); +#endif /* First simply write the converted line -- afterward we'll add colors * and the marking highlight on just the pieces that need it. */ mvwaddstr(midwin, row, margin, converted); @@ -2850,18 +2865,50 @@ void draw_row(int row, const char *converted, linestruct *line, size_t from_col) #endif /* !NANO_TINY */ } -/* Redraw the given line so that the character at the given index is visible - * -- if necessary, scroll the line horizontally (when not softwrapping). - * Return the number of rows "consumed" (relevant when softwrapping). */ -int update_line(linestruct *line, size_t index) +#ifdef ENABLE_FOLDING +/* Draw a folded segment at the given row. */ +int update_folded_line(linestruct *line, int row) +{ + const int seg_len = get_folded_segment_length(line); + const int string_len = snprintf(NULL, 0, "%d lines folded", seg_len); + const int fill_length = (editwincols > string_len) ? + (editwincols - string_len) : 0; + const int half_fill = fill_length / 2; + + char *string = nmalloc(editwincols + 1); + + memset(string, '-', half_fill); + snprintf(string+half_fill, editwincols, "%d lines folded", seg_len); + memset(string+half_fill+string_len, '-', fill_length - half_fill); + string[editwincols] = '\0'; + +#ifdef ENABLE_LINENUMBERS + draw_linenumber(row, line, 0); +#endif + + mvwaddstr(midwin, row, margin, string); + + free(string); + return 1; +} +#endif /* ENABLE_FOLDING */ + + +/* Redraw the given line at the given row so that the character at the + * given index is visible -- if necessary, scroll the line horizontally + * (when not softwrapping). Return the number of rows "consumed" + * (relevant when softwrapping). */ +int update_line_at(linestruct *line, size_t index, int row) { - int row; - /* The row in the edit window we will be updating. */ char *converted; /* The data of the line with tabs and control characters expanded. */ size_t from_col; /* From which column a horizontally scrolled line is displayed. */ +#ifdef ENABLE_FOLDING + if (line->folded) + return update_folded_line(line, row); +#endif #ifndef NANO_TINY if (ISSET(SOFTWRAP)) return update_softwrapped_line(line); @@ -2869,7 +2916,6 @@ int update_line(linestruct *line, size_t index) sequel_column = 0; #endif - row = line->lineno - openfile->edittop->lineno; from_col = get_page_start(wideness(line->data, index)); /* Expand the piece to be drawn to its representable form, and draw it. */ @@ -2892,6 +2938,15 @@ int update_line(linestruct *line, size_t index) spotlight(light_from_col, light_to_col); return 1; + +} + +/* Redraw the given line so that the character at the given index is visible + * -- if necessary, scroll the line horizontally (when not softwrapping). + * Return the number of rows "consumed" (relevant when softwrapping). */ +int update_line(linestruct *line, size_t index) +{ + return update_line_at(line, index, get_row_from_edittop(line)); } #ifndef NANO_TINY @@ -2937,6 +2992,11 @@ int update_softwrapped_line(linestruct *line) starting_row = row; +#ifdef ENABLE_FOLDING + if (line->folded) + return update_folded_line(line, row); +#endif + while (!end_of_line && row < editwinrows) { to_col = get_softwrap_breakpoint(line->data, from_col, &kickoff, &end_of_line); -- 2.39.2