diff --git a/src/ring.c b/src/ring.c
index aa4e364..2cc95b6 100644
--- a/src/ring.c
+++ b/src/ring.c
@@ -35,9 +35,9 @@ _vte_ring_validate (VteRing * ring)
 {
 	g_assert(ring != NULL);
 	_vte_debug_print(VTE_DEBUG_RING,
-			" Delta = %lu, Length = %lu, Max = %lu, Writable = %lu.\n",
+			" Delta = %lu, Length = %lu, Max = %lu, Writable = %lu, LastPage = %lu.\n",
 			ring->start, ring->end - ring->start,
-			ring->max, ring->end - ring->writable);
+			ring->max, ring->end - ring->writable, ring->last_page);
 
 	g_assert (ring->start <= ring->writable);
 	g_assert (ring->writable <= ring->end);
@@ -100,8 +100,16 @@ _vte_ring_fini (VteRing *ring)
 typedef struct _VteRowRecord {
 	gsize text_start_offset;  /* offset where text of this row begins */
 	gsize attr_start_offset;  /* offset of the first character's attributes */
+	gsize /* FIXME */ soft_wrapped;          /* end of line is not '\n' */
+	gsize /* FIXME */ is_ascii;              /* for rewrapping speedup: guarantees that line contains 32..126 bytes only. Can be 0 even when ascii only. */
 } VteRowRecord;
 
+/* Represents a cell position (row is within the area covered by the ring, col is arbitrary) using text_stream's offset. */
+typedef struct _VteCellTextOffset {
+	gsize text_offset;  /* byte offset in text_stream (properly UTF-8 aligned) */
+	gsize extra_cells;  /* extra number of cells to walk, in case it's a CJK continuation cell or beyond EOL */
+} VteCellTextOffset;
+
 static gboolean
 _vte_ring_read_row_record (VteRing *ring, VteRowRecord *record, gulong position)
 {
@@ -126,6 +134,7 @@ _vte_ring_freeze_row (VteRing *ring, gulong position, const VteRowData *row)
 
 	record.text_start_offset = _vte_stream_head (ring->text_stream, 0);
 	record.attr_start_offset = _vte_stream_head (ring->attr_stream, 0);
+	record.is_ascii = 1;
 
 	g_string_set_size (buffer, 0);
 	for (i = 0, cell = row->cells; i < row->len; i++, cell++) {
@@ -171,11 +180,13 @@ _vte_ring_freeze_row (VteRing *ring, gulong position, const VteRowData *row)
 				ring->last_attr = attr;
 			}
 
+			if (cell->c < 32 || cell->c > 126) record.is_ascii = 0;
 			_vte_unistr_append_to_string (cell->c, buffer);
 		}
 	}
 	if (!row->attr.soft_wrapped)
 		g_string_append_c (buffer, '\n');
+	record.soft_wrapped = row->attr.soft_wrapped;
 
 	_vte_stream_append (ring->text_stream, buffer->str, buffer->len);
 	_vte_ring_append_row_record (ring, &record, position);
@@ -191,7 +202,7 @@ _vte_ring_thaw_row (VteRing *ring, gulong position, VteRowData *row, gboolean do
 	const char *p, *q, *end;
 	GString *buffer = ring->utf8_buffer;
 
-	_vte_debug_print (VTE_DEBUG_RING, "Thawing row %lu.\n", position);
+if (do_truncate)	_vte_debug_print (VTE_DEBUG_RING, "Thawing row %lu.\n", position);
 
 	_vte_row_data_clear (row);
 
@@ -332,7 +343,7 @@ _vte_ring_index (VteRing *ring, gulong position)
 		return _vte_ring_writable_index (ring, position);
 
 	if (ring->cached_row_num != position) {
-		_vte_debug_print(VTE_DEBUG_RING, "Caching row %lu.\n", position);
+//		_vte_debug_print(VTE_DEBUG_RING, "Caching row %lu.\n", position);
 		_vte_ring_thaw_row (ring, position, &ring->cached_row, FALSE);
 		ring->cached_row_num = position;
 	}
@@ -356,7 +367,7 @@ _vte_ring_freeze_one_row (VteRing *ring)
 	VteRowData *row;
 
 	/* We should never even try to freeze if writable is less than a screenful from the end */
-	g_assert(ring->writable + ring->visible_rows_hint < ring->end);
+	// g_assert(ring->writable + ring->visible_rows_hint < ring->end);  // FIXME with rewrapping!!!
 
 	if (G_UNLIKELY (ring->writable == ring->start))
 		_vte_ring_reset_streams (ring, ring->writable);
@@ -609,6 +620,385 @@ _vte_ring_set_visible_rows_hint (VteRing *ring, gulong rows)
 }
 
 
+/* Convert a (row,col) into a VteCellTextOffset.
+ * Requires the row to be frozen.
+ */
+static void
+_vte_frozen_row_column_to_text_offset (VteRing *ring,
+				       gulong position,
+				       gulong column,
+				       VteCellTextOffset *offset)
+{
+	VteRowRecord records[2];
+	VteCell *cell;
+	GString *buffer = ring->utf8_buffer;
+	VteRowData *row;
+	int i, off = 0;
+
+	g_assert(position < ring->writable);
+
+	if (!_vte_ring_read_row_record (ring, &records[0], position))
+		return;  // FIXME: error handling?
+	if ((position + 1) * sizeof (records[0]) < _vte_stream_head (ring->row_stream, 0)) {
+		if (!_vte_ring_read_row_record (ring, &records[1], position + 1))
+			return;  // FIXME: error handling?
+	} else
+		records[1].text_start_offset = _vte_stream_head (ring->text_stream, 0);
+
+	g_string_set_size (buffer, records[1].text_start_offset - records[0].text_start_offset);
+	if (!_vte_stream_read (ring->text_stream, records[0].text_start_offset, buffer->str, buffer->len))
+		return;  // FIXME: error handling?
+
+	if (G_LIKELY (buffer->len && buffer->str[buffer->len - 1] == '\n'))
+		buffer->len--;
+
+	row = _vte_ring_index(ring, position);
+
+	/* row and buffer now contain the same text, in different representation */
+	// FIXME: double loop is ugly, two simple loops (as in _vte_frozen_row_text_offset_to_column()) would be better
+	for (i = 0, cell = row->cells; i < row->len && i < column; i++, cell++) {
+		if (G_LIKELY (!cell->attr.fragment)) {
+			if (G_UNLIKELY (i + cell->attr.columns > column))
+				break;
+			int num_chars = _vte_unistr_strlen(cell->c);
+			do {
+				off++;
+				if ((buffer->str[off] & 0xC0) != 0x80) num_chars--;
+			} while (num_chars > 0 && off < buffer->len);
+		}
+	}
+	offset->text_offset = records[0].text_start_offset + off;
+	offset->extra_cells = column - i;
+}
+
+
+/* Given a row number and a VteCellTextOffset, compute the column within that row.
+   It's the caller's responsibility to ensure that VteCellTextOffset really falls into that row.
+   Requires the row to be frozen.
+ */
+static gulong
+_vte_frozen_row_text_offset_to_column (VteRing *ring,
+				       gulong position,
+				       const VteCellTextOffset *offset)
+{
+	VteRowRecord records[2];
+	VteCell *cell;
+	GString *buffer = ring->utf8_buffer;
+	VteRowData *row;
+	int num_chars = 0;
+	int i, off;
+
+	g_assert(position < ring->writable);
+
+	if (!_vte_ring_read_row_record (ring, &records[0], position))
+		return;  // FIXME: retval? error handling?
+	if ((position + 1) * sizeof (records[0]) < _vte_stream_head (ring->row_stream, 0)) {
+		if (!_vte_ring_read_row_record (ring, &records[1], position + 1))
+			return;  // FIXME: retval? error handling?
+	} else
+		records[1].text_start_offset = _vte_stream_head (ring->text_stream, 0);
+
+	g_assert(offset->text_offset >= records[0].text_start_offset && offset->text_offset < records[1].text_start_offset);
+
+	g_string_set_size (buffer, records[1].text_start_offset - records[0].text_start_offset);
+	if (!_vte_stream_read (ring->text_stream, records[0].text_start_offset, buffer->str, buffer->len))
+		return;  // FIXME: retval? error handling?
+
+	if (G_LIKELY (buffer->len && buffer->str[buffer->len - 1] == '\n'))
+		buffer->len--;
+
+	row = _vte_ring_index(ring, position);
+
+	/* row and buffer now contain the same text, in different representation */
+	// TODO: better variable names
+	off = offset->text_offset - records[0].text_start_offset;
+	for (i = 0; i < off && i < buffer->len; i++) {
+		if ((buffer->str[i] & 0xC0) != 0x80) num_chars++;
+	}
+	for (i = 0, cell = row->cells; i < row->len; i++, cell++) {
+		if (G_LIKELY (!cell->attr.fragment)) {
+			if (num_chars == 0) break;
+			int nc = _vte_unistr_strlen(cell->c);
+			if (nc > num_chars) break;
+			num_chars -= nc;
+		}
+	}
+	return i + offset->extra_cells;
+}
+
+
+/**
+ * _vte_ring_rewrap:
+ * @ring: a #VteRing
+ * @columns: new number of columns
+ * @markers: NULL-terminated array of #VteVisualPosition
+ *
+ * Reflow the @ring to match the new number of @columns.
+ * For all @markers, find the cell at that position and update them to
+ * reflect the cell's new position, or put (-1,-1) if not found.
+ */
+void
+_vte_ring_rewrap (VteRing *ring,
+		  glong columns,
+		  VteVisualPosition **markers)
+{
+	glong old_row_index;
+	int i;
+	int num_markers = 0;
+	VteCellTextOffset *marker_text_offsets;
+	VteVisualPosition *new_markers;
+	VteRowRecord old_record;
+	VteCellAttrChange attr_change;
+
+	if (_vte_ring_length(ring) == 0)
+		return;
+	_vte_debug_print(VTE_DEBUG_RING, "Ring before rewrapping:\n");
+	_vte_ring_validate(ring);
+
+	/* Freeze everything, because rewrapping is really complicated and we don't want to
+	   duplicate the code for frozen and thawed rows. */
+	while (ring->writable < ring->end)
+		_vte_ring_freeze_one_row(ring);
+
+	/* For markers given as (row,col) pairs find their offsets in the text stream.
+	   This code requires that the rows are already frozen. */
+	while (markers[num_markers] != NULL)
+		num_markers++;
+	marker_text_offsets = g_malloc(num_markers * sizeof (marker_text_offsets[0]));
+	new_markers = g_malloc(num_markers * sizeof (new_markers[0]));
+	for (i = 0; i < num_markers; i++) {
+		VteRowRecord record;
+		marker_text_offsets[i].text_offset = 0;
+		marker_text_offsets[i].extra_cells = 0;
+		new_markers[i].row = new_markers[i].col = -1;
+		if (_vte_ring_read_row_record (ring, &record, markers[i]->row)) {
+			/* Convert visual column into byte offset */
+			_vte_frozen_row_column_to_text_offset(ring, markers[i]->row, markers[i]->col, &marker_text_offsets[i]);
+		} else {
+			marker_text_offsets[i].text_offset = (gsize) -1;
+		}
+		_vte_debug_print(VTE_DEBUG_RESIZE,
+				"Marker #%d old coords:  row %ld  col %ld  ->  text_offset %ld  extra_cells %ld%s\n",
+				i, markers[i]->row, markers[i]->col, marker_text_offsets[i].text_offset, marker_text_offsets[i].extra_cells,
+				marker_text_offsets[i].text_offset == -1 ? "  (not found)" : "");
+	}
+
+	/* Prepare for rewrapping */
+	VteStream *new_row_stream = _vte_file_stream_new ();
+	if (!_vte_ring_read_row_record(ring, &old_record, ring->start))
+		goto err;
+	gsize paragraph_start_text_offset = old_record.text_start_offset;
+	gsize paragraph_end_text_offset;  /* points to beyond the end */
+	gsize paragraph_len;  /* excluding trailing '\n' */
+	gsize attr_offset;
+	gulong new_row_index = 0;
+
+	attr_offset = old_record.attr_start_offset;
+	if (!_vte_stream_read(ring->attr_stream, attr_offset, (char *) &attr_change, sizeof (attr_change))) {
+		attr_change.attr = ring->last_attr;
+		attr_change.text_end_offset = _vte_stream_head (ring->text_stream, 0);
+	}
+
+	old_row_index = ring->start + 1;
+	while (paragraph_start_text_offset < _vte_stream_head (ring->text_stream, 0)) {
+		/* Find the boundaries of the next paragraph */
+		gboolean prev_record_was_soft_wrapped;
+		gboolean paragraph_is_ascii = TRUE;
+		gsize paragraph_start_row = old_row_index - 1;
+		gsize paragraph_end_row;  /* points to beyond the end */
+		_vte_debug_print(VTE_DEBUG_RESIZE,
+				"  Old paragraph:  row %lld  (text_offset %lld)  up to (exclusive)  ",
+				paragraph_start_row, paragraph_start_text_offset);
+		while (old_row_index <= ring->end) {
+			prev_record_was_soft_wrapped = old_record.soft_wrapped;
+			paragraph_is_ascii = paragraph_is_ascii && old_record.is_ascii;
+			if (G_LIKELY (old_row_index < ring->end)) {
+				if (!_vte_ring_read_row_record(ring, &old_record, old_row_index))
+					goto err;
+				paragraph_end_text_offset = old_record.text_start_offset;
+			} else {
+				paragraph_end_text_offset = _vte_stream_head (ring->text_stream, 0);
+			}
+			old_row_index++;
+			if (!prev_record_was_soft_wrapped)
+				break;
+		}
+		paragraph_end_row = old_row_index - 1;
+		paragraph_len = paragraph_end_text_offset - paragraph_start_text_offset;
+		if (!prev_record_was_soft_wrapped)  /* The last paragraph can be soft wrapped! */
+			paragraph_len--;  /* Strip trailing '\n' */
+		_vte_debug_print(VTE_DEBUG_RESIZE,
+				"row %lld  (text_offset %lld)%s  len %lld  is_ascii %d\n",
+				paragraph_end_row, paragraph_end_text_offset,
+				prev_record_was_soft_wrapped ? "  soft_wrapped" : "",
+				paragraph_len, paragraph_is_ascii);
+
+		/* Wrap the paragraph */
+		gsize text_offset = paragraph_start_text_offset;
+		VteRowRecord new_record;
+		glong col = 0;
+
+		if (attr_change.text_end_offset <= text_offset) {
+			/* Attr change at paragraph boundary, advance to next attr. */
+                        attr_offset += sizeof (attr_change);
+                        if (!_vte_stream_read(ring->attr_stream, attr_offset, (char *) &attr_change, sizeof (attr_change))) {
+                                attr_change.attr = ring->last_attr;
+                                attr_change.text_end_offset = _vte_stream_head (ring->text_stream, 0);
+                        }
+		}
+		new_record.text_start_offset = text_offset;
+		new_record.attr_start_offset = attr_offset;
+		new_record.is_ascii = paragraph_is_ascii;
+
+		while (paragraph_len > 0) {
+			/* Wrap one continuous run of identical attributes within the paragraph. */
+
+			if (attr_change.text_end_offset <= text_offset) {
+				/* Attr change at line boundary, advance to next attr. */
+                                attr_offset += sizeof (attr_change);
+                                if (!_vte_stream_read(ring->attr_stream, attr_offset, (char *) &attr_change, sizeof (attr_change))) {
+                                        attr_change.attr = ring->last_attr;
+                                        attr_change.text_end_offset = _vte_stream_head (ring->text_stream, 0);
+                                }
+			}
+
+			gsize runlength = MIN(paragraph_len, attr_change.text_end_offset - text_offset);  /* number of bytes we process in one run */
+			if (paragraph_is_ascii) {
+				/* Shortcut for quickly wrapping ASCII (excluding TAB) text.
+				   If every character is single cell wide and takes 1 byte then we can avoid reading text_stream. */
+				if (col == columns) {
+					/* Wrap now, write the soft wrapped row's record */
+					new_record.soft_wrapped = 1;
+					_vte_stream_append(new_row_stream, &new_record, sizeof (new_record));
+					_vte_debug_print(VTE_DEBUG_RESIZE,
+							"    New row %d  text_offset %lld  attr_offset %lld  soft_wrapped\n",
+							new_row_index,
+							new_record.text_start_offset, new_record.attr_start_offset);
+					for (i = 0; i < num_markers; i++) {
+						if (G_UNLIKELY (marker_text_offsets[i].text_offset >= new_record.text_start_offset &&
+								marker_text_offsets[i].text_offset < text_offset)) {
+							new_markers[i].row = new_row_index;
+							_vte_debug_print(VTE_DEBUG_RESIZE,
+									"      Marker #%d will be here in row %lu\n", i, new_row_index);
+						}
+					}
+					new_row_index++;
+					new_record.text_start_offset = text_offset;
+					new_record.attr_start_offset = attr_offset;
+					col = 0;
+				}
+				int len = MIN(runlength, columns - col);
+				col += len;
+				text_offset += len;
+				paragraph_len -= len;
+			} else if (attr_change.attr.s.columns == 0) {
+				/* Combining characters all fit in the current row */
+				text_offset += runlength;
+				paragraph_len -= runlength;
+			} else {
+				char text[4096];  // FIXME this is so ugly
+				char *textp = &text[0];
+				runlength = MIN(runlength, 4096);  // ditto
+				if (!_vte_stream_read(ring->text_stream, text_offset, &text, MIN(runlength, 4096)))
+					goto err;
+				while (runlength) {
+					if (col >= columns - attr_change.attr.s.columns + 1) {
+						/* Wrap now, write the soft wrapped row's record */
+						new_record.soft_wrapped = 1;
+						_vte_stream_append(new_row_stream, &new_record, sizeof (new_record));
+						_vte_debug_print(VTE_DEBUG_RESIZE,
+								"    New row %d  text_offset %lld  attr_offset %lld  soft_wrapped\n",
+								new_row_index,
+								new_record.text_start_offset, new_record.attr_start_offset);
+						for (i = 0; i < num_markers; i++) {
+							if (G_UNLIKELY (marker_text_offsets[i].text_offset >= new_record.text_start_offset &&
+									marker_text_offsets[i].text_offset < text_offset)) {
+								new_markers[i].row = new_row_index;
+								_vte_debug_print(VTE_DEBUG_RESIZE,
+										"      Marker #%d will be here in row %lu\n", i, new_row_index);
+							}
+						}
+						new_row_index++;
+						new_record.text_start_offset = text_offset;
+						new_record.attr_start_offset = attr_offset;
+						col = 0;
+					}
+					col += attr_change.attr.s.columns;
+					/* Find beginning of next UTF-8 character */
+					textp++; text_offset++; paragraph_len--; runlength--;
+					while (runlength) {
+						if (textp == &text[4096]) {  // FIXME this is so ugly
+							if (!_vte_stream_read(ring->text_stream, text_offset, &text, MIN(runlength, 4096)))
+								goto err;
+							textp = &text[0];
+						}
+						if ((*textp & 0xC0) != 0x80) break;
+						textp++; text_offset++; paragraph_len--; runlength--;
+					}
+				}
+			}
+		}
+
+		/* Write the record of the paragraph's last row. */
+		/* Hard wrapped, except maybe at the end of the very last paragraph */
+		new_record.soft_wrapped = prev_record_was_soft_wrapped;
+		_vte_stream_append(new_row_stream, &new_record, sizeof (new_record));
+		_vte_debug_print(VTE_DEBUG_RESIZE,
+				"    New row %d  text_offset %lld  attr_offset %lld\n",
+				new_row_index,
+				new_record.text_start_offset, new_record.attr_start_offset);
+		for (i = 0; i < num_markers; i++) {
+			if (G_UNLIKELY (marker_text_offsets[i].text_offset >= new_record.text_start_offset &&
+					marker_text_offsets[i].text_offset < paragraph_end_text_offset)) {
+				new_markers[i].row = new_row_index;
+				_vte_debug_print(VTE_DEBUG_RESIZE,
+						"      Marker #%d will be here in row %lu\n", i, new_row_index);
+			}
+		}
+		new_row_index++;
+		paragraph_start_text_offset = paragraph_end_text_offset;
+	}
+
+	/* Update the ring. */
+	g_object_unref(ring->row_stream);
+	ring->row_stream = new_row_stream;
+	/* This can underflow but that's okay. We didn't turn page in the new row stream yet, to make sure that
+	   we still have enough scrollback if a new page is started soon after rewrapping. */
+	ring->last_page += new_row_index - ring->end;
+	ring->writable = ring->end = new_row_index;
+	ring->start = 0;
+	if (_vte_ring_length (ring) > ring->max)
+		ring->start = _vte_ring_length (ring) - ring->max;
+
+	/* Find the markers. This requires that the ring is already updated. */
+	for (i = 0; i < num_markers; i++) {
+		if (new_markers[i].row != -1) {
+			new_markers[i].col = _vte_frozen_row_text_offset_to_column(ring, new_markers[i].row, &marker_text_offsets[i]);
+		}
+		_vte_debug_print(VTE_DEBUG_RESIZE,
+				"Marker #%d new coords:  text_offset %ld  extra_cells %ld  ->  row %ld  col %ld%s\n",
+				i, marker_text_offsets[i].text_offset, marker_text_offsets[i].extra_cells, new_markers[i].row, new_markers[i].col,
+				new_markers[i].row == -1 ? "  (not found)" : "");
+		markers[i]->row = new_markers[i].row;
+		markers[i]->col = new_markers[i].col;
+	}
+	g_free(marker_text_offsets);
+	g_free(new_markers);
+
+	_vte_debug_print(VTE_DEBUG_RING, "Ring after rewrapping:\n");
+	_vte_ring_validate(ring);
+	return;
+
+err:
+	_vte_debug_print(VTE_DEBUG_RESIZE,
+			"Error while rewrapping\n");
+	g_object_unref(new_row_stream);
+	g_free(marker_text_offsets);
+	g_free(new_markers);
+
+}
+
+
 static gboolean
 _vte_ring_write_row (VteRing *ring,
 		     GOutputStream *stream,
diff --git a/src/ring.h b/src/ring.h
index d4cab96..ee47022 100644
--- a/src/ring.h
+++ b/src/ring.h
@@ -32,6 +32,10 @@
 G_BEGIN_DECLS
 
 
+typedef struct _VteVisualPosition {
+	long row, col;
+} VteVisualPosition;
+
 typedef struct _VteCellAttrChange {
 	gsize text_end_offset;  /* offset of first character no longer using this attr */
 	VteIntCellAttr attr;
@@ -83,6 +87,7 @@ VteRowData *_vte_ring_insert (VteRing *ring, gulong position);
 VteRowData *_vte_ring_append (VteRing *ring);
 void _vte_ring_remove (VteRing *ring, gulong position);
 void _vte_ring_set_visible_rows_hint (VteRing *ring, gulong rows);
+void _vte_ring_rewrap (VteRing *ring, glong columns, VteVisualPosition **markers);
 gboolean _vte_ring_write_contents (VteRing *ring,
 				   GOutputStream *stream,
 				   VteTerminalWriteFlags flags,
diff --git a/src/vte-private.h b/src/vte-private.h
index f53c230..b8f27f1 100644
--- a/src/vte-private.h
+++ b/src/vte-private.h
@@ -143,10 +143,6 @@ typedef struct _VteWordCharRange {
 	gunichar start, end;
 } VteWordCharRange;
 
-typedef struct _VteVisualPosition {
-	long row, col;
-} VteVisualPosition;
-
 /* Terminal private data. */
 struct _VteTerminalPrivate {
 	/* Emulation setup data. */
diff --git a/src/vte.c b/src/vte.c
index 57980a6..6a0669a 100644
--- a/src/vte.c
+++ b/src/vte.c
@@ -8013,22 +8013,62 @@ vte_terminal_refresh_size(VteTerminal *terminal)
 
 /* Resize the given screen (normal or alternate) of the terminal. */
 static void
-vte_terminal_screen_set_size(VteTerminal *terminal, VteScreen *screen, glong old_columns, glong old_rows)
+vte_terminal_screen_set_size(VteTerminal *terminal, VteScreen *screen, glong old_columns, glong old_rows, gboolean rewrap)
 {
 	VteRing *ring = screen->row_data;
+	VteVisualPosition cursor_saved_absolute, below_bottom_left;
+	VteVisualPosition *markers[4];
 	gboolean was_scrolled_to_top = (screen->scroll_delta == _vte_ring_delta(ring));
 	gboolean was_scrolled_to_bottom = (screen->scroll_delta == screen->insert_delta);
 	glong new_scroll_delta;
-	long cursor_saved_absolute_row = screen->insert_delta + screen->cursor_saved.row;
+
+	if (G_UNLIKELY (!_vte_ring_length(ring)))
+		return;
 
 	_vte_debug_print(VTE_DEBUG_RESIZE,
 			"Resizing %s screen\n"
 			"Old  ring_delta=%ld  ring_next=%ld\n"
-			"Old  insert_delta=%ld  scroll_delta=%ld\n",
+			"Old  insert_delta=%ld  scroll_delta=%ld\n"
+			"Old cursor_current (absolute)  row=%ld  (visual line %ld)  col=%ld\n"
+			"Old cursor_saved (relative to insert_delta)  row=%ld  col=%ld\n",
 			screen == &terminal->pvt->normal_screen ? "normal" : "alternate",
 			_vte_ring_delta(ring), _vte_ring_next(ring),
-			screen->insert_delta, screen->scroll_delta);
+			screen->insert_delta, screen->scroll_delta,
+			screen->cursor_current.row,
+			screen->cursor_current.row - screen->scroll_delta + 1,
+			screen->cursor_current.col,
+			screen->cursor_saved.row, screen->cursor_saved.col);
+
+	cursor_saved_absolute.row = screen->cursor_saved.row + screen->insert_delta;
+	cursor_saved_absolute.col = screen->cursor_saved.col;
+	below_bottom_left.row = screen->scroll_delta + old_rows;
+	below_bottom_left.col = 0;
+	markers[0] = &screen->cursor_current;
+	markers[1] = &cursor_saved_absolute;
+	markers[2] = &below_bottom_left;
+	markers[3] = NULL;
+
+	if (old_columns != terminal->column_count) {
+		if (rewrap)
+			_vte_ring_rewrap(ring, terminal->column_count, markers);
+
+#ifdef VTE_DEBUG
+// ???????	g_assert(screen->cursor_current.row != -1);
+//		g_assert(cursor_saved_absolute.row != -1);  // TODO: I'm a bit afraid of this one!
+#endif
 
+		if (screen->cursor_current.row == -1) {
+//			what now???  Can this happen at all?  If yes, why???
+			screen->cursor_current.row == _vte_ring_next(ring) - 1;
+			screen->cursor_current.col = 0;
+		}
+
+	}
+
+	// TODO: what to do if screen==normal_screen && normal_screen->scrolling_restricted is true?
+
+#if 0
+	// TODO: port this to the rewrapping code, figure out actually what we should do :)
 	if (old_rows > terminal->row_count &&
 	    screen->insert_delta + old_rows > screen->cursor_current.row + 1) {
 		/* Shrinking the window, cursor was not at the bottom.
@@ -8042,6 +8082,7 @@ vte_terminal_screen_set_size(VteTerminal *terminal, VteScreen *screen, glong old
 				drop_lines);
 		_vte_ring_shrink(ring, new_ring_next - _vte_ring_delta(ring));
 	}
+#endif
 	if (_vte_ring_length(ring) <= terminal->row_count) {
 		/* Everything fits without scrollbars. Align at top. */
 		screen->insert_delta = _vte_ring_delta(ring);
@@ -8062,6 +8103,18 @@ vte_terminal_screen_set_size(VteTerminal *terminal, VteScreen *screen, glong old
 			_vte_debug_print(VTE_DEBUG_RESIZE,
 					"Scroll to top\n");
 		} else {
+			/* Try to scroll so that the bottom row stays.
+			   More precisely, the character below the bottom left corner stays in that
+			   (invisible) row.
+			   So if the bottom of the screen was at a hard line break then that hard
+			   line break will stay there.
+			   TODO: What would be the best behavior if the bottom of the screen is a
+			   soft line break, i.e. only a partial line is visible at the bottom? */
+#ifdef VTE_DEBUG
+			/* If -1, we should've entered the "scroll to bottom" branch. */
+			g_assert(below_bottom_left.row != -1);
+#endif
+			new_scroll_delta = below_bottom_left.row - terminal->row_count;
 			/* Try to scroll so that the bottom visible row stays. */
 			new_scroll_delta = screen->scroll_delta + old_rows - terminal->row_count;
 			_vte_debug_print(VTE_DEBUG_RESIZE,
@@ -8069,15 +8122,26 @@ vte_terminal_screen_set_size(VteTerminal *terminal, VteScreen *screen, glong old
 		}
 	}
 
-	/* Saved cursor is relative to visible part. Adjust so that it stays relative to the ring.
-	   Don't clamp yet (so that it's tracked correctly through multiple resizes), it will be clamped when restored. */
-	screen->cursor_saved.row = cursor_saved_absolute_row - screen->insert_delta;
+	if (cursor_saved_absolute.row != -1) {
+		/* Don't clamp, they'll be clamped when restored. Until then remember off-screen values
+		   since they might become on-screen again on subsequent resizes. */
+		screen->cursor_saved.row = cursor_saved_absolute.row - screen->insert_delta;
+		screen->cursor_saved.col = cursor_saved_absolute.col;
+	} else {
+		screen->cursor_saved.row = screen->cursor_saved.col = 0;
+	}
 
 	_vte_debug_print(VTE_DEBUG_RESIZE,
 			"New  ring_delta=%ld  ring_next=%ld\n"
-			"New  insert_delta=%ld  scroll_delta=%ld\n\n",
+			"New  insert_delta=%ld  scroll_delta=%ld\n"
+			"New cursor_current (absolute)  row=%ld  (visual line %ld)  col=%ld\n"
+			"New cursor_saved (relative to insert_delta)  row=%ld  col=%ld\n\n",
 			_vte_ring_delta(ring), _vte_ring_next(ring),
-			screen->insert_delta, new_scroll_delta);
+			screen->insert_delta, new_scroll_delta,
+			screen->cursor_current.row,
+			screen->cursor_current.row - new_scroll_delta + 1,
+			screen->cursor_current.col,
+			screen->cursor_saved.row, screen->cursor_saved.col);
 
 	if (screen == terminal->pvt->screen)
 		vte_terminal_queue_adjustment_value_changed (
@@ -8130,9 +8194,10 @@ vte_terminal_set_size(VteTerminal *terminal, glong columns, glong rows)
 		_vte_ring_set_visible_rows_hint(terminal->pvt->alternate_screen.row_data, terminal->row_count);
 
 		/* Always resize normal screen, even if alternate is visible: bug 415277 */
-		vte_terminal_screen_set_size(terminal, &terminal->pvt->normal_screen, old_columns, old_rows);
+		vte_terminal_screen_set_size(terminal, &terminal->pvt->normal_screen, old_columns, old_rows, TRUE);
+		/* Resize the alternate screen if it's the current one, but never rewrap it */
 		if (terminal->pvt->screen == &terminal->pvt->alternate_screen)
-			vte_terminal_screen_set_size(terminal, &terminal->pvt->alternate_screen, old_columns, old_rows);
+			vte_terminal_screen_set_size(terminal, &terminal->pvt->alternate_screen, old_columns, old_rows, FALSE);
 
 		gtk_widget_queue_resize_no_redraw (&terminal->widget);
 		/* Our visible text changed. */
