From 23826fa07bd551db9125ea726e945f3c4398e098 Mon Sep 17 00:00:00 2001
From: rexy712 <rexy712@protonmail.com>
Date: Mon, 4 Jul 2022 15:27:00 -0700
Subject: [PATCH] Add line folding support

Folding lines 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.

I also found this bug report before I began implementing line folding,
which told me I'm not the only one who would like to see this implemented:
bug #58199: [Wish] the ability to temporarily "fold" lines

Signed-off-by: rexy712 <rexy712@protonmail.com>
---
 src/color.c       |   9 +-
 src/cut.c         |  44 ++++++--
 src/definitions.h |  22 ++++
 src/files.c       |   8 ++
 src/global.c      |   7 ++
 src/move.c        | 152 +++++++++++++++++++++----
 src/nano.c        |  11 +-
 src/prototypes.h  |  30 ++++-
 src/rcfile.c      |   3 +
 src/search.c      |  28 ++++-
 src/text.c        | 236 ++++++++++++++++++++++++++++++++++++++-
 src/winio.c       | 277 ++++++++++++++++++++++++++++++++++++----------
 12 files changed, 727 insertions(+), 100 deletions(-)

diff --git a/src/color.c b/src/color.c
index 4bb1b426..16e643a2 100644
--- a/src/color.c
+++ b/src/color.c
@@ -67,8 +67,15 @@ void set_interface_colorpairs(void)
 			else if (index == ERROR_MESSAGE) {
 				init_pair(index + 1, COLOR_WHITE, COLOR_RED);
 				interface_color_pair[index] = COLOR_PAIR(index + 1) | A_BOLD;
-			} else
+			} else if(index == FOLDED_LINE) {
+				if(COLORS > 15)
+					init_pair(index + 1, COLOR_BLACK + 8, COLOR_BLACK);
+				else
+					init_pair(index + 1, COLOR_WHITE, COLOR_BLACK);
+				interface_color_pair[index] = COLOR_PAIR(index + 1);
+			} else {
 				interface_color_pair[index] = hilite_attribute;
+			}
 		}
 
 		free(color_combo[index]);
diff --git a/src/cut.c b/src/cut.c
index ac0f45f3..efc1eefd 100644
--- a/src/cut.c
+++ b/src/cut.c
@@ -27,6 +27,9 @@
 /* Delete the character under the cursor. */
 void do_deletion(undo_type action)
 {
+#ifndef NANO_TINY
+	folded_segment *seg = get_folded_segment(openfile->current);
+#endif
 	openfile->placewewant = xplustabs();
 
 	/* When in the middle of a line, delete the current character. */
@@ -49,6 +52,10 @@ void do_deletion(undo_type action)
 					&openfile->current->data[openfile->current_x + charlen],
 					line_len - charlen + 1);
 #ifndef NANO_TINY
+		if (seg) {
+			remove_folded_segment(seg);
+			refresh_needed = TRUE;
+		}
 		/* When softwrapping, a changed number of chunks requires a refresh. */
 		if (ISSET(SOFTWRAP) && extra_chunks_in(openfile->current) != old_amount)
 			refresh_needed = TRUE;
@@ -62,6 +69,13 @@ void do_deletion(undo_type action)
 	} else if (openfile->current != openfile->filebot) {
 		linestruct *joining = openfile->current->next;
 
+#ifndef NANO_TINY
+		if (seg || (seg = get_folded_segment(joining))) {
+			remove_folded_segment(seg);
+			refresh_needed = TRUE;
+		}
+#endif
+
 		/* 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)) {
@@ -136,7 +150,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);
 	}
 }
@@ -185,7 +200,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(TRUE);
 		if (openfile->current != is_current) {
 			if (is_current_x > 0) {
 				openfile->current = is_current;
@@ -194,13 +209,16 @@ 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), TRUE);
 		if (openfile->current != is_current &&
 							is_current->data[is_current_x] != '\0') {
 			openfile->current = is_current;
 			openfile->current_x = strlen(is_current->data);
 		}
 	}
+#ifndef NANO_TINY
+	remove_if_in_any_folded_segment(openfile->current);
+#endif
 
 	/* Set the mark at the start of that word. */
 	openfile->mark = openfile->current;
@@ -255,9 +273,12 @@ 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) {
+		for (linestruct *line = top->next; line != bot->next; line = line->next) {
 			had_anchor |= line->has_anchor;
+			remove_if_in_any_folded_segment(line);
+		}
+	}
 #endif
 
 	if (top == bot) {
@@ -279,8 +300,12 @@ void extract_segment(linestruct *top, size_t top_x, linestruct *bot, size_t bot_
 		bot->prev = top->prev;
 		if (top->prev)
 			top->prev->next = bot;
-		else
+		else {
 			openfile->filetop = bot;
+#ifndef NANO_TINY
+			remove_if_in_any_folded_segment(openfile->filetop);
+#endif
+		}
 
 		openfile->current = bot;
 	} else {
@@ -341,8 +366,12 @@ void extract_segment(linestruct *top, size_t top_x, linestruct *bot, size_t bot_
 	if (post_marked)
 		openfile->mark_x = openfile->current_x;
 #endif
-	if (openfile->filebot == bot)
+	if (openfile->filebot == bot) {
 		openfile->filebot = openfile->current;
+#ifndef NANO_TINY
+		remove_if_in_any_folded_segment(openfile->filebot);
+#endif
+	}
 
 	renumber_from(openfile->current);
 
@@ -712,6 +741,7 @@ void paste_text(void)
 	linestruct *was_current = openfile->current;
 #endif
 #ifndef NANO_TINY
+	remove_if_in_any_folded_segment(openfile->current);
 	bool had_anchor = was_current->has_anchor;
 #endif
 	ssize_t was_lineno = openfile->current->lineno;
diff --git a/src/definitions.h b/src/definitions.h
index 5c517a3e..e3e34f91 100644
--- a/src/definitions.h
+++ b/src/definitions.h
@@ -310,6 +310,7 @@ enum {
 	ERROR_MESSAGE,
 	KEY_COMBO,
 	FUNCTION_TAG,
+	FOLDED_LINE,
 	NUMBER_OF_ELEMENTS
 };
 
@@ -455,6 +456,21 @@ typedef struct lintstruct {
 } lintstruct;
 #endif /* ENABLE_COLOR */
 
+#ifndef NANO_TINY
+struct linestruct;
+typedef struct folded_segment {
+	struct linestruct *start_line;
+		/* The first line of the fold */
+	struct linestruct *end_line;
+		/* The last line of the fold */
+
+	struct folded_segment *next;
+		/* Next node */
+	struct folded_segment *prev;
+		/* Previous node */
+}folded_segment;
+#endif
+
 /* More structure types. */
 typedef struct linestruct {
 	char *data;
@@ -472,6 +488,8 @@ typedef struct linestruct {
 #ifndef NANO_TINY
 	bool has_anchor;
 		/* Whether the user has placed an anchor at this line. */
+	struct folded_segment *fold;
+		/* Folded segment this line is a part of */
 #endif
 } linestruct;
 
@@ -590,6 +608,10 @@ typedef struct openfilestruct {
 	struct openfilestruct *prev;
 		/* The preceding open file, if any. */
 #endif
+#ifndef NANO_TINY
+	folded_segment *fold_segments;
+		/* Linked list of folded segments */
+#endif
 } openfilestruct;
 
 #ifdef ENABLE_NANORC
diff --git a/src/files.c b/src/files.c
index 852bb24a..8840e3cc 100644
--- a/src/files.c
+++ b/src/files.c
@@ -96,6 +96,7 @@ void make_new_buffer(void)
 
 	openfile->statinfo = NULL;
 	openfile->lock_filename = NULL;
+	openfile->fold_segments = NULL;
 #endif
 #ifdef ENABLE_MULTIBUFFER
 	openfile->errormessage = NULL;
@@ -615,6 +616,10 @@ void close_buffer(void)
 	free(orphan->lock_filename);
 	/* Free the undo stack. */
 	discard_until(NULL);
+
+	while (orphan->fold_segments) {
+		remove_folded_segment(orphan->fold_segments);
+	}
 #endif
 	free(orphan->errormessage);
 
@@ -648,6 +653,9 @@ char *encode_data(char *text, size_t length)
  * created and that the file does not need to be checked for writability. */
 void read_file(FILE *f, int fd, const char *filename, bool undoable)
 {
+#ifndef NANO_TINY
+	remove_if_in_any_folded_segment(openfile->current);
+#endif
 	ssize_t was_lineno = openfile->current->lineno;
 		/* The line number where we start the insertion. */
 	size_t was_leftedge = 0;
diff --git a/src/global.c b/src/global.c
index a3c90fe7..c54fdab1 100644
--- a/src/global.c
+++ b/src/global.c
@@ -642,6 +642,7 @@ void shortcut_init(void)
 	const char *findprev_gist = N_("Search next occurrence backward");
 	const char *findnext_gist = N_("Search next occurrence forward");
 #ifndef NANO_TINY
+	const char *fold_gist = N_("Fold/unfold the currently selected lines");
 	const char *recordmacro_gist = N_("Start/stop recording a macro");
 	const char *runmacro_gist = N_("Run the last recorded macro");
 	const char *anchor_gist = N_("Place or remove an anchor at the current line");
@@ -1019,6 +1020,9 @@ void shortcut_init(void)
 	add_to_funcs(run_macro, MMAIN,
 		N_("Run Macro"), WITHORSANS(runmacro_gist), BLANKAFTER, VIEW);
 
+	add_to_funcs(do_fold_segment, MMAIN, N_("Fold"),
+		WITHORSANS(fold_gist), BLANKAFTER, VIEW);
+
 	add_to_funcs(put_or_lift_anchor, MMAIN,
 		N_("Anchor"), WITHORSANS(anchor_gist), TOGETHER, VIEW);
 	add_to_funcs(to_prev_anchor, MMAIN,
@@ -1216,6 +1220,9 @@ void shortcut_init(void)
 	add_to_sclist(MEXECUTE, "^Y", 0, do_linter, 0);
 	add_to_sclist(MMAIN, "M-F", 0, do_formatter, 0);
 	add_to_sclist(MEXECUTE, "^O", 0, do_formatter, 0);
+#endif
+#ifndef NANO_TINY
+	add_to_sclist(MMAIN, "^E", 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);
diff --git a/src/move.c b/src/move.c
index 3db49c92..174673e3 100644
--- a/src/move.c
+++ b/src/move.c
@@ -111,6 +111,14 @@ 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)
@@ -132,7 +140,7 @@ void do_page_up(void)
 
 	/* Move up the required number of lines or chunks.  If we can't, we're
 	 * at the top of the file, so put the cursor there and get out. */
-	if (go_back_chunks(mustmove, &openfile->current, &leftedge) > 0) {
+	if (go_back_chunks(mustmove, &openfile->current, &leftedge, FALSE) > 0) {
 		to_first_line();
 		return;
 	}
@@ -164,7 +172,7 @@ void do_page_down(void)
 
 	/* Move down the required number of lines or chunks.  If we can't, we're
 	 * at the bottom of the file, so put the cursor there and get out. */
-	if (go_forward_chunks(mustmove, &openfile->current, &leftedge) > 0) {
+	if (go_forward_chunks(mustmove, &openfile->current, &leftedge, FALSE) > 0) {
 		to_last_line();
 		return;
 	}
@@ -238,16 +246,23 @@ void to_prev_block(void)
 	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 (get_prev_line(openfile->current) != NULL && (!seen_text || is_text)) {
+		openfile->current = get_prev_line(openfile->current);
+#ifndef NANO_TINY
+		if (is_in_any_folded_segment(openfile->current)) {
+			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 &&
+	if (seen_text && get_next_line(openfile->current) != NULL &&
 				white_string(openfile->current->data))
-		openfile->current = openfile->current->next;
+	{
+		openfile->current = get_next_line(openfile->current);
+	}
 
 	openfile->current_x = 0;
 	edit_redraw(was_current, CENTERING);
@@ -261,8 +276,15 @@ void to_next_block(void)
 	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 (get_next_line(openfile->current) != NULL &&
+	       (!seen_white || is_white))
+	{
+		openfile->current = get_next_line(openfile->current);
+#ifndef NANO_TINY
+		if (is_in_any_folded_segment(openfile->current)) {
+			continue;
+		}
+#endif
 		is_white = white_string(openfile->current->data);
 		seen_white = seen_white || is_white;
 	}
@@ -274,20 +296,47 @@ void to_next_block(void)
 #endif
 }
 
-/* Move to the previous word. */
-void do_prev_word(void)
+/* Move to the previous word.
+ * when allow_folded as 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;
 
+#ifndef NANO_TINY
+	folded_segment *seg;
+#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_x == 0
+#ifndef NANO_TINY
+		    || (!allow_folded && (seg = get_folded_segment(openfile->current)))
+#endif
+		) {
 			if (openfile->current->prev == NULL)
 				break;
-			openfile->current = openfile->current->prev;
-			openfile->current_x = strlen(openfile->current->data);
+#ifndef NANO_TINY
+			if (allow_folded) {
+#endif
+				openfile->current = openfile->current->prev;
+				openfile->current_x = strlen(openfile->current->data);
+#ifndef NANO_TINY
+			} else {
+				linestruct *prev;
+				if ((prev = get_prev_line(openfile->current)) == NULL) {
+					break;
+				}
+				if ((seg = get_folded_segment(prev))) {
+					prev = seg->start_line;
+				} else {
+					openfile->current_x = strlen(prev->data);
+				}
+				openfile->current = prev;
+			}
+#endif
 		}
 
 		/* Step back one character. */
@@ -318,8 +367,10 @@ 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 +
@@ -327,16 +378,38 @@ bool do_next_word(bool after_ends)
 	bool seen_space = !started_on_word;
 #ifndef NANO_TINY
 	bool seen_word = started_on_word;
+	folded_segment *seg;
 #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. */
-		if (openfile->current->data[openfile->current_x] == '\0') {
+		if (openfile->current->data[openfile->current_x] == '\0'
+#ifndef NANO_TINY
+		   || (!allow_folded && (seg = get_folded_segment(openfile->current)))
+#endif
+		) {
 			/* When at end of file, stop. */
-			if (openfile->current->next == NULL)
+			if (openfile->current->next == NULL) {
 				break;
-			openfile->current = openfile->current->next;
+			}
+#ifndef NANO_TINY
+			if (allow_folded) {
+#endif
+				openfile->current = openfile->current->next;
+#ifndef NANO_TINY
+				remove_if_in_any_folded_segment(openfile->current);
+			} else {
+				linestruct *next;
+				if ((next = get_next_line(openfile->current)) == NULL) {
+					break;
+				}
+				if ((seg = get_folded_segment(next))) {
+					next = seg->end_line->next;
+				}
+				openfile->current = next;
+			}
+#endif
 			openfile->current_x = 0;
 			seen_space = TRUE;
 		} else {
@@ -384,7 +457,7 @@ void to_prev_word(void)
 {
 	linestruct *was_current = openfile->current;
 
-	do_prev_word();
+	do_prev_word(FALSE);
 
 	edit_redraw(was_current, FLOWING);
 }
@@ -395,7 +468,7 @@ void to_next_word(void)
 {
 	linestruct *was_current = openfile->current;
 
-	do_next_word(ISSET(AFTER_ENDS));
+	do_next_word(ISSET(AFTER_ENDS), FALSE);
 
 	edit_redraw(was_current, FLOWING);
 }
@@ -412,6 +485,11 @@ void do_home(void)
 	bool moved = FALSE;
 	size_t leftedge = 0, leftedge_x = 0;
 
+	/* Noop when inside a folded segment */
+	if (is_in_any_folded_segment(openfile->current)) {
+		return;
+	}
+
 	if (ISSET(SOFTWRAP)) {
 		leftedge = leftedge_for(was_column, openfile->current);
 		leftedge_x = proper_x(openfile->current, &leftedge, FALSE, leftedge,
@@ -471,6 +549,11 @@ void do_end(void)
 	bool moved_off_chunk = TRUE;
 
 #ifndef NANO_TINY
+	/* Noop when in a folded segment */
+	if (is_in_any_folded_segment(openfile->current)) {
+		return;
+	}
+
 	if (ISSET(SOFTWRAP)) {
 		bool last_chunk = FALSE;
 		size_t leftedge = leftedge_for(was_column, openfile->current);
@@ -520,7 +603,7 @@ void do_up(void)
 	get_edge_and_target(&leftedge, &target_column);
 
 	/* If we can't move up one line or chunk, we're at top of file. */
-	if (go_back_chunks(1, &openfile->current, &leftedge) > 0)
+	if (go_back_chunks(1, &openfile->current, &leftedge, TRUE) > 0)
 		return;
 
 	set_proper_index_and_pww(&leftedge, target_column, FALSE);
@@ -543,7 +626,7 @@ void do_down(void)
 	get_edge_and_target(&leftedge, &target_column);
 
 	/* If we can't move down one line or chunk, we're at bottom of file. */
-	if (go_forward_chunks(1, &openfile->current, &leftedge) > 0)
+	if (go_forward_chunks(1, &openfile->current, &leftedge, TRUE) > 0)
 		return;
 
 	set_proper_index_and_pww(&leftedge, target_column, TRUE);
@@ -612,7 +695,16 @@ void do_left(void)
 #endif
 	} else if (openfile->current != openfile->filetop) {
 		openfile->current = openfile->current->prev;
-		openfile->current_x = strlen(openfile->current->data);
+#ifndef NANO_TINY
+		folded_segment *seg;
+		if ((seg = get_folded_segment(openfile->current))) {
+			openfile->current = seg->start_line;
+			openfile->current_x = 0;
+		} else
+#endif
+		{
+			openfile->current_x = strlen(openfile->current->data);
+		}
 	}
 
 	edit_redraw(was_current, FLOWING);
@@ -622,8 +714,15 @@ void do_left(void)
 void do_right(void)
 {
 	linestruct *was_current = openfile->current;
+#ifndef NANO_TINY
+	folded_segment *seg = get_folded_segment(openfile->current);
+#endif
 
-	if (openfile->current->data[openfile->current_x] != '\0') {
+	if (openfile->current->data[openfile->current_x] != '\0'
+#ifndef NANO_TINY
+	   && seg == NULL
+#endif
+	) {
 		openfile->current_x = step_right(openfile->current->data,
 												openfile->current_x);
 #ifdef ENABLE_UTF8
@@ -634,6 +733,11 @@ void do_right(void)
 #endif
 	} else if (openfile->current != openfile->filebot) {
 		openfile->current = openfile->current->next;
+#ifndef NANO_TINY
+		if (seg != NULL) {
+			openfile->current = seg->end_line->next;
+		}
+#endif
 		openfile->current_x = 0;
 	}
 
diff --git a/src/nano.c b/src/nano.c
index 93f32eed..4137ae7b 100644
--- a/src/nano.c
+++ b/src/nano.c
@@ -77,6 +77,7 @@ linestruct *make_new_node(linestruct *prevnode)
 	newnode->lineno = (prevnode) ? prevnode->lineno + 1 : 1;
 #ifndef NANO_TINY
 	newnode->has_anchor = FALSE;
+	newnode->fold = NULL;
 #endif
 
 	return newnode;
@@ -155,6 +156,7 @@ linestruct *copy_node(const linestruct *src)
 	dst->lineno = src->lineno;
 #ifndef NANO_TINY
 	dst->has_anchor = src->has_anchor;
+	dst->fold = src->fold;
 #endif
 
 	return dst;
@@ -1325,9 +1327,9 @@ int do_mouse(void)
 
 		/* Move current up or down to the row that was clicked on. */
 		if (row_count < 0)
-			go_back_chunks(-row_count, &openfile->current, &leftedge);
+			go_back_chunks(-row_count, &openfile->current, &leftedge, FALSE);
 		else
-			go_forward_chunks(row_count, &openfile->current, &leftedge);
+			go_forward_chunks(row_count, &openfile->current, &leftedge, FALSE);
 
 		openfile->current_x = actual_x(openfile->current->data,
 								actual_last_column(leftedge, click_col));
@@ -1422,6 +1424,11 @@ void inject(char *burst, size_t count)
 #ifndef NANO_TINY
 	size_t original_row = 0;
 	size_t old_amount = 0;
+	folded_segment *seg;
+	if ((seg = get_folded_segment(thisline))) {
+		remove_folded_segment(seg);
+		refresh_needed = TRUE;
+	}
 
 	if (ISSET(SOFTWRAP)) {
 		if (openfile->current_y == editwinrows - 1)
diff --git a/src/prototypes.h b/src/prototypes.h
index 0708ded0..59da6714 100644
--- a/src/prototypes.h
+++ b/src/prototypes.h
@@ -362,8 +362,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);
@@ -377,6 +377,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);
@@ -597,13 +598,16 @@ 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);
 #endif
 bool line_needs_update(const size_t old_column, const size_t new_column);
-int go_back_chunks(int nrows, linestruct **line, size_t *leftedge);
-int go_forward_chunks(int nrows, linestruct **line, size_t *leftedge);
+int go_back_chunks(int nrows, linestruct **line,
+                   size_t *leftedge, bool allow_folds);
+int go_forward_chunks(int nrows, linestruct **line,
+                      size_t *leftedge, bool allow_folds);
 bool less_than_a_screenful(size_t was_lineno, size_t was_leftedge);
 void edit_scroll(bool direction);
 #ifndef NANO_TINY
@@ -661,3 +665,21 @@ void flip_newbuffer(void);
 #endif
 void discard_buffer(void);
 void do_cancel(void);
+
+#ifndef NANO_TINY
+folded_segment *add_folded_segment(folded_segment *prev,
+	                                 linestruct *start, linestruct *end);
+void remove_folded_segment(folded_segment *seg);
+void remove_if_in_folded_segment(folded_segment *seg, const linestruct *line);
+void remove_if_in_any_folded_segment(const linestruct* line);
+bool is_in_folded_segment(const folded_segment *seg, const linestruct *top);
+bool is_in_any_folded_segment(const linestruct *top);
+folded_segment* get_folded_segment(const linestruct *line);
+bool range_contains_folded_segment(const linestruct *top, const linestruct *bot);
+int fold_segment_length(const folded_segment *seg);
+bool do_unfold_segments(const linestruct *top, const linestruct *bot);
+void do_fold_segment(void);
+#endif
+int get_row_from_edittop(const linestruct *l);
+linestruct *get_next_line(linestruct *line);
+linestruct *get_prev_line(linestruct *line);
diff --git a/src/rcfile.c b/src/rcfile.c
index 88a71c78..5696185e 100644
--- a/src/rcfile.c
+++ b/src/rcfile.c
@@ -134,6 +134,7 @@ static const rcoption rcopts[] = {
 	{"errorcolor", 0},
 	{"keycolor", 0},
 	{"functioncolor", 0},
+	{"foldedcolor", 0},
 #endif
 	{NULL, 0}
 };
@@ -1591,6 +1592,8 @@ void parse_rcfile(FILE *rcstream, bool just_syntax, bool intros_only)
 			color_combo[KEY_COMBO] = parse_interface_color(argument);
 		else if (strcmp(option, "functioncolor") == 0)
 			color_combo[FUNCTION_TAG] = parse_interface_color(argument);
+		else if (strcmp(option, "foldedcolor") == 0)
+			color_combo[FOLDED_LINE] = parse_interface_color(argument);
 		else
 #endif
 #ifdef ENABLE_OPERATINGDIR
diff --git a/src/search.c b/src/search.c
index c5898141..028c8286 100644
--- a/src/search.c
+++ b/src/search.c
@@ -223,6 +223,9 @@ int findnextstr(const char *needle, bool whole_word_only, int modus,
 			}
 #endif
 			/* The match is valid. */
+#ifndef NANO_TINY
+			remove_if_in_any_folded_segment(line);
+#endif
 			break;
 		}
 
@@ -751,6 +754,9 @@ void goto_line_posx(ssize_t line, size_t pos_x)
 
 	openfile->current_x = pos_x;
 	openfile->placewewant = xplustabs();
+#ifndef NANO_TINY
+	remove_if_in_any_folded_segment(openfile->current);
+#endif
 
 	refresh_needed = TRUE;
 }
@@ -804,8 +810,12 @@ void goto_line_and_column(ssize_t line, ssize_t column, bool retain_answer,
 		line = 1;
 
 #ifdef ENABLE_COLOR
+#ifndef NANO_TINY
+	if (line > openfile->current->lineno)
+#else
 	if (line > openfile->edittop->lineno + editwinrows ||
-				(ISSET(SOFTWRAP) && line > openfile->current->lineno))
+	      (ISSET(SOFTWRAP) && line > openfile->current->lineno))
+#endif
 		recook |= perturbed;
 #endif
 
@@ -814,6 +824,14 @@ void goto_line_and_column(ssize_t line, ssize_t column, bool retain_answer,
 				openfile->current != openfile->filebot; line--)
 		openfile->current = openfile->current->next;
 
+#ifndef NANO_TINY
+	folded_segment *seg;
+	if ((seg = get_folded_segment(openfile->current))) {
+		remove_folded_segment(seg);
+		refresh_needed = TRUE;
+	}
+#endif
+
 	/* Take a negative column number to mean: from the end of the line. */
 	if (column < 0)
 		column = breadth(openfile->current->data) + column + 2;
@@ -843,7 +861,7 @@ void goto_line_and_column(ssize_t line, ssize_t column, bool retain_answer,
 			size_t leftedge = leftedge_for(xplustabs(), openfile->current);
 
 			rows_from_tail = (editwinrows / 2) - go_forward_chunks(
-								editwinrows / 2, &currentline, &leftedge);
+								editwinrows / 2, &currentline, &leftedge, FALSE);
 		} else
 #endif
 			rows_from_tail = openfile->filebot->lineno -
@@ -978,6 +996,9 @@ void do_find_bracket(void)
 
 		/* When balance reached zero, we've found the complementary bracket. */
 		if (balance == 0) {
+#ifndef NANY_TINY
+			remove_if_in_any_folded_segment(openfile->current);
+#endif
 			edit_redraw(was_current, FLOWING);
 			return;
 		}
@@ -1011,6 +1032,9 @@ void go_to_and_confirm(linestruct *line)
 	if (line != openfile->current) {
 		openfile->current = line;
 		openfile->current_x = 0;
+#ifndef NANO_TINY
+		remove_if_in_any_folded_segment(openfile->current);
+#endif
 #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 4103f7ed..9b4ecfbf 100644
--- a/src/text.c
+++ b/src/text.c
@@ -44,6 +44,204 @@ static completionstruct *list_of_completions;
 #endif
 
 #ifndef NANO_TINY
+
+/* Add a new folded segment after old_seg, holding the range [start,end] */
+folded_segment *add_folded_segment(folded_segment *old_seg,
+	                                 linestruct *start, linestruct *end)
+{
+	folded_segment *seg = nmalloc(sizeof(folded_segment));
+	seg->next = old_seg;
+	seg->prev = NULL;
+	if (old_seg) {
+		seg->prev = old_seg->prev;
+		old_seg->prev = seg;
+	}
+
+	seg->start_line = start;
+	seg->end_line = end;
+
+	for (linestruct* line = start;line != end->next;line = line->next) {
+		line->fold = seg;
+	}
+	return seg;
+}
+
+/* Remove seg from the current file's fold_segments list */
+void remove_folded_segment(folded_segment *seg)
+{
+	if (seg == openfile->fold_segments) {
+		openfile->fold_segments = seg->next;
+	}
+	folded_segment *nseg = seg->next;
+	folded_segment *pseg = seg->prev;
+
+	for (linestruct *line = seg->start_line;
+	    line != seg->end_line->next;
+	    line = line->next)
+	{
+		line->fold = NULL;
+	}
+
+	free(seg);
+	if (pseg) {
+		pseg->next = nseg;
+	}
+	if (nseg) {
+		nseg->prev = pseg;
+	}
+}
+
+/* If line is a part of the folded segment seg, remove the folded segment */
+void remove_if_in_folded_segment(folded_segment *seg, const linestruct *line)
+{
+	if (seg && line->fold == seg) {
+		remove_folded_segment(seg);
+		refresh_needed = TRUE;
+	}
+}
+
+/* If line is a part of any of the current file's folded segments,
+ * remove the found folded segments from the fold_segments list */
+void remove_if_in_any_folded_segment(const linestruct *line)
+{
+	if (line->fold) {
+		remove_folded_segment(line->fold);
+		refresh_needed = TRUE;
+	}
+}
+
+/* Check if line is in any folded segments */
+bool is_in_any_folded_segment(const linestruct *line)
+{
+	return line->fold != NULL;
+}
+
+/* Check if line is inside the folded_segment seg */
+bool is_in_folded_segment(const folded_segment *seg, const linestruct *line)
+{
+	return line->fold == seg;
+}
+
+/* Return the folded_segment that line is a part of,
+ * or NULL if no such segment */
+folded_segment *get_folded_segment(const linestruct *line)
+{
+	return line->fold;
+}
+
+/* Check if any line in the range [top, bottom] is a part of a
+ * folded segment */
+bool range_contains_folded_segment(const linestruct *top,
+                                   const linestruct *bot)
+{
+	for (const linestruct *line = top;line != bot->next;line = line->next) {
+		if (line->fold) {
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+/* Get the span length of a folded_segment */
+int fold_segment_length(const folded_segment *seg)
+{
+	/* Add one because these lines are inclusive */
+	return seg->end_line->lineno - seg->start_line->lineno + 1;
+}
+#endif
+
+/* Return the current offset of target from openfile->edittop */
+int get_row_from_edittop(const linestruct *target)
+{
+#ifndef NANO_TINY
+	const linestruct *line = openfile->edittop;
+	int row = 0;
+	while (line != NULL && line != target && row < editwinrows) {
+		if (line->fold) {
+			line = line->fold->end_line->next;
+		} else {
+			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_line(linestruct *line)
+{
+	if (!line) {
+		return NULL;
+	}
+#ifndef NANO_TINY
+	if (line->fold) {
+		return line->fold->end_line->next;
+	}
+#endif
+	return line->next;
+}
+/* Return the previous line, taking fold segments into account */
+linestruct *get_prev_line(linestruct *line)
+{
+	if (!line) {
+		return NULL;
+	}
+#ifndef NANO_TINY
+	if (line->fold) {
+		return line->fold->start_line->prev;
+	}
+	if (line->prev && line->prev->fold) {
+		return line->prev->fold->start_line;
+	}
+#endif
+	return line->prev;
+}
+
+#ifndef NANO_TINY
+/* Remove any folded_segments within the range [top, bottom].
+ * Returns true if any segments are removed. */
+bool do_unfold_segments(const linestruct *top, const linestruct *bot)
+{
+	const linestruct *line;
+	bool performed = FALSE;
+	for(line = top;line != bot->next;line = line->next){
+		if (line->fold) {
+			remove_folded_segment(line->fold);
+			refresh_needed = performed = TRUE;
+		}
+	}
+	move_cursor_to_proper_column();
+
+	return performed;
+}
+
+/* Add a new folded_segment to openfile->fold_segments 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 (do_unfold_segments(top, bot)) {
+		return;
+	}
+	if (top == bot) {
+		return;
+	}
+
+	openfile->fold_segments =
+		add_folded_segment(openfile->fold_segments, top->next, bot->prev);
+
+	if (openfile->current->lineno > top->lineno &&
+	    openfile->current->lineno < bot->lineno)
+	{
+		openfile->current = bot->next;
+	}
+
+	refresh_needed = TRUE;
+}
+
 /* Toggle the mark. */
 void do_mark(void)
 {
@@ -96,6 +294,10 @@ void indent_a_line(linestruct *line, char *indentation)
 	if (indent_len == 0)
 		return;
 
+#ifndef NANO_TINY
+	remove_if_in_any_folded_segment(line);
+#endif
+
 	/* 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 +428,10 @@ void unindent_a_line(linestruct *line, size_t indent_len)
 	if (indent_len == 0)
 		return;
 
+#ifndef NANO_TINY
+	remove_if_in_any_folded_segment(line);
+#endif
+
 	/* Remove the first tab's worth of whitespace from this line. */
 	memmove(line->data, line->data + indent_len, length - indent_len + 1);
 
@@ -415,9 +621,13 @@ void do_comment(void)
 
 	/* Comment/uncomment each of the selected lines when possible, and
 	 * store undo data when a line changed. */
-	for (line = top; line != bot->next; line = line->next)
+	for (line = top; line != bot->next; line = line->next) {
+#ifndef NANO_TINY
+		remove_if_in_any_folded_segment(line);
+#endif
 		if (comment_line(action, line, comment_seq))
 			update_multiline_undo(line->lineno, "");
+	}
 
 	set_modified();
 	ensure_firstcolumn_is_aligned();
@@ -854,10 +1064,17 @@ void do_enter(void)
 {
 	linestruct *newnode = make_new_node(openfile->current);
 	size_t extra = 0;
+
 #ifndef NANO_TINY
+	folded_segment *seg;
+	if ((seg = get_folded_segment(openfile->current))) {
+		openfile->current = seg->end_line;
+		openfile->current_x = strlen(openfile->current->data);
+	}
 	linestruct *sampleline = openfile->current;
 	bool allblanks = FALSE;
 
+
 	if (ISSET(AUTOINDENT)) {
 #ifdef ENABLE_JUSTIFY
 		/* When doing automatic long-line wrapping and the next line is
@@ -1582,6 +1799,7 @@ void concat_paragraph(linestruct *line, size_t count)
 		strcat(line->data, next_line->data + next_lead_len);
 #ifndef NANO_TINY
 		line->has_anchor |= next_line->has_anchor;
+		remove_if_in_any_folded_segment(line);
 #endif
 		unlink_node(next_line);
 		count--;
@@ -2838,6 +3056,14 @@ void do_linter(void)
 
 		/* Place the cursor to indicate the affected line. */
 		place_the_cursor();
+#ifndef NANO_TINY
+		{
+			folded_segment *seg;
+			if ((seg = get_folded_segment(openfile->current))) {
+				remove_folded_segment(seg);
+			}
+		}
+#endif
 		wnoutrefresh(midwin);
 
 		kbinput = get_kbinput(footwin, VISIBLE);
@@ -2930,6 +3156,12 @@ void do_formatter(void)
 	else
 		treat(temp_name, openfile->syntax->formatter, FALSE);
 
+#ifndef NANO_TINY
+	while (openfile->fold_segments) {
+		remove_folded_segment(openfile->fold_segments);
+	}
+#endif
+
 	unlink(temp_name);
 	free(temp_name);
 }
@@ -2977,7 +3209,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, TRUE))
 			words++;
 	}
 
diff --git a/src/winio.c b/src/winio.c
index dee54851..aeade9c9 100644
--- a/src/winio.c
+++ b/src/winio.c
@@ -2443,6 +2443,8 @@ void place_the_cursor(void)
 	size_t column = xplustabs();
 
 #ifndef NANO_TINY
+	folded_segment* seg;
+
 	if (ISSET(SOFTWRAP)) {
 		linestruct *line = openfile->edittop;
 		size_t leftedge;
@@ -2451,20 +2453,33 @@ 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);
+			if ((seg = get_folded_segment(line))) {
+				line = seg->end_line;
+				++row;
+			} else {
+				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;
+		if (!is_in_any_folded_segment(openfile->current)) {
+			row += get_chunk_and_edge(column, openfile->current, &leftedge);
+			column -= leftedge;
+		}
 	} else
 #endif
 	{
-		row = openfile->current->lineno - openfile->edittop->lineno;
+		row = get_row_from_edittop(openfile->current);
 		column -= get_page_start(column);
 	}
 
+#ifndef NANO_TINY
+	if ((seg = get_folded_segment(openfile->current))) {
+		openfile->current_x = 0;
+		column = 0;
+	}
+#endif
 	if (row < editwinrows)
 		wmove(midwin, row, margin + column);
 #ifndef NANO_TINY
@@ -2478,13 +2493,8 @@ 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
+void draw_linenumber(int row, const linestruct* line, size_t from_col, bool folded){
 	/* 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) {
@@ -2492,9 +2502,13 @@ void draw_row(int row, const char *converted, linestruct *line, size_t from_col)
 #ifndef NANO_TINY
 		if (ISSET(SOFTWRAP) && from_col != 0)
 			mvwprintw(midwin, row, 0, "%*s", margin - 1, " ");
-		else
+		else if (folded) {
+			mvwprintw(midwin, row, 0, "%*c", margin - 1, '-');
+		} else
 #endif
+		{
 			mvwprintw(midwin, row, 0, "%*zd", margin - 1, line->lineno);
+		}
 		wattroff(midwin, interface_color_pair[LINE_NUMBER]);
 #ifndef NANO_TINY
 		if (line->has_anchor && (from_col == 0 || !ISSET(SOFTWRAP)))
@@ -2508,15 +2522,33 @@ 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, bool folded)
+{
+#ifdef ENABLE_LINENUMBERS
+	draw_linenumber(row, line, from_col, folded);
+#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);
 
 	/* When needed, clear the remainder of the row. */
+#ifndef NANO_TINY
+	if ((is_shorter || ISSET(SOFTWRAP)) && !folded)
+#else
 	if (is_shorter || ISSET(SOFTWRAP))
+#endif
+	{
 		wclrtoeol(midwin);
+	}
 
 #ifndef NANO_TINY
 	if (thebar)
@@ -2525,7 +2557,12 @@ void draw_row(int row, const char *converted, linestruct *line, size_t from_col)
 
 #ifdef ENABLE_COLOR
 	/* If there are color rules (and coloring is turned on), apply them. */
-	if (openfile->syntax && !ISSET(NO_SYNTAX)) {
+#ifndef NANO_TINY
+	if (!folded && openfile->syntax && !ISSET(NO_SYNTAX))
+#else
+	if (openfile->syntax && !ISSET(NO_SYNTAX))
+#endif
+	{
 		const colortype *varnish = openfile->syntax->color;
 
 		/* If there are multiline regexes, make sure this line has a cache. */
@@ -2763,12 +2800,46 @@ 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)
+#ifndef NANO_TINY
+/* Draw a folded segment at the given row. */
+void draw_folded_row(linestruct *line, int row, size_t from_col)
+{
+	const folded_segment *seg = line->fold;
+	const int seg_len = fold_segment_length(seg);
+	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 = malloc(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';
+
+	char *converted = display_string(string, from_col, editwincols, FALSE, FALSE);
+	free(string);
+
+	draw_linenumber(row, line, from_col, TRUE);
+
+#ifdef ENABLE_COLOR
+	wattron(midwin, interface_color_pair[FOLDED_LINE]);
+	mvwaddstr(midwin, row, margin, converted);
+	wattroff(midwin, interface_color_pair[FOLDED_LINE]);
+#endif
+
+	free(converted);
+}
+#endif
+
+
+/* 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. */
@@ -2782,13 +2853,19 @@ 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. */
-	converted = display_string(line->data, from_col, editwincols, TRUE, FALSE);
-	draw_row(row, converted, line, from_col);
-	free(converted);
+#ifndef NANO_TINY
+	if (is_in_any_folded_segment(line)) {
+		draw_folded_row(line, row, from_col);
+	} else
+#endif
+	{
+		converted = display_string(line->data, from_col, editwincols, TRUE, FALSE);
+		draw_row(row, converted, line, from_col, FALSE);
+		free(converted);
+	}
 
 	if (from_col > 0) {
 		wattron(midwin, hilite_attribute);
@@ -2805,6 +2882,16 @@ 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)
+{
+	const int row = get_row_from_edittop(line);
+	return update_line_at(line, index, row);
 }
 
 #ifndef NANO_TINY
@@ -2835,8 +2922,16 @@ 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;
+#ifndef NANO_TINY
+		folded_segment *seg;
+		if ((seg = get_folded_segment(someline))) {
+			++row;
+		} else
+#endif
+		{
+			row += 1 + extra_chunks_in(someline);
+		}
+		someline = get_next_line(someline);
 	}
 
 	/* If the first chunk is offscreen, don't even try to display it. */
@@ -2848,6 +2943,13 @@ int update_softwrapped_line(linestruct *line)
 
 	starting_row = row;
 
+#ifndef NANO_TINY
+	if (is_in_any_folded_segment(line)) {
+		draw_folded_row(line, row, from_col);
+		return 1;
+	}
+#endif
+
 	while (!end_of_line && row < editwinrows) {
 		to_col = get_softwrap_breakpoint(line->data, from_col, &end_of_line);
 
@@ -2856,7 +2958,7 @@ int update_softwrapped_line(linestruct *line)
 		/* Convert the chunk to its displayable form and draw it. */
 		converted = display_string(line->data, from_col, to_col - from_col,
 									TRUE, FALSE);
-		draw_row(row++, converted, line, from_col);
+		draw_row(row++, converted, line, from_col, FALSE);
 		free(converted);
 
 		from_col = to_col;
@@ -2886,35 +2988,56 @@ bool line_needs_update(const size_t old_column, const size_t new_column)
  * given column (leftedge).  After moving, leftedge will be set to the
  * starting column of the current chunk.  Return the number of chunks we
  * couldn't move up, which will be zero if we completely succeeded. */
-int go_back_chunks(int nrows, linestruct **line, size_t *leftedge)
+int go_back_chunks(int nrows, linestruct **line, size_t *leftedge, bool allow_folds)
 {
 	int i;
-
 #ifndef NANO_TINY
+	folded_segment *seg;
+	if ((seg = get_folded_segment(*line))) {
+		*line = seg->start_line;
+	}
+
 	if (ISSET(SOFTWRAP)) {
 		/* Recede through the requested number of chunks. */
 		for (i = nrows; i > 0; i--) {
 			size_t chunk = chunk_for(*leftedge, *line);
 
+			if ((seg = get_folded_segment(*line))) {
+				*line = seg->start_line->prev;
+				continue;
+			}
+
 			*leftedge = 0;
 
 			if (chunk >= i)
-				return go_forward_chunks(chunk - i, line, leftedge);
+				return go_forward_chunks(chunk - i, line, leftedge, allow_folds);
 
 			if (*line == openfile->filetop)
 				break;
 
 			i -= chunk;
-			*line = (*line)->prev;
+			*line = get_prev_line(*line);
 			*leftedge = HIGHEST_POSITIVE;
 		}
 
 		if (*leftedge == HIGHEST_POSITIVE)
 			*leftedge = leftedge_for(*leftedge, *line);
-	} else
+	} else {
+		for (i = nrows; i > 0 && (*line)->prev != NULL; i--) {
+			seg = get_folded_segment((*line)->prev);
+			if (seg) {
+				*line = allow_folds ? seg->start_line : seg->start_line->prev;
+			} else {
+				*line = (*line)->prev;
+			}
+		}
+	}
+#else /* NANO_TINY */
+	for (i = nrows; i > 0 && (*line)->prev != NULL; i--) {
+		*line = (*line)->prev;
+	}
+
 #endif
-		for (i = nrows; i > 0 && (*line)->prev != NULL; i--)
-			*line = (*line)->prev;
 
 	return i;
 }
@@ -2923,16 +3046,25 @@ int go_back_chunks(int nrows, linestruct **line, size_t *leftedge)
  * given column (leftedge).  After moving, leftedge will be set to the
  * starting column of the current chunk.  Return the number of chunks we
  * couldn't move down, which will be zero if we completely succeeded. */
-int go_forward_chunks(int nrows, linestruct **line, size_t *leftedge)
+int go_forward_chunks(int nrows, linestruct **line, size_t *leftedge, bool allow_folds)
 {
 	int i;
-
 #ifndef NANO_TINY
+	folded_segment *seg;
+	if ((seg = get_folded_segment(*line))) {
+		*line = seg->start_line;
+	}
+
 	if (ISSET(SOFTWRAP)) {
 		size_t current_leftedge = *leftedge;
 
 		/* Advance through the requested number of chunks. */
 		for (i = nrows; i > 0; i--) {
+			if ((seg = get_folded_segment(*line))) {
+				*line = seg->end_line->next;
+				continue;
+			}
+
 			bool end_of_line = FALSE;
 
 			current_leftedge = get_softwrap_breakpoint((*line)->data,
@@ -2951,10 +3083,23 @@ int go_forward_chunks(int nrows, linestruct **line, size_t *leftedge)
 		/* Only change leftedge when we actually could move. */
 		if (i < nrows)
 			*leftedge = current_leftedge;
-	} else
+	} else {
+		for (i = nrows; i > 0 && (*line)->next != NULL; i--) {
+			seg = get_folded_segment((*line)->next);
+			if (seg) {
+				*line = ((allow_folds && !is_in_folded_segment(seg, *line)) ?
+					seg->start_line :
+					seg->end_line->next);
+			} else {
+				*line = (*line)->next;
+			}
+		}
+	}
+#else
+	for (i = nrows; i > 0 && (*line)->next != NULL; i--) {
+		*line = (*line)->next;
+	}
 #endif
-		for (i = nrows; i > 0 && (*line)->next != NULL; i--)
-			*line = (*line)->next;
 
 	return i;
 }
@@ -2968,7 +3113,7 @@ bool less_than_a_screenful(size_t was_lineno, size_t was_leftedge)
 	if (ISSET(SOFTWRAP)) {
 		linestruct *line = openfile->current;
 		size_t leftedge = leftedge_for(xplustabs(), openfile->current);
-		int rows_left = go_back_chunks(editwinrows - 1, &line, &leftedge);
+		int rows_left = go_back_chunks(editwinrows - 1, &line, &leftedge, FALSE);
 
 		return (rows_left > 0 || line->lineno < was_lineno ||
 				(line->lineno == was_lineno && leftedge <= was_leftedge));
@@ -3021,9 +3166,9 @@ void edit_scroll(bool direction)
 
 	/* Move the top line of the edit window one row up or down. */
 	if (direction == BACKWARD)
-		go_back_chunks(1, &openfile->edittop, &openfile->firstcolumn);
+		go_back_chunks(1, &openfile->edittop, &openfile->firstcolumn, TRUE);
 	else
-		go_forward_chunks(1, &openfile->edittop, &openfile->firstcolumn);
+		go_forward_chunks(1, &openfile->edittop, &openfile->firstcolumn, TRUE);
 
 	/* Actually scroll the text of the edit window one row up or down. */
 	scrollok(midwin, TRUE);
@@ -3040,14 +3185,15 @@ void edit_scroll(bool direction)
 	leftedge = openfile->firstcolumn;
 
 	/* If we scrolled forward, the bottom row needs to be redrawn. */
-	if (direction == FORWARD)
-		go_forward_chunks(editwinrows - nrows, &line, &leftedge);
+	if (direction == FORWARD) {
+		go_forward_chunks(editwinrows - nrows, &line, &leftedge, TRUE);
+	}
 
 #ifndef NANO_TINY
 	if (thebar)
 		draw_scrollbar();
 
-	if (ISSET(SOFTWRAP)) {
+	if (ISSET(SOFTWRAP) && !is_in_any_folded_segment(line)) {
 		/* Compensate for the earlier chunks of a softwrapped line. */
 		nrows += chunk_for(leftedge, line);
 
@@ -3059,10 +3205,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_line(line);
 	}
 }
 
@@ -3239,14 +3386,14 @@ bool current_is_below_screen(void)
 
 		/* If current[current_x] is more than a screen's worth of lines after
 		 * edittop at column firstcolumn, it's below the screen. */
-		return (go_forward_chunks(editwinrows - 1 - SHIM, &line, &leftedge) == 0 &&
+		return (go_forward_chunks(editwinrows - 1 - SHIM, &line, &leftedge, FALSE) == 0 &&
 						(line->lineno < openfile->current->lineno ||
 						(line->lineno == openfile->current->lineno &&
 						leftedge < leftedge_for(xplustabs(), openfile->current))));
-	} else
+	}
 #endif
-		return (openfile->current->lineno >=
-						openfile->edittop->lineno + editwinrows - SHIM);
+	const int row = get_row_from_edittop(openfile->current);
+	return (row >= editwinrows - SHIM);
 }
 
 /* Return TRUE if current[current_x] is outside the viewport. */
@@ -3274,12 +3421,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_line(line);
+				--row;
+			} else {
+				line = get_next_line(line);
+				++row;
+			}
 		}
 	} else
 #endif
@@ -3334,10 +3486,19 @@ 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);
-		line = line->next;
+			row += update_line_at(line, 0, row);
+
+#ifndef NANO_TINY
+		folded_segment *seg = get_folded_segment(line);
+		if (seg) {
+			line = seg->end_line->next;
+		} else
+#endif
+		{
+			line = line->next;
+		}
 	}
 
 	while (row < editwinrows) {
@@ -3382,7 +3543,7 @@ void adjust_viewport(update_type manner)
 #endif
 
 	/* Move edittop back goal rows, starting at current[current_x]. */
-	go_back_chunks(goal, &openfile->edittop, &openfile->firstcolumn);
+	go_back_chunks(goal, &openfile->edittop, &openfile->firstcolumn, FALSE);
 }
 
 /* Tell curses to unconditionally redraw whatever was on the screen. */
-- 
2.35.1

