Re: [PATCH] line folding support

rexy712 <[email protected]>
Newsgroups gmane.editors.nano.devel
Message-ID <pSyQtRQ9Vw3iPVKYA2oDw-j562zl6s09XSwtgsJ8m7C3y3AVQCyxoxTaEjQHvXwmVVcy_3KtbjyO5q8-RKSWr7okErSNFv8rp0pb7YBtwmI=@protonmail.com>
Already noticed an issue in the patches I made when I splitting it up into multiple patches. This was caused by my splitting the changes and building incrementally and with '--enable-folding' not activated in the final test build. Sorry about that. Everything is correct except one variable name in 2 spots.

Replacement patches are attached which fix this issue.

Apologies again,
rexy712
------- Original Message -------
On Saturday, April 1st, 2023 at 2:59 PM, rexy712 <[email protected]> wrote:

> Line folding is the ability for to hide or collapse a section of a file when you don't need to see it. All IDEs I've used have had this feature and it's the one thing I feel GNU nano has been missing when working from the terminal. It helps to keep the screen less cluttered when trying to focus. Folding lines is also useful when trying to work on two parts of a file at the same time, or when referencing something in a different section of the same file instead of jumping back and forth.
>
> Almost a year ago, I was wondering about hacking line folding support into nano. Then I came across this [bug #58199](https://savannah.gnu.org/bugs/?58199): [Wish] the ability to temporarily "fold" lines. This made me realize I wasn't the only one who wanted to see this feature, so I implemented it. These patches are the results.
>
> I have been maintaining this patch for around 9 months on my own personal builds. I haven't seen any instability in a long time and feel it's time to submit to see if anyone else is interested or, more likely, has issues with my patches they'd want improved before considering it.
>
> This patch is pretty massive, so I've also split it into multiple patches after finishing. So to be clear, these patches are not the order in which it was developed. But rather my attempt at a logical grouping of modifications to hopefully make my intent with some changes more clear. I'm also posting the full patch as a single file. This single file does not include the color patch. That way it doesn't get in the way of seeing other changes.
>
> Thanks for your time,
> rexy712
0000-Full-folding-support-in-one.patch (text/x-patch, 52.6 KB)
From dd9dd297699cf3ab797eb1e6926da13ee880e926 Mon Sep 17 00:00:00 2001
From: rexy712 <[email protected]>
Date: Sat, 1 Apr 2023 14:29:13 -0700
Subject: [PATCH] Add line folding support

Line folding is the ability to hide or collapse a section of a file when you
don't need to see it. All IDEs I've used have had this feature and it's the
one thing I feel GNU nano has been missing when working from the terminal. It
helps to keep the screen less cluttered when trying to focus.
Folding lines is useful when trying to work on two parts of a file at the same
time, or when referencing something in a different section of the same file
instead of jumping back and forth.

Signed-off-by: rexy712 <[email protected]>
---
 configure.ac      |  14 +++
 src/Makefile.am   |   1 +
 src/cut.c         |  23 +++-
 src/definitions.h |  19 +++
 src/files.c       |   1 +
 src/folding.c     | 138 +++++++++++++++++++++
 src/global.c      |  17 ++-
 src/move.c        | 122 ++++++++++++++++---
 src/nano.c        |   8 ++
 src/prototypes.h  |  21 +++-
 src/search.c      | 236 +++++++++++++++++++++++++++--------
 src/text.c        |  16 ++-
 src/winio.c       | 305 ++++++++++++++++++++++++++++++++++++++--------
 13 files changed, 792 insertions(+), 129 deletions(-)
 create mode 100644 src/folding.c

diff --git a/configure.ac b/configure.ac
index b08dade5..ceb16a0f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -128,6 +128,20 @@ if test "x$enable_color" != xno; then
 	color_support=yes
 fi
 
+AC_ARG_ENABLE(folding,
+AS_HELP_STRING([--disable-folding], [Disable line folding support]))
+if test "x$enable_tiny" = xyes; then
+	if test "x$enable_folding" = xyes; then
+			AC_MSG_ERROR([
+  ***  --enable-folding cannot work with --enable-tiny])
+	else
+		enable_folding=no
+	fi
+fi
+if test "x$enable_folding" != xno; then
+	AC_DEFINE(ENABLE_FOLDING, 1, [Define this to have line folding support.])
+fi
+
 AC_ARG_ENABLE(comment,
 AS_HELP_STRING([--disable-comment], [Disable the comment/uncomment function]))
 if test "x$enable_tiny" = xyes; then
diff --git a/src/Makefile.am b/src/Makefile.am
index cd019daf..48d1ef30 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -30,6 +30,7 @@ nano_SOURCES = \
 		color.c \
 		cut.c \
 		files.c \
+		folding.c \
 		global.c \
 		help.c \
 		history.c \
diff --git a/src/cut.c b/src/cut.c
index 4458abce..954485f9 100644
--- a/src/cut.c
+++ b/src/cut.c
@@ -48,6 +48,7 @@ void do_deletion(undo_type action)
 		memmove(&openfile->current->data[openfile->current_x],
 					&openfile->current->data[openfile->current_x + charlen],
 					line_len - charlen + 1);
+		UNFOLD_SEGMENT(openfile->current);
 #ifndef NANO_TINY
 		/* When softwrapping, a changed number of chunks requires a refresh. */
 		if (ISSET(SOFTWRAP) && extra_chunks_in(openfile->current) != old_amount)
@@ -62,6 +63,9 @@ void do_deletion(undo_type action)
 	} else if (openfile->current != openfile->filebot) {
 		linestruct *joining = openfile->current->next;
 
+		UNFOLD_SEGMENT(openfile->current);
+		UNFOLD_SEGMENT(joining);
+
 		/* If there is a magic line, and we're before it: don't eat it. */
 		if (joining == openfile->filebot && openfile->current_x != 0 &&
 													!ISSET(NO_NEWLINES)) {
@@ -143,7 +147,8 @@ void do_backspace(void)
 		openfile->current_x = step_left(openfile->current->data, openfile->current_x);
 		do_deletion(BACK);
 	} else if (openfile->current != openfile->filetop) {
-		do_left();
+		openfile->current = openfile->current->prev;
+		openfile->current_x = strlen(openfile->current->data);
 		do_deletion(BACK);
 	}
 }
@@ -192,7 +197,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;
@@ -201,13 +206,14 @@ 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;
 			openfile->current_x = strlen(is_current->data);
 		}
 	}
+	UNFOLD_SEGMENT(openfile->current);
 
 	/* Set the mark at the start of that word. */
 	openfile->mark = openfile->current;
@@ -262,9 +268,13 @@ void extract_segment(linestruct *top, size_t top_x, linestruct *bot, size_t bot_
 	if (top == bot && top_x == bot_x)
 		return;
 
-	if (top != bot)
-		for (linestruct *line = top->next; line != bot->next; line = line->next)
+	if (top != bot) {
+		UNFOLD_SEGMENT(top);
+		for (linestruct *line = top->next; line != bot->next; line = line->next) {
+			UNFOLD_SEGMENT(line);
 			had_anchor |= line->has_anchor;
+		}
+	}
 #endif
 
 	if (top == bot) {
@@ -378,6 +388,8 @@ void ingraft_buffer(linestruct *topline)
 #endif
 	linestruct *botline = topline;
 
+	UNFOLD_SEGMENT(line);
+
 	while (botline->next != NULL)
 		botline = botline->next;
 
@@ -728,6 +740,7 @@ void paste_text(void)
 		statusline(AHEM, _("Cutbuffer is empty"));
 		return;
 	}
+	UNFOLD_SEGMENT(openfile->current);
 
 #ifndef NANO_TINY
 	add_undo(PASTE, NULL);
diff --git a/src/definitions.h b/src/definitions.h
index d6ca001d..bc108a41 100644
--- a/src/definitions.h
+++ b/src/definitions.h
@@ -273,6 +273,15 @@
 #define MSOME  MMAIN|MBROWSER
 #endif
 
+#ifdef ENABLE_FOLDING
+#define UNFOLD_SEGMENT(line) unfold_folded_segment(line)
+#else
+#define UNFOLD_SEGMENT(line)
+#endif
+
+#define ALLOW_FOLDED TRUE
+#define DISALLOW_FOLDED FALSE
+
 /* Enumeration types. */
 typedef enum {
 	UNSPECIFIED, NIX_FILE, DOS_FILE, MAC_FILE
@@ -374,6 +383,12 @@ enum {
 	ZERO
 };
 
+typedef enum {
+	FOUND_BRACKET = 0,
+	NOT_FOUND_BRACKET,
+	NOT_A_BRACKET
+} bracket_search_result;
+
 /* Structure types. */
 #ifdef ENABLE_COLOR
 typedef struct colortype {
@@ -480,6 +495,10 @@ typedef struct linestruct {
 	bool has_anchor;
 		/* Whether the user has placed an anchor at this line. */
 #endif
+#ifdef ENABLE_FOLDING
+	bool folded;
+		/* Whether or not this line is in a fold segment */
+#endif
 } linestruct;
 
 #ifndef NANO_TINY
diff --git a/src/files.c b/src/files.c
index c6eadc1c..5fdc9a00 100644
--- a/src/files.c
+++ b/src/files.c
@@ -681,6 +681,7 @@ void read_file(FILE *f, int fd, const char *filename, bool undoable)
 	if (ISSET(SOFTWRAP))
 		was_leftedge = leftedge_for(xplustabs(), openfile->current);
 #endif
+	UNFOLD_SEGMENT(openfile->current);
 
 	/* Create an empty buffer. */
 	topline = make_new_node(NULL);
diff --git a/src/folding.c b/src/folding.c
new file mode 100644
index 00000000..4b71dc05
--- /dev/null
+++ b/src/folding.c
@@ -0,0 +1,138 @@
+/**************************************************************************
+ *   folding.c  --  This file is part of GNU nano.                        *
+ *                                                                        *
+ *   Copyright (C) 2023 rexy712                                           *
+ *                                                                        *
+ *   GNU nano is free software: you can redistribute it and/or modify     *
+ *   it under the terms of the GNU General Public License as published    *
+ *   by the Free Software Foundation, either version 3 of the License,    *
+ *   or (at your option) any later version.                               *
+ *                                                                        *
+ *   GNU nano is distributed in the hope that it will be useful,          *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty          *
+ *   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.              *
+ *   See the GNU General Public License for more details.                 *
+ *                                                                        *
+ *   You should have received a copy of the GNU General Public License    *
+ *   along with this program.  If not, see http://www.gnu.org/licenses/.  *
+ *                                                                        *
+ **************************************************************************/
+
+#include "prototypes.h"
+
+#include <ctype.h>
+	/* isspace */
+
+#ifdef ENABLE_FOLDING
+
+/* Remove a folded segment from the given line until end of segment */
+void unfold_folded_segment_from(linestruct *line)
+{
+	if (line != NULL)
+		refresh_needed = TRUE;
+	for(;line != NULL && line->folded;line = line->next)
+		line->folded = FALSE;
+}
+
+/* Remove a folded segment containing the given line */
+void unfold_folded_segment(linestruct *line)
+{
+	unfold_folded_segment_from(get_start_of_folded_segment(line));
+}
+
+/* Get the first member of the folded segment */
+linestruct *get_start_of_folded_segment(linestruct *line)
+{
+	if (!line->folded)
+		return line;
+
+	while (line->prev != NULL && line->prev->folded)
+		line = line->prev;
+
+	return line;
+}
+/* Get the last member of the folded segment */
+linestruct *get_end_of_folded_segment(linestruct *line)
+{
+	if (!line->folded)
+		return line;
+
+	while (line->next != NULL && line->next->folded)
+		line = line->next;
+
+	return line;
+}
+
+/* Get the span length of a folded_segment */
+int get_folded_segment_length(linestruct *line)
+{
+	int i = 1;
+
+	if (!line->folded)
+		return 0;
+
+	line = get_start_of_folded_segment(line);
+	for (;line->next != NULL && line->next->folded;line = line->next)
+		++i;
+	return i;
+}
+
+
+/* Remove any folded_segments within the range [top, bottom].
+ * Returns true if any segments are removed. */
+bool do_unfold_segment(linestruct *line)
+{
+	/* Attempt to find a bracketed region first.
+	 * If not, top is unmodified. */
+	if (!line->folded) {
+		linestruct *bot;
+		find_bracketed_region(openfile->current, &line, &bot);
+	}
+
+	if (line->folded) {
+		unfold_folded_segment(line);
+		move_cursor_to_proper_column();
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+/* Fold the currently selected lines unless the current selection
+ * already contains a folded_segment. In that case, remove any
+ * segments that are selected. */
+void do_fold_segment(void)
+{
+	linestruct *top, *bot;
+	get_range(&top, &bot);
+
+	if (top == bot) {
+		/* First try to unfold if this line/bracketed region is folded */
+		if (do_unfold_segment(top))
+			return;
+
+		/* When not selecting multiple lines, try to find bounding
+		 * brackets to act as top and bot. */
+		if (!find_bracketed_region(openfile->current, &top, &bot)) {
+			statusline(AHEM, _("No valid region found for automatic fold"));
+			return;
+		}
+	}
+
+	for (linestruct* line = top;line != bot->next;line = line->next)
+		line->folded = TRUE;
+
+	if (top->lineno < openfile->edittop->lineno &&
+	    bot->lineno > openfile->edittop->lineno)
+		openfile->edittop = top;
+
+	/* Place the cursor at the start of the fold segment.
+	 * Anywhere else within the segment is invalid. */
+	if (openfile->current->folded)
+		openfile->current = get_start_of_folded_segment(top);
+	openfile->mark = NULL;
+
+	refresh_needed = TRUE;
+}
+
+#endif /* ENABLE_FOLDING */
diff --git a/src/global.c b/src/global.c
index ed93fa31..e7217a02 100644
--- a/src/global.c
+++ b/src/global.c
@@ -642,6 +642,9 @@ void shortcut_init(void)
 	const char *savefile_gist = N_("Save file without prompting");
 	const char *findprev_gist = N_("Search next occurrence backward");
 	const char *findnext_gist = N_("Search next occurrence forward");
+#ifdef ENABLE_FOLDING
+	const char *fold_gist = N_("Fold/unfold the currently selected lines");
+#endif
 #ifndef NANO_TINY
 	const char *recordmacro_gist = N_("Start/stop recording a macro");
 	const char *runmacro_gist = N_("Run the last recorded macro");
@@ -998,7 +1001,14 @@ void shortcut_init(void)
 			N_("Record"), WHENHELP(recordmacro_gist), TOGETHER);
 	add_to_funcs(run_macro, MMAIN,
 			N_("Run Macro"), WHENHELP(runmacro_gist), BLANKAFTER);
+#endif
 
+#ifdef ENABLE_FOLDING
+	add_to_funcs(do_fold_segment, MMAIN, N_("Fold"),
+		WHENHELP(fold_gist), BLANKAFTER);
+#endif
+
+#ifndef NANO_TINY
 	add_to_funcs(zap_text, MMAIN,
 			/* TRANSLATORS: This refers to deleting a line or marked region. */
 			N_("Zap"), WHENHELP(zap_gist), BLANKAFTER);
@@ -1208,6 +1218,9 @@ void shortcut_init(void)
 #ifdef ENABLE_FORMATTER
 	add_to_sclist(MMAIN, "M-F", 0, do_formatter, 0);
 	add_to_sclist(MEXECUTE, "^O", 0, do_formatter, 0);
+#endif
+#ifdef ENABLE_FOLDING
+	add_to_sclist(MMAIN, "M-[", 0, do_fold_segment, 0);
 #endif
 	add_to_sclist(MMAIN, "^C", 0, report_cursor_position, 0);
 	add_to_sclist(MMAIN, SLASH_OR_DASH, 0, do_gotolinecolumn, 0);
@@ -1341,10 +1354,10 @@ void shortcut_init(void)
 	add_to_sclist(MMAIN|MHELP, "M-=", 0, do_scroll_down, 0);
 #endif
 #ifdef ENABLE_MULTIBUFFER
-	add_to_sclist(MMAIN, "M-,", 0, switch_to_prev_buffer, 0);
 	add_to_sclist(MMAIN, "M-<", 0, switch_to_prev_buffer, 0);
-	add_to_sclist(MMAIN, "M-.", 0, switch_to_next_buffer, 0);
+	add_to_sclist(MMAIN, "M-,", 0, switch_to_prev_buffer, 0);
 	add_to_sclist(MMAIN, "M->", 0, switch_to_next_buffer, 0);
+	add_to_sclist(MMAIN, "M-.", 0, switch_to_next_buffer, 0);
 #endif
 	add_to_sclist(MMOST, "M-V", 0, do_verbatim_input, 0);
 #ifndef NANO_TINY
diff --git a/src/move.c b/src/move.c
index 9b1d80c3..9a81a0fd 100644
--- a/src/move.c
+++ b/src/move.c
@@ -37,7 +37,13 @@ void to_first_line(void)
 void to_last_line(void)
 {
 	openfile->current = openfile->filebot;
-	openfile->current_x = (inhelp) ? 0 : strlen(openfile->filebot->data);
+#ifdef ENABLE_FOLDING
+	if (openfile->current->folded) {
+		openfile->current = get_start_of_folded_segment(openfile->current);
+		openfile->current_x = 0;
+	} else
+#endif
+		openfile->current_x = (inhelp) ? 0 : strlen(openfile->filebot->data);
 	openfile->placewewant = xplustabs();
 
 	/* Set the last line of the screen as the target for the cursor. */
@@ -112,6 +118,15 @@ void set_proper_index_and_pww(size_t *leftedge, size_t target, bool forward)
 	openfile->placewewant = *leftedge + target;
 }
 
+/* Move the cursor position on the current line to the desired column */
+void move_cursor_to_proper_column(void)
+{
+	size_t leftedge;
+	size_t target_column;
+	get_edge_and_target(&leftedge, &target_column);
+	set_proper_index_and_pww(&leftedge, target_column, TRUE);
+}
+
 /* Move up almost one screenful. */
 void do_page_up(void)
 {
@@ -235,19 +250,29 @@ void to_para_end(void)
 void to_prev_block(void)
 {
 	linestruct *was_current = openfile->current;
+	linestruct *line_step = get_prev_visible_line(openfile->current);
 	bool is_text = FALSE, seen_text = FALSE;
 
 	/* Skip backward until first blank line after some nonblank line(s). */
-	while (openfile->current->prev != NULL && (!seen_text || is_text)) {
-		openfile->current = openfile->current->prev;
+	while (line_step != NULL && (!seen_text || is_text)) {
+		openfile->current = line_step;
+		line_step = get_prev_visible_line(line_step);
+#ifdef ENABLE_FOLDING
+		/* Skip over the visibly drawn line of the folded segment */
+		if (openfile->current->folded)
+			continue;
+#endif
 		is_text = !white_string(openfile->current->data);
 		seen_text = seen_text || is_text;
 	}
 
 	/* Step forward one line again if we passed text but this line is blank. */
-	if (seen_text && openfile->current->next != NULL &&
-				white_string(openfile->current->data))
-		openfile->current = openfile->current->next;
+	line_step = get_next_visible_line(openfile->current);
+	if (seen_text && line_step != NULL &&
+				white_string(openfile->current->data)) {
+		openfile->current = line_step;
+		line_step = get_next_visible_line(line_step);
+	}
 
 	openfile->current_x = 0;
 	edit_redraw(was_current, CENTERING);
@@ -257,12 +282,19 @@ void to_prev_block(void)
 void to_next_block(void)
 {
 	linestruct *was_current = openfile->current;
+	linestruct *line_step = get_next_visible_line(openfile->current);
 	bool is_white = white_string(openfile->current->data);
 	bool seen_white = is_white;
 
 	/* Skip forward until first nonblank line after some blank line(s). */
-	while (openfile->current->next != NULL && (!seen_white || is_white)) {
-		openfile->current = openfile->current->next;
+	while (line_step != NULL && (!seen_white || is_white)) {
+		openfile->current = line_step;
+		line_step = get_next_visible_line(line_step);
+#ifdef ENABLE_FOLDING
+		/* Skip over the visibly drawn line of the folded segment */
+		if (openfile->current->folded)
+			continue;
+#endif
 		is_white = white_string(openfile->current->data);
 		seen_white = seen_white || is_white;
 	}
@@ -274,19 +306,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_folded) {
+				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);
 		}
 
@@ -315,11 +363,13 @@ void do_prev_word(void)
 		/* Move one character forward again to sit on the start of the word. */
 		openfile->current_x = step_right(openfile->current->data,
 												openfile->current_x);
+	UNFOLD_SEGMENT(openfile->current);
 }
 
 /* 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 +
@@ -329,6 +379,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. */
@@ -336,7 +392,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_folded) {
+				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 {
@@ -376,6 +443,8 @@ bool do_next_word(bool after_ends)
 		}
 	}
 
+	UNFOLD_SEGMENT(openfile->current);
+
 	return started_on_word;
 }
 
@@ -384,7 +453,7 @@ void to_prev_word(void)
 {
 	linestruct *was_current = openfile->current;
 
-	do_prev_word();
+	do_prev_word(DISALLOW_FOLDED);
 
 	edit_redraw(was_current, FLOWING);
 }
@@ -395,7 +464,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);
 }
@@ -413,6 +482,11 @@ void do_home(void)
 	size_t leftedge = 0;
 	size_t left_x = 0;
 
+#ifdef ENABLE_FOLDING
+	if (openfile->current->folded)
+		return;
+#endif
+
 	if (ISSET(SOFTWRAP)) {
 		leftedge = leftedge_for(was_column, openfile->current);
 		left_x = proper_x(openfile->current, &leftedge, FALSE, leftedge, NULL);
@@ -470,6 +544,11 @@ void do_end(void)
 	size_t line_len = strlen(openfile->current->data);
 	bool moved_off_chunk = TRUE;
 
+#ifdef ENABLE_FOLDING
+	if (openfile->current->folded)
+		return;
+#endif
+
 #ifndef NANO_TINY
 	if (ISSET(SOFTWRAP)) {
 		bool kickoff = TRUE;
@@ -612,7 +691,7 @@ void do_left(void)
 												openfile->current_x);
 #endif
 	} else if (openfile->current != openfile->filetop) {
-		openfile->current = openfile->current->prev;
+		openfile->current = get_prev_visible_line(openfile->current);
 		openfile->current_x = strlen(openfile->current->data);
 	}
 
@@ -624,6 +703,13 @@ void do_right(void)
 {
 	linestruct *was_current = openfile->current;
 
+#ifdef ENABLE_FOLDING
+	if (openfile->current->folded) {
+		openfile->current = get_next_visible_line(openfile->current);
+		if (openfile->current == NULL)
+			openfile->current = was_current;
+	} else
+#endif
 	if (openfile->current->data[openfile->current_x] != '\0') {
 		openfile->current_x = step_right(openfile->current->data,
 												openfile->current_x);
@@ -634,7 +720,7 @@ void do_right(void)
 												openfile->current_x);
 #endif
 	} else if (openfile->current != openfile->filebot) {
-		openfile->current = openfile->current->next;
+		openfile->current = get_next_visible_line(openfile->current);
 		openfile->current_x = 0;
 	}
 
diff --git a/src/nano.c b/src/nano.c
index 8578ee7d..783f2997 100644
--- a/src/nano.c
+++ b/src/nano.c
@@ -78,6 +78,9 @@ linestruct *make_new_node(linestruct *prevnode)
 #ifndef NANO_TINY
 	newnode->has_anchor = FALSE;
 #endif
+#ifdef ENABLE_FOLDING
+	newnode->folded = FALSE;
+#endif
 
 	return newnode;
 }
@@ -117,6 +120,7 @@ void delete_node(linestruct *line)
 /* Disconnect a node from a linked list of linestructs and delete it. */
 void unlink_node(linestruct *line)
 {
+	UNFOLD_SEGMENT(line);
 	if (line->prev != NULL)
 		line->prev->next = line->next;
 	if (line->next != NULL)
@@ -156,6 +160,9 @@ linestruct *copy_node(const linestruct *src)
 #ifndef NANO_TINY
 	dst->has_anchor = src->has_anchor;
 #endif
+#ifdef ENABLE_FOLDING
+	dst->folded = src->folded;
+#endif
 
 	return dst;
 }
@@ -1461,6 +1468,7 @@ void inject(char *burst, size_t count)
 #ifndef NANO_TINY
 	size_t original_row = 0;
 	size_t old_amount = 0;
+	UNFOLD_SEGMENT(thisline);
 
 	if (ISSET(SOFTWRAP)) {
 		if (openfile->current_y == editwinrows - 1)
diff --git a/src/prototypes.h b/src/prototypes.h
index d2b2e1a5..66449b49 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);
@@ -380,6 +380,7 @@ void do_center(void);
 #endif
 void do_left(void);
 void do_right(void);
+void move_cursor_to_proper_column(void);
 
 /* Most functions in nano.c. */
 linestruct *make_new_node(linestruct *prevnode);
@@ -476,6 +477,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);
@@ -579,6 +583,8 @@ linestruct *line_from_number(ssize_t number);
 #endif
 
 /* Most functions in winio.c. */
+linestruct *get_next_visible_line(linestruct *line);
+linestruct *get_prev_visible_line(linestruct *line);
 #ifndef NANO_TINY
 void record_macro(void);
 void run_macro(void);
@@ -610,6 +616,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);
@@ -674,3 +681,13 @@ void flip_newbuffer(void);
 #endif
 void discard_buffer(void);
 void do_cancel(void);
+
+/* Most functions in folding.c. */
+#ifdef ENABLE_FOLDING
+void do_fold_segment(void);
+void unfold_folded_segment(linestruct *line);
+void unfold_folded_segment_from(linestruct *line);
+linestruct *get_start_of_folded_segment(linestruct* line);
+linestruct *get_end_of_folded_segment(linestruct* line);
+int get_folded_segment_length(linestruct *line);
+#endif
diff --git a/src/search.c b/src/search.c
index 7fbb244c..3ac86a1f 100644
--- a/src/search.c
+++ b/src/search.c
@@ -308,6 +308,8 @@ int findnextstr(const char *needle, bool whole_word_only, int modus,
 		}
 	}
 
+	UNFOLD_SEGMENT(line);
+
 	found_x = found - line->data;
 
 	nodelay(midwin, FALSE);
@@ -754,6 +756,7 @@ void goto_line_posx(ssize_t linenumber, size_t pos_x)
 
 	openfile->current_x = pos_x;
 	openfile->placewewant = xplustabs();
+	UNFOLD_SEGMENT(openfile->current);
 
 	refresh_needed = TRUE;
 }
@@ -808,16 +811,27 @@ void goto_line_and_column(ssize_t line, ssize_t column, bool retain_answer,
 		line = 1;
 
 #ifdef ENABLE_COLOR
+#ifdef ENABLE_FOLDING
+	linestruct *edit_bottom = openfile->edittop;
+	for (int i = 0;edit_bottom != openfile->filebot && i < editwinrows;++i)
+		edit_bottom = get_next_visible_line(edit_bottom);
+	if (line > edit_bottom->lineno ||
+				(ISSET(SOFTWRAP) && line > openfile->current->lineno))
+		recook |= perturbed;
+#else
 	if (line > openfile->edittop->lineno + editwinrows ||
 				(ISSET(SOFTWRAP) && line > openfile->current->lineno))
 		recook |= perturbed;
-#endif
+#endif /* ENABLE_FOLDING */
+#endif /* ENABLE_COLOR */
 
 	/* Iterate to the requested line. */
 	for (openfile->current = openfile->filetop; line > 1 &&
 				openfile->current != openfile->filebot; line--)
 		openfile->current = openfile->current->next;
 
+	UNFOLD_SEGMENT(openfile->current);
+
 	/* Take a negative column number to mean: from the end of the line. */
 	if (column < 0)
 		column = breadth(openfile->current->data) + column + 2;
@@ -872,23 +886,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))) {
@@ -898,7 +999,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;
@@ -908,45 +1009,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++)
@@ -954,19 +1034,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);
 
@@ -974,24 +1070,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) {
-			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. */
@@ -1015,6 +1152,7 @@ void go_to_and_confirm(linestruct *line)
 	if (line != openfile->current) {
 		openfile->current = line;
 		openfile->current_x = 0;
+		UNFOLD_SEGMENT(openfile->current);
 #ifdef ENABLE_COLOR
 		if (line->lineno > openfile->edittop->lineno + editwinrows ||
 					(ISSET(SOFTWRAP) && line->lineno > was_current->lineno))
diff --git a/src/text.c b/src/text.c
index e3713c1d..67caa20c 100644
--- a/src/text.c
+++ b/src/text.c
@@ -96,6 +96,8 @@ void indent_a_line(linestruct *line, char *indentation)
 	if (indent_len == 0)
 		return;
 
+	UNFOLD_SEGMENT(line);
+
 	/* Add the fabricated indentation to the beginning of the line. */
 	line->data = nrealloc(line->data, length + indent_len + 1);
 	memmove(line->data + indent_len, line->data, length + 1);
@@ -226,6 +228,8 @@ void unindent_a_line(linestruct *line, size_t indent_len)
 	if (indent_len == 0)
 		return;
 
+	UNFOLD_SEGMENT(line);
+
 	/* Remove the first tab's worth of whitespace from this line. */
 	memmove(line->data, line->data + indent_len, length - indent_len + 1);
 
@@ -317,6 +321,8 @@ bool comment_line(undo_type action, linestruct *line, const char *comment_seq)
 		/* Length of postfix. */
 	size_t line_len = strlen(line->data);
 
+	UNFOLD_SEGMENT(line);
+
 	if (!ISSET(NO_NEWLINES) && line == openfile->filebot)
 		return FALSE;
 
@@ -859,6 +865,12 @@ void do_enter(void)
 {
 	linestruct *newnode = make_new_node(openfile->current);
 	size_t extra = 0;
+#ifdef ENABLE_FOLDING
+	if (openfile->current->folded) {
+		newnode->folded = TRUE;
+		openfile->current->folded = FALSE;
+	}
+#endif
 #ifndef NANO_TINY
 	linestruct *sampleline = openfile->current;
 	bool allblanks = FALSE;
@@ -1585,6 +1597,7 @@ void concat_paragraph(linestruct *line, size_t count)
 		line->data = nrealloc(line->data,
 								line_len + next_line_len - next_lead_len + 1);
 		strcat(line->data, next_line->data + next_lead_len);
+		UNFOLD_SEGMENT(line);
 #ifndef NANO_TINY
 		line->has_anchor |= next_line->has_anchor;
 #endif
@@ -2852,6 +2865,7 @@ void do_linter(void)
 
 		/* Place the cursor to indicate the affected line. */
 		place_the_cursor();
+		UNFOLD_SEGMENT(openfile->current);
 		wnoutrefresh(midwin);
 
 		kbinput = get_kbinput(footwin, VISIBLE);
@@ -2993,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 fee2a84e..22c04a2a 100644
--- a/src/winio.c
+++ b/src/winio.c
@@ -140,6 +140,55 @@ void run_macro(void)
 }
 #endif /* !NANO_TINY */
 
+/* Return the current offset of target from openfile->edittop */
+int get_row_from_edittop(linestruct *target)
+{
+#ifdef ENABLE_FOLDING
+	linestruct *line = openfile->edittop;
+	int row = 0;
+	while (line != NULL && line != target && row < editwinrows) {
+		line = get_end_of_folded_segment(line);
+		line = line->next;
+		++row;
+	}
+	return row;
+#else
+	return target->lineno - openfile->edittop->lineno;
+#endif
+}
+
+/* Return the next line, taking fold segments into account */
+linestruct *get_next_visible_line(linestruct *line)
+{
+	if (!line)
+		return NULL;
+
+#ifdef ENABLE_FOLDING
+	line = get_end_of_folded_segment(line);
+	if (line == NULL)
+		return NULL;
+#endif
+	return line->next;
+}
+
+/* Return the previous line, taking fold segments into account */
+linestruct *get_prev_visible_line(linestruct *line)
+{
+	if (!line)
+		return NULL;
+
+#ifdef ENABLE_FOLDING
+	if (line->folded) {
+		line = get_start_of_folded_segment(line);
+		return line == NULL ? NULL : line->prev;
+	}
+	if (line->prev != NULL && line->prev->folded)
+		return get_start_of_folded_segment(line->prev);
+
+#endif
+	return line->prev;
+}
+
 /* Allocate the requested space for the keystroke buffer. */
 void reserve_space_for(size_t newsize)
 {
@@ -2489,20 +2538,37 @@ void place_the_cursor(void)
 
 		/* Calculate how many rows the lines from edittop to current use. */
 		while (line != NULL && line != openfile->current) {
-			row += 1 + extra_chunks_in(line);
+#ifdef ENABLE_FOLDING
+			if (line->folded) {
+				line = get_end_of_folded_segment(line);
+				++row;
+			} else
+#endif
+				row += 1 + extra_chunks_in(line);
 			line = line->next;
 		}
 
-		/* Add the number of wraps in the current line before the cursor. */
-		row += get_chunk_and_edge(column, openfile->current, &leftedge);
-		column -= leftedge;
-	} else
+#ifdef ENABLE_FOLDING
+		if (!openfile->current->folded)
 #endif
+		{
+			/* Add the number of wraps in the current line before the cursor. */
+			row += get_chunk_and_edge(column, openfile->current, &leftedge);
+			column -= leftedge;
+		}
+	} else
+#endif /* !NANO_TINY */
 	{
-		row = openfile->current->lineno - openfile->edittop->lineno;
+		row = get_row_from_edittop(openfile->current);
 		column -= get_page_start(column);
 	}
 
+#ifdef ENABLE_FOLDING
+	if (openfile->current->folded) {
+		openfile->current_x = 0;
+		column = 0;
+	}
+#endif
 	if (row < editwinrows)
 		wmove(midwin, row, margin + column);
 #ifndef NANO_TINY
@@ -2516,17 +2582,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, " ");
@@ -2546,8 +2615,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);
@@ -2801,18 +2882,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);
@@ -2820,7 +2933,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. */
@@ -2843,6 +2955,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
@@ -2875,8 +2996,13 @@ int update_softwrapped_line(linestruct *line)
 
 	/* Find out on which screen row the target line should be shown. */
 	while (someline != line && someline != NULL) {
-		row += 1 + extra_chunks_in(someline);
-		someline = someline->next;
+#ifdef ENABLE_FOLDING
+		if (someline->folded)
+			++row;
+		else
+#endif
+			row += 1 + extra_chunks_in(someline);
+		someline = get_next_visible_line(someline);
 	}
 
 	/* If the first chunk is offscreen, don't even try to display it. */
@@ -2888,6 +3014,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);
 
@@ -2934,7 +3065,14 @@ int go_back_chunks(int nrows, linestruct **line, size_t *leftedge)
 	if (ISSET(SOFTWRAP)) {
 		/* Recede through the requested number of chunks. */
 		for (i = nrows; i > 0; i--) {
-			size_t chunk = chunk_for(*leftedge, *line);
+			size_t chunk;
+
+#ifdef ENABLE_FOLDING
+			if ((*line)->folded)
+				chunk = 0;
+			else
+#endif
+				chunk = chunk_for(*leftedge, *line);
 
 			*leftedge = 0;
 
@@ -2945,16 +3083,25 @@ int go_back_chunks(int nrows, linestruct **line, size_t *leftedge)
 				break;
 
 			i -= chunk;
-			*line = (*line)->prev;
+			*line = get_prev_visible_line(*line);
 			*leftedge = HIGHEST_POSITIVE;
 		}
-
+#ifdef ENABLE_FOLDING
+		if ((*line)->folded)
+			*leftedge = 0;
+		else
+#endif
 		if (*leftedge == HIGHEST_POSITIVE)
 			*leftedge = leftedge_for(*leftedge, *line);
 	} else
-#endif
-		for (i = nrows; i > 0 && (*line)->prev != NULL; i--)
-			*line = (*line)->prev;
+#endif /* !NANO_TINY */
+	{
+		linestruct *prev = get_prev_visible_line(*line);
+		for (i = nrows; i > 0 && prev != NULL; i--) {
+			*line = prev;
+			prev = get_prev_visible_line(prev);
+		}
+	}
 
 	return i;
 }
@@ -2974,6 +3121,14 @@ int go_forward_chunks(int nrows, linestruct **line, size_t *leftedge)
 
 		/* Advance through the requested number of chunks. */
 		for (i = nrows; i > 0; i--) {
+#ifdef ENABLE_FOLDING
+			if ((*line)->folded) {
+				*line = get_next_visible_line(*line);
+				current_leftedge = 0;
+				continue;
+			}
+#endif
+
 			bool end_of_line = FALSE;
 
 			current_leftedge = get_softwrap_breakpoint((*line)->data,
@@ -2994,9 +3149,14 @@ int go_forward_chunks(int nrows, linestruct **line, size_t *leftedge)
 		if (i < nrows)
 			*leftedge = current_leftedge;
 	} else
-#endif
-		for (i = nrows; i > 0 && (*line)->next != NULL; i--)
-			*line = (*line)->next;
+#endif /* !NANO_TINY */
+	{
+		linestruct *next = get_next_visible_line(*line);
+		for (i = nrows; i > 0 && next != NULL; i--) {
+			*line = next;
+			next = get_next_visible_line(*line);
+		}
+	}
 
 	return i;
 }
@@ -3023,20 +3183,47 @@ bool less_than_a_screenful(size_t was_lineno, size_t was_leftedge)
 /* Draw a scroll bar on the righthand side of the screen. */
 void draw_scrollbar(void)
 {
-	int fromline = openfile->edittop->lineno - 1;
 	int totallines = openfile->filebot->lineno;
-	int coveredlines = editwinrows;
+	int fromline = openfile->edittop->lineno - 1;
+	int coveredlines = 0;
+	linestruct *line = openfile->edittop;
 
 	if (ISSET(SOFTWRAP)) {
-		linestruct *line = openfile->edittop;
-		int extras = extra_chunks_in(line) - chunk_for(openfile->firstcolumn, line);
-
-		while (line->lineno + extras < fromline + editwinrows && line->next) {
-			line = line->next;
-			extras += extra_chunks_in(line);
+		int i = 0;
+#ifdef ENABLE_FOLDING
+		if (line->folded)
+			coveredlines += get_folded_segment_length(line);
+		else
+#endif
+		{
+			i = extra_chunks_in(line) - chunk_for(openfile->firstcolumn, line);
+			coveredlines = i;
 		}
 
-		coveredlines = line->lineno - fromline;
+		for (;i < editwinrows && line->next != NULL;++i) {
+			line = get_next_visible_line(line);
+#ifdef ENABLE_FOLDING
+			if (line->folded)
+				coveredlines += get_folded_segment_length(line);
+			else
+#endif
+			{
+				i += extra_chunks_in(line);
+				++coveredlines;
+			}
+		}
+	} else {
+#ifdef ENABLE_FOLDING
+		for (int i = 0;i < editwinrows && line->next != NULL;++i) {
+			if (line->folded)
+				coveredlines += get_folded_segment_length(line);
+			else
+				++coveredlines;
+			line = get_next_visible_line(line);
+		}
+#else
+		coveredlines = editwinrows;
+#endif /* ENABLE_FOLDING */
 	}
 
 	int lowest = (fromline * editwinrows) / totallines;
@@ -3089,7 +3276,12 @@ void edit_scroll(bool direction)
 	if (thebar)
 		draw_scrollbar();
 
-	if (ISSET(SOFTWRAP)) {
+#ifdef ENABLE_FOLDING
+	if (ISSET(SOFTWRAP) && !line->folded)
+#else
+	if (ISSET(SOFTWRAP))
+#endif
+	{
 		/* Compensate for the earlier chunks of a softwrapped line. */
 		nrows += chunk_for(leftedge, line);
 
@@ -3101,10 +3293,11 @@ void edit_scroll(bool direction)
 
 	/* Draw new content on the blank row (and on the bordering row too
 	 * when it was deemed necessary). */
+	int row = get_row_from_edittop(line);
 	while (nrows > 0 && line != NULL) {
-		nrows -= update_line(line, (line == openfile->current) ?
-										openfile->current_x : 0);
-		line = line->next;
+		nrows -= update_line_at(line, (line == openfile->current) ?
+		                              openfile->current_x : 0, row++);
+		line = get_next_visible_line(line);
 	}
 }
 
@@ -3301,8 +3494,7 @@ bool current_is_below_screen(void)
 						leftedge < leftedge_for(xplustabs(), openfile->current))));
 	} else
 #endif
-		return (openfile->current->lineno >=
-						openfile->edittop->lineno + editwinrows - SHIM);
+		return (get_row_from_edittop(openfile->current) >= editwinrows - SHIM);
 }
 
 /* Return TRUE if current[current_x] is outside the viewport. */
@@ -3330,12 +3522,17 @@ void edit_redraw(linestruct *old_current, update_type manner)
 	/* If the mark is on, update all lines between old_current and current. */
 	if (openfile->mark) {
 		linestruct *line = old_current;
-
+		int row = get_row_from_edittop(line);
 		while (line != openfile->current) {
-			update_line(line, 0);
-
-			line = (line->lineno > openfile->current->lineno) ?
-						line->prev : line->next;
+			update_line_at(line, 0, row);
+
+			if (line->lineno > openfile->current->lineno) {
+				line = get_prev_visible_line(line);
+				--row;
+			} else {
+				line = get_next_visible_line(line);
+				++row;
+			}
 		}
 	} else
 #endif
@@ -3390,9 +3587,13 @@ void edit_refresh(void)
 
 	while (row < editwinrows && line != NULL) {
 		if (line == openfile->current)
-			row += update_line(line, openfile->current_x);
+			row += update_line_at(line, openfile->current_x, row);
 		else
-			row += update_line(line, 0);
+			row += update_line_at(line, 0, row);
+
+#ifdef ENABLE_FOLDING
+		line = get_end_of_folded_segment(line);
+#endif
 		line = line->next;
 	}
 
-- 
2.39.2
0003-Modify-signatures-of-functions-to-be-modular-for-fur.patch (text/x-patch, 22.6 KB)
From 0c8fa23e4e2b4eec8ecc58bfc8740a866494482a Mon Sep 17 00:00:00 2001
From: rexy712 <[email protected]>
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 <[email protected]>
---
 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_folded) {
+				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_folded) {
+				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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.