diff --git a/src/global.c b/src/global.c
index 6ae36816e452098cfe3a7aa7ad9e573547ae1ae4..3e1b9ac1056c41c9ab9da0851e26ea7650813dc2 100644
--- a/src/global.c
+++ b/src/global.c
@@ -931,7 +931,7 @@ void shortcut_init(void)
 #ifndef NANO_TINY
     add_to_funcs(do_indent_void, MMAIN,
 	N_("Indent Text"), IFSCHELP(nano_indent_msg), TOGETHER, NOVIEW);
-    add_to_funcs(do_unindent, MMAIN,
+    add_to_funcs(do_unindent_void, MMAIN,
 	N_("Unindent Text"), IFSCHELP(nano_unindent_msg), BLANKAFTER, NOVIEW);
 #endif
 #ifdef ENABLE_WORDCOMPLETION
@@ -1106,7 +1106,7 @@ void shortcut_init(void)
     add_to_sclist(MMAIN, "M-6", 0, do_copy_text, 0);
     add_to_sclist(MMAIN, "M-^", 0, do_copy_text, 0);
     add_to_sclist(MMAIN, "M-}", 0, do_indent_void, 0);
-    add_to_sclist(MMAIN, "M-{", 0, do_unindent, 0);
+    add_to_sclist(MMAIN, "M-{", 0, do_unindent_void, 0);
     add_to_sclist(MMAIN, "M-U", 0, do_undo, 0);
     add_to_sclist(MMAIN, "M-E", 0, do_redo, 0);
 #endif
@@ -1490,7 +1490,7 @@ sc *strtosc(const char *input)
     else if (!strcasecmp(input, "indent"))
 	s->scfunc = do_indent_void;
     else if (!strcasecmp(input, "unindent"))
-	s->scfunc = do_unindent;
+	s->scfunc = do_unindent_void;
     else if (!strcasecmp(input, "scrollup"))
 	s->scfunc = do_scroll_up;
     else if (!strcasecmp(input, "scrolldown"))
diff --git a/src/proto.h b/src/proto.h
index a444f7abc5e1e600e8fd49a92c906dab669250ec..e708f33f27ce07af3d461fabdfc196fdd41d60fa 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -525,7 +525,8 @@ void do_tab(void);
 #ifndef NANO_TINY
 void do_indent(ssize_t cols);
 void do_indent_void(void);
-void do_unindent(void);
+void do_unindent(ssize_t cols);
+void do_unindent_void(void);
 #endif
 bool white_string(const char *s);
 #ifdef ENABLE_COMMENT
diff --git a/src/text.c b/src/text.c
index 1be1a4e2c4f70abf055e5f35f8213dff6f009877..d267f62ef65ce6ef5b6618834dcbddf1d373c429 100644
--- a/src/text.c
+++ b/src/text.c
@@ -440,11 +440,167 @@ void do_indent_void(void)
     do_indent(tabsize);
 }
 
-/* Unindent the current line, or all lines covered by the mark if the
- * mark is on, tabsize columns. */
-void do_unindent(void)
+/* Indent or unindent the current line (or, if the mark is on, all lines
+ * covered by the mark) len columns, depending on whether len is
+ * positive or negative.  If the TABS_TO_SPACES flag is set, indent or
+ * unindent by len spaces.  Otherwise, indent or unindent by (len /
+ * tabsize) tabs and (len % tabsize) spaces. */
+void do_unindent(ssize_t cols)
+{
+    bool indent_changed = FALSE;
+	/* Whether any indenting or unindenting was done. */
+    bool unindent = FALSE;
+	/* Whether we're unindenting text. */
+    char *line_indent = NULL;
+	/* The text added to each line in order to indent it. */
+    size_t line_indent_len = 0;
+	/* The length of the text added to each line in order to indent
+	 * it. */
+    filestruct *top, *bot, *f;
+    size_t top_x, bot_x;
+
+    assert(openfile->current != NULL && openfile->current->data != NULL);
+
+    /* If cols is zero, get out. */
+    if (cols == 0)
+	return;
+
+    /* If cols is negative, make it positive and set unindent to TRUE. */
+    if (cols < 0) {
+	cols = -cols;
+	unindent = TRUE;
+    /* Otherwise, we're indenting, in which case the file will always be
+     * modified, so set indent_changed to TRUE. */
+    } else
+	indent_changed = TRUE;
+
+    /* If the mark is on, use all lines covered by the mark. */
+    if (openfile->mark_set)
+	mark_order((const filestruct **)&top, &top_x,
+			(const filestruct **)&bot, &bot_x, NULL);
+    /* Otherwise, use the current line. */
+    else {
+	top = openfile->current;
+	bot = top;
+    }
+
+    if (!unindent) {
+	/* Set up the text we'll be using as indentation. */
+	line_indent = charalloc(cols + 1);
+
+	if (ISSET(TABS_TO_SPACES)) {
+	    /* Set the indentation to cols spaces. */
+	    charset(line_indent, ' ', cols);
+	    line_indent_len = cols;
+	} else {
+	    /* Set the indentation to (cols / tabsize) tabs and (cols %
+	     * tabsize) spaces. */
+	    size_t num_tabs = cols / tabsize;
+	    size_t num_spaces = cols % tabsize;
+
+	    charset(line_indent, '\t', num_tabs);
+	    charset(line_indent + num_tabs, ' ', num_spaces);
+
+	    line_indent_len = num_tabs + num_spaces;
+	}
+
+	line_indent[line_indent_len] = '\0';
+    }
+
+    /* Go through each line of the text. */
+    for (f = top; f != bot->next; f = f->next) {
+	size_t line_len = strlen(f->data);
+	size_t indent_len = indent_length(f->data);
+
+	if (!unindent) {
+	    /* If we're indenting, add the characters in line_indent to
+	     * the beginning of the non-whitespace text of this line. */
+	    f->data = charealloc(f->data, line_len + line_indent_len + 1);
+	    charmove(&f->data[indent_len + line_indent_len],
+		&f->data[indent_len], line_len - indent_len + 1);
+	    strncpy(f->data + indent_len, line_indent, line_indent_len);
+	    openfile->totsize += line_indent_len;
+
+	    /* Keep track of the change in the current line. */
+	    if (openfile->mark_set && f == openfile->mark_begin &&
+			openfile->mark_begin_x >= indent_len)
+		openfile->mark_begin_x += line_indent_len;
+
+	    if (f == openfile->current && openfile->current_x >= indent_len) {
+		openfile->current_x += line_indent_len;
+		openfile->placewewant = xplustabs();
+	    }
+
+	    /* If the NO_NEWLINES flag isn't set, and this is the
+	     * magicline, add a new magicline. */
+	    if (!ISSET(NO_NEWLINES) && f == openfile->filebot)
+		new_magicline();
+	} else {
+	    size_t indent_col = strnlenpt(f->data, indent_len);
+		/* The length in columns of the indentation on this line. */
+
+	    if (cols <= indent_col) {
+		size_t indent_new = actual_x(f->data, indent_col - cols);
+			/* The length of the indentation remaining on
+			 * this line after we unindent. */
+		size_t indent_shift = indent_len - indent_new;
+			/* The change in the indentation on this line
+			 * after we unindent. */
+
+		/* If we're unindenting, and there's at least cols
+		 * columns' worth of indentation at the beginning of the
+		 * non-whitespace text of this line, remove it. */
+		charmove(&f->data[indent_new], &f->data[indent_len],
+			line_len - indent_shift - indent_new + 1);
+		null_at(&f->data, line_len - indent_shift + 1);
+		openfile->totsize -= indent_shift;
+
+		/* Keep track of the change in the current line. */
+		if (openfile->mark_set && f == openfile->mark_begin &&
+			openfile->mark_begin_x > indent_new) {
+		    if (openfile->mark_begin_x <= indent_len)
+			openfile->mark_begin_x = indent_new;
+		    else
+			openfile->mark_begin_x -= indent_shift;
+		}
+
+		if (f == openfile->current &&
+			openfile->current_x > indent_new) {
+		    if (openfile->current_x <= indent_len)
+			openfile->current_x = indent_new;
+		    else
+			openfile->current_x -= indent_shift;
+		    openfile->placewewant = xplustabs();
+		}
+
+		/* We've unindented, so the indentation changed. */
+		indent_changed = TRUE;
+	    }
+	}
+    }
+
+    if (!unindent)
+	/* Clean up. */
+	free(line_indent);
+
+    if (indent_changed) {
+	/* Throw away the undo stack, to prevent making mistakes when
+	 * the user tries to undo something in the reindented text. */
+	discard_until(NULL, openfile);
+
+	/* Mark the file as modified. */
+	set_modified();
+
+	/* Update the screen. */
+	refresh_needed = TRUE;
+    }
+}
+
+/* Unindent the current line, or all lines covered by the mark if the mark
+ * is on, tabsize columns. */
+void do_unindent_void(void)
 {
-    do_indent(-tabsize);
+    do_unindent(-tabsize);
 }
 #endif /* !NANO_TINY */