From 3064e6938202057801bfc1befd7517ac90b24611 Mon Sep 17 00:00:00 2001
From: David Lawrence Ramsey <pooka109@gmail.com>
Date: Mon, 10 Sep 2018 17:34:33 -0500
Subject: [PATCH] new feature: marked text gets justified into a single, new
 paragraph

This is modelled after Pico's behavior, with two major differences.
First, Pico doesn't add the quoting + indentation to the line after
a justified selection, but this is wrong, so nano does add it.
Second, Pico won't do anything if you do a full-justify while the
mark is on, but this is unnecessarily limiting, so nano will do
the same as for a plain justify with the mark on.
---
 src/text.c | 231 ++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 218 insertions(+), 13 deletions(-)

diff --git a/src/text.c b/src/text.c
index a35d487a..a3a69600 100644
--- a/src/text.c
+++ b/src/text.c
@@ -2178,7 +2178,8 @@ void justify_paragraph(filestruct **firstline, size_t quote_len,
 }
 
 /* Justify the current paragraph, and justify the entire file when
- * full_justify is TRUE. */
+ * full_justify is TRUE.  If the mark is on, justify only the marked
+ * text instead. */
 void do_justify(bool full_justify)
 {
 	size_t quote_len;
@@ -2190,15 +2191,14 @@ void do_justify(bool full_justify)
 	filestruct *last_par_line = NULL;
 		/* Will be the line after the last line of the justified
 		 * paragraph(s), if any. */
-	size_t x_for_last;
-		/* The x position until where to extract the last paragraph line. */
-
+	size_t top_x = 0;
+		/* The top x-coordinate of the paragraph we justify. */
+	size_t bot_x = 0;
+		/* The bottom x-coordinate of the paragraph we justify. */
 	filestruct *was_cutbuffer = cutbuffer;
 		/* The old cutbuffer, so we can justify in the current cutbuffer. */
 	filestruct *was_cutbottom = cutbottom;
 		/* The old cutbottom, so we can justify in the current cutbuffer. */
-	size_t jus_len;
-		/* The number of lines we're storing in the current cutbuffer. */
 	filestruct *jusline;
 		/* The line that we're justifying in the current cutbuffer. */
 
@@ -2206,8 +2206,33 @@ void do_justify(bool full_justify)
 	/* Stash the cursor position, to be stored in the undo item. */
 	ssize_t was_lineno = openfile->current->lineno;
 	size_t was_current_x = openfile->current_x;
+
+	/* We need these to restore the coordinates of the mark after justifying
+	 * marked text. */
+	ssize_t was_top_lineno = 0;
+	size_t was_top_x = 0;
+	bool right_side_up = FALSE;
+
+	/* We need these to hold the leading part (quoting + indentation) of the
+	 * line where the marked text begins, whether or not that part is covered
+	 * by the mark. */
+	char *the_lead = mallocstrcpy(NULL, "");
+	size_t lead_len = 0;
+
+	/* How many characters we need to add to the beginning of the marked text,
+	 * in order to get a full leading part there. */
+	size_t needed_extra = 0;
+
+	/* Whether the bottom of the mark is at the end of its line, in which case
+	 * we don't need to add a new line after it. */
+	bool bot_at_eol = FALSE;
 #endif
 
+#ifndef NANO_TINY
+	/* Do normal justification only when the mark is off. */
+	if (!openfile->mark)
+#endif
+	{
 	/* When justifying the entire buffer, start at the top.  Otherwise, when
 	 * in a paragraph but not at its beginning, move back to its first line. */
 	if (full_justify)
@@ -2223,14 +2248,40 @@ void do_justify(bool full_justify)
 		refresh_needed = TRUE;
 		return;
 	}
-
-	/* We cannot (yet) justify a marked region, so turn the mark off. */
-	openfile->mark = NULL;
+	}
 
 	/* Prepare to put the text we want to justify in the cutbuffer. */
 	cutbuffer = NULL;
 	cutbottom = NULL;
 
+#ifndef NANO_TINY
+	/* If the mark is on, do as Pico: treat all marked text as one paragraph. */
+	if (openfile->mark) {
+		mark_order((const filestruct **)&first_par_line, &top_x,
+						(const filestruct **)&last_par_line, &bot_x,
+						&right_side_up);
+
+		/* Save the coordinates of the mark. */
+		was_top_lineno = first_par_line->lineno;
+		was_top_x = top_x;
+
+		quote_len = quote_length(first_par_line->data);
+		par_len = last_par_line->lineno - first_par_line->lineno +
+											(bot_x > 0 ? 1 : 0);
+
+		/* Remember whether the end of the region was at the end of a line. */
+		bot_at_eol = last_par_line->data[bot_x] == '\0';
+
+		/* Save the leading part that is to be used for the new paragraph. */
+		lead_len = quote_len + indent_length(first_par_line->data + quote_len);
+		the_lead = mallocstrncpy(the_lead, first_par_line->data, lead_len + 1);
+		the_lead[lead_len] = '\0';
+	} else
+#endif
+	{
+		size_t jus_len;
+			/* The number of lines we're storing in the current cutbuffer. */
+
 	/* Start out at the first line of the paragraph. */
 	first_par_line = openfile->current;
 	last_par_line = openfile->current;
@@ -2248,9 +2299,10 @@ void do_justify(bool full_justify)
 	/* When possible, step one line further; otherwise, to line's end. */
 	if (last_par_line->next != NULL) {
 		last_par_line = last_par_line->next;
-		x_for_last = 0;
+		bot_x = 0;
 	} else
-		x_for_last = strlen(last_par_line->data);
+		bot_x = strlen(last_par_line->data);
+	}
 
 #ifndef NANO_TINY
 	add_undo(COUPLE_BEGIN);
@@ -2263,10 +2315,103 @@ void do_justify(bool full_justify)
 	add_undo(CUT);
 #endif
 	/* Do the equivalent of a marked cut. */
-	extract_buffer(&cutbuffer, &cutbottom, first_par_line, 0, last_par_line,
-																x_for_last);
+	extract_buffer(&cutbuffer, &cutbottom, first_par_line, top_x,
+											last_par_line, bot_x);
 #ifndef NANO_TINY
 	update_undo(CUT);
+
+	if (openfile->mark) {
+		size_t line_len = strlen(cutbuffer->data);
+		size_t indent_len, sample_indent_len;
+		filestruct *line, *sampleline;
+
+		needed_extra = (top_x < lead_len ? top_x : lead_len);
+
+		/* If the marked region starts in the middle of a line, and this line
+		 * has a leading part, prepend any missing portion of this leading part
+		 * to the first line of the extracted region. */
+		if (needed_extra > 0) {
+			cutbuffer->data = charealloc(cutbuffer->data,
+									line_len + needed_extra + 1);
+			charmove(cutbuffer->data + needed_extra, cutbuffer->data,
+									line_len + 1);
+			strncpy(cutbuffer->data, the_lead, needed_extra);
+			line_len += needed_extra;
+		}
+
+		indent_len = indent_length(cutbuffer->data + lead_len);
+
+		/* Remove extra whitespace after the leading part. */
+		if (indent_len > 0)
+			charmove(cutbuffer->data + lead_len,
+						cutbuffer->data + lead_len + indent_len,
+						line_len - indent_len + 1);
+
+		line = cutbuffer->next;
+
+		/* Adjust quoting, if we have more than one line.  This is for when we
+		 * have more than one quoting, which find_paragraph() can't handle:
+		 * we'll only have one after this. */
+		while (line != NULL) {
+			line_len = strlen(line->data);
+
+			/* Don't add quoting to a final empty line. */
+			if (line_len > 0 || line->next != NULL) {
+				/* The leading part of the marked text starts with the
+				 * quoting.  If the line doesn't begin with that, make sure it
+				 * does. */
+				if (line_len < quote_len ||
+						strncmp(line->data, the_lead, quote_len) != 0) {
+					line->data = charealloc(line->data,
+											line_len + quote_len + 1);
+					charmove(line->data + quote_len,
+								line->data, line_len + quote_len + 1);
+					strncpy(line->data, the_lead, quote_len);
+				}
+			}
+
+			line = line->next;
+		}
+
+		/* Adjust indentation, if we have more than two lines.  This is for
+		 * when we have more than two indentations, which find_paragraph()
+		 * can't handle: we'll only have two after this. */
+		if (cutbuffer != cutbottom) {
+			sampleline = cutbuffer->next;
+			sample_indent_len = indent_length(sampleline->data + quote_len);
+
+			line = sampleline->next;
+
+			while (line != NULL) {
+				line_len = strlen(line->data);
+
+				/* Don't add indentation to a final empty line. */
+				if (line_len > 0 || line->next != NULL) {
+					size_t line_indent_len = indent_length(line->data + quote_len);
+
+					/* Replace the indentation of this line with the indentation of
+					 * the second (or only) line of the marked text, accounting for
+					 * quoting. */
+					if (line_indent_len > 0) {
+						charmove(line->data + quote_len,
+									line->data + quote_len + line_indent_len,
+									line_len - quote_len - line_indent_len + 1);
+						line_len -= line_indent_len;
+					}
+
+					if (sample_indent_len > 0) {
+						line->data = charealloc(line->data,
+												line_len + sample_indent_len + 1);
+						charmove(line->data + quote_len + sample_indent_len,
+								line->data + quote_len, line_len - quote_len + 1);
+						strncpy(line->data, sampleline->data, sample_indent_len);
+					}
+				}
+
+				line = line->next;
+			}
+		}
+	}
 #endif
 
 	/* Prepare to justify the text we just put in the cutbuffer. */
@@ -2275,6 +2420,45 @@ void do_justify(bool full_justify)
 	/* Justify the current paragraph. */
 	justify_paragraph(&jusline, quote_len, par_len);
 
+	/* Set the bottom of the cutbuffer to the end of the justified text. */
+	cutbottom = jusline;
+
+#ifndef NANO_TINY
+	/* Ensure that both the top and bottom lines of justified marked text are
+	 * not part of other lines, as Pico does. */
+	if (openfile->mark) {
+		/* Add a new line before the top line if the top of the mark
+		 * started past the beginning of its line.  But if it started in the
+		 * middle of its leading part, don't: remove the (now-redundant)
+		 * portions of the leading part that we prepended to it earlier, and
+		 * it will be the new line. */
+		if (top_x > 0) {
+			if (needed_extra > 0 && top_x <= lead_len)
+				charmove(cutbuffer->data, cutbuffer->data + needed_extra,
+							strlen(cutbuffer->data) - needed_extra + 1);
+			else {
+				cutbuffer->prev = make_new_node(NULL);
+				cutbuffer->prev->data = mallocstrcpy(NULL, "");
+				cutbuffer->prev->next = cutbuffer;
+				cutbuffer = cutbuffer->prev;
+			}
+		}
+
+		/* Add a new line after the bottom line if the bottom of the mark
+		 * started past the beginning of its line.  But if it started at the
+		 * end of its line, don't: the line after it, if any will be the new
+		 * line. */
+		if (bot_x > 0 && !bot_at_eol) {
+			cutbottom->next = make_new_node(cutbottom);
+
+			/* Copy the leading part of the top justified line. */
+			cutbottom->next->data = mallocstrcpy(NULL, the_lead);
+
+			cutbottom = cutbottom->next;
+		}
+	} else
+#endif
+	{
 	/* When justifying the entire buffer, find and justify all paragraphs. */
 	if (full_justify) {
 		while (find_paragraph(&jusline, &quote_len, &par_len)) {
@@ -2284,6 +2468,7 @@ void do_justify(bool full_justify)
 				break;
 		}
 	}
+	}
 
 #ifndef NANO_TINY
 	add_undo(PASTE);
@@ -2295,6 +2480,20 @@ void do_justify(bool full_justify)
 
 	add_undo(COUPLE_END);
 	openfile->undotop->strdata = mallocstrcpy(NULL, _("justify"));
+
+	/* If we justified marked text, restore mark or cursor position. */
+	if (openfile->mark) {
+		if (right_side_up) {
+			openfile->mark = fsfromline(was_top_lineno);
+			openfile->mark_x = was_top_x;
+		} else {
+			openfile->current = fsfromline(was_top_lineno);
+			openfile->current_x = was_top_x;
+		}
+		update_undo(COUPLE_END);
+
+		free(the_lead);
+	}
 #endif
 
 	/* We're done justifying.  Restore the old cutbuffer. */
@@ -2302,6 +2501,11 @@ void do_justify(bool full_justify)
 	cutbottom = was_cutbottom;
 
 	/* Show what we justified on the status bar. */
+#ifndef NANO_TINY
+	if (openfile->mark)
+		statusbar(_("Justified selection"));
+	else
+#endif
 	if (full_justify)
 		statusbar(_("Justified file"));
 	else
@@ -2312,6 +2516,7 @@ void do_justify(bool full_justify)
 
 	set_modified();
 	refresh_needed = TRUE;
+	shift_held = TRUE;
 }
 
 /* Justify the current paragraph. */
-- 
2.19.1

