[vte/vte-0-36] widget, emulation: Rewrap the lines when the window is resized



commit 01380dae776a560c61aecb95d06be5bf8ccae5f4
Author: Egmont Koblinger <egmont gmail com>
Date:   Wed Oct 23 00:35:30 2013 +0200

    widget,emulation: Rewrap the lines when the window is resized
    
    https://bugzilla.gnome.org/show_bug.cgi?id=336238

 doc/rewrap.txt    |  447 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/ring.c        |  426 +++++++++++++++++++++++++++++++++++++++++++++++++-
 src/ring.h        |    5 +
 src/vte-private.h |    4 -
 src/vte.c         |  104 +++++++++----
 src/vteseq.c      |    4 +-
 6 files changed, 951 insertions(+), 39 deletions(-)
---
diff --git a/doc/rewrap.txt b/doc/rewrap.txt
new file mode 100644
index 0000000..0f440d5
--- /dev/null
+++ b/doc/rewrap.txt
@@ -0,0 +1,447 @@
+╔════════════════╗
+║ VTE rewrapping ║
+╚════════════════╝
+
+as per the feature request and discussions at
+https://bugzilla.gnome.org/show_bug.cgi?id=336238
+
+by Egmont Koblinger and Behdad Esfahbod
+
+
+Overview
+════════
+
+It is a really cool feature if the terminal rewraps long lines when the window
+is resized.
+
+In order to implement this, we need to remember for each line whether we
+advanced to the next because a newline (a.k.a. linefeed) was printed, or
+because the end of line was reached. VTE and most other terminals already
+remember this (even if they don't support rewrap) for copy-paste purposes.
+
+Let's use the following terminology:
+
+A "line" or "row" (these two words are used interchangeably in this document)
+refer to a physical line of the terminal.
+
+A line is "hard wrapped" if it was terminated by an explicit newline. On
+contrary, a line is "soft wrapped" if the text overflowed to the next line.
+
+It's not clear by this definition whether the last line should be defined as
+hard or soft wrapped. It should be irrelevant. The definition also gets
+unclear as soon as we start printing escape codes that move the cursor. E.g.
+should positioning the cursor to the beginning of a previous line and printing
+something there effect the soft or hard wrapped state of the preceding line?
+
+A "paragraph" is one or more lines enclosed between two hard line breaks. That
+is, the line preceding the paragraph is hard wrapped (or we're at the
+beginning of the buffer), all lines of the paragraph except the last are soft
+wrapped, and the last line is hard wrapped (or we're at the end of the buffer,
+in which case it can also be soft wrapped).
+
+
+Specification
+═════════════
+
+Content after rewrapping
+────────────────────────
+
+The basic goal is that if an application prints some continuous stream of text
+(with no cursor positioning escape codes) then after resizing the terminal the
+text should look just as if it was originally printed at the new terminal
+width.
+
+Rewrapping paragraphs containing single width and combining characters only
+should be obvious.
+
+Double width (CJK) characters should not be cut in half. If they don't fit at
+the end of the row, they should overflow to the next, leaving one empty cell
+at the end of the previous line. That empty cell should not be considered when
+copy-pasting the text, nor when rewrapping the text again. This is the same as
+when the CJK text is originally printed.
+
+TAB characters are a nightmare. Even without rewrapping, their behavior is
+weird. You can print arbitrary amount of tabs, the cursor doesn't advance from
+the last column. Then you can print a letter, and the cursor stays just beyond
+the last cell and yet again you can print arbitrary amounts of tabs which do
+nothing. Then the next letter wraps to the next line. So, even without
+rewrapping, copy-pasting tabs around EOL doesn't reproduce the exact same text
+that was printed by the application, tab characters can get dropped. In order
+to "fix" this, we'd need to remember two numbers per line (number of tabs at
+EOL before the last character, and number of tabs at EOL after the last
+character). It's definitely not worth it. Furthermore, there's dynamic tab
+stop positions, and the very last thing we'd want to do is to remember for
+each tab character where the tab stops were when it was printed. So when
+rewrapping, we don't try to rewrap to the state exactly as if the application
+originally printed the text at the new width. If we do anything that's not
+obviously horribly broken then we're okay. (In other words, in this respect
+we're safe to say that tab is a cursor positioning code rather than a
+printable character.)
+
+
+Other generic expectations
+──────────────────────────
+
+Window managers can be configured to resize applications (and hence the VTE
+widget) only once for the final size, and can resize it continuously. It's
+expected that these two should lead to the same result (as much as possible).
+
+Some terminal emulators scroll to the bottom on resize. VTE has traditionally
+been cleverer, it kept the scroll position. I believe it's a nice feature and
+we should try to keep it the same.
+
+It is expected that a small difference in the way you resize the terminal
+shouldn't lead to a big difference in behavior. This is very hard to lay in
+exact specifications, these are rather "common sense" expectations, but I try
+to demonstrate via a couple of examples. If you change the width but all
+paragraphs were and still are shorter than the width, rewrapping shouldn't
+change the scroll offset. If there was only 1 paragraph that needed to be
+rewrapped from one line to two lines, the content shouldn't scroll by more
+than 1 line anywhere on the screen. If you change the height only, the
+behavior would be the same as with old non-rewrapping VTE. In this case the
+rewrapping code is actually skipped (because it's an expensive operation), but
+even if it was executed, the behavior should remain the same.
+
+
+Normal vs alternate screen
+──────────────────────────
+
+The normal screen should always be resized and rewrapped, even if the
+alternate screen is visible (bug 415277). This can occur immediately on each
+resize, or once when returning from the alternate screen. Probably resizing
+immediately gives a better user experience (main bug comment 34), since
+resizing is a heavyweight user-initiated event, while returning from the
+alternate screen is not where the user would expect the terminal to hang for
+some time.
+
+The alternate screen should not be rewrapped. It is used by applications that
+have full control over the entire area and they will repaint it themselves.
+Rewrapping by vte would cause ugly artifacts after vte rewraps but before the
+application catches up, e.g. characters aligned below each other would become
+arranged diagonally for a short while. (Moreover, with current VTE design,
+rewrapping the alternate screen would require many new fds to be used: main
+bug comment 60).
+
+
+Cursor position after rewrapping
+────────────────────────────────
+
+Both the active cursor and the saved cursor should be updated when rewrapping.
+(The saved cursor might be important e.g. when returning from alternate
+screen.)
+
+The cursor should ideally stay over the same character (whenever possible), or
+as "close" to that as possible. If it is over the second cell of a CJK, or in
+the middle of a Tab, it should remain so.
+
+In VTE versions prior to the rewrapping feature, the cursor can be anywhere to
+the right, even far away from the right end of the screen. This can occur
+easily when the window is narrowed. It's undecided yet if rewrapping will be
+an optional or mandatory feature. If it will be optional, this will still be
+the case. If it will be mandatory, it's not clear to me whether the cursor
+will be able to go past EOL. But, in any case, there are at least 1 more valid
+positions than the number of columns. E.g. with 80 columns, the cursor can be
+over the 1st character, ..., over the 80th character, or beyond the 80th
+character, which are 81 valid horizontal positions; in the latter case the
+cursor is not over a character. We need to distinguish all these positions and
+keep them during rewrap whenever possible.
+
+Let's assume the cursor's old position is not above a character, but at EOL or
+beyond. After rewrapping, we should try to maintain this position, so we
+should walk to the right from the corresponding character if possible.
+However, we should not walk into text that got joined with this line during
+rewrapping a paragraphs, nor should we wrap to next line.
+
+Here are a couple of examples. Imagine the cursor stands in the underlined
+cell (although it's technically an "upper one eighth block" character in the
+cell below in this document). The text printed by applications doesn't contain
+space characters in these examples.
+
+- The cursor is far to the right in a hard wrapped line. Keep that position,
+  no matter if visible or not:
+
+  ▏width 13    ▏                ▏width 20           ▏
+  paragraphend.         <->     paragraphend.
+  Newparagraph    ▔             Newparagraph    ▔
+
+- The cursor is far to the right in a soft wrapped line. That position cannot
+  be maintained, so jump to a character:
+
+  ▏width 11  ▏              ▏width 10 ▏           ▏width 12   ▏
+  blabla12345        ->     blabla1234     or     blabla123456
+  67890        ▔            567890                7890       ▔
+                             ▔
+- The cursor is far to the right in a soft wrapped line. That position can be
+  maintained because the next CJK doesn't fix:
+
+  ▏width 11  ▏               ▏width 12   ▏
+  blabla12345        <->     blabla12345
+  伀           ▔             伀           ▔
+
+- Wrapping a CJK leaves an empty cell. Also, keep the cursor under the second
+  half:
+
+  ▏width 13    ▏            ▏width 12   ▏
+  blabla12345伀     <->     blabla12345
+              ▔             伀
+                             ▔
+
+Shell prompt
+────────────
+
+If you resize the terminal to be narrower than your shell prompt (plus the
+command you're entering) while the shell is waiting for your command, you see
+weird behavior there. This is not a bug in rewrapping: it's because the shell
+redisplays its prompt (and command line) on every resize. There's not much VTE
+could do here.
+
+As a long term goal, maybe readline could have an option where it knows that
+the terminal rewraps its contents so that it doesn't redisplay the prompt and
+the command line, just expects the terminal to do this correctly. It's a bit
+risky, since probably all terminals that support rewrapping do this a little
+bit differently.
+
+
+Scroll position, cutting lines from the bottom
+──────────────────────────────────────────────
+
+A very tricky question is to figure out the scroll position after a resize.
+First, let's ignore bug 708213's requirements.
+
+Normally the scrollbar is at the bottom. If this is the case, it should remain
+so.
+
+How to position the scroll offset if the scrollbar is somewhere at the middle?
+Playing with various possibilities suggested that probably the best behavior
+is if we try to keep the bottom visible paragraph at the bottom. (After all,
+in terminals the bottom is far more important than the top.) It's not yet
+exactly specified if the bottom of the viewport cuts a paragraph in two, but
+still then we try to keep it approximately there.
+
+The exact implemented behavior is: we look at the character at the cell just
+under the viewport's bottom left corner, keep track where this character moves
+during rewrapping, and position the scrollbar so that this character is again
+just under the viewport.
+
+As an exception, I personally found a "snap to top" feature useful: if the
+scrollbar was all the way at the top, it should stay there.
+
+Now let's address bug 708213.
+
+This breaks the expectation that changing the terminal height back and forth
+should be a no-op. To match XTerm's behavior, when the window height is
+reduced and there are lines under the cursor then those lines should be
+dropped for good.
+
+It is very hard to figure out the desired behavior when this is combined with
+rewrapping. E.g. in one step you decrease the height and would expect lines to
+be dropped from the bottom, but in the very same step you increase the width
+which causes some previously wrapped paragraphs to fit in a single line (this
+could be above or below the cursor or just in the cursor's line, or all of
+these) which makes room for previously undisplayed lines. What to do then?
+
+The total number of rows, the number of rows above the cursor, and the number
+of rows below the cursor can all increase/decrease/stay pretty much
+independently from each other, almost all combinations are possible when
+resizing diagonally with rewrapping enabled. The behavior should also be sane
+when the cursor's paragraph starts wrapping.
+
+As an additional requirement, I had the aforementioned shell prompt feature in
+mind. One of the most typical use cases when the cursor is not in the bottom
+row is when you edit a multiline shell command and move the cursor back. In
+this case, shrinking the terminal shouldn't cut lines from the bottom.
+
+My best idea which reasonably covers all the possible cases is that we drop
+the lines (if necessary) after rewrapping, but before computing the new
+scrollbar offsets, and we drop the highest number of lines that satisfies all
+these three conditions:
+
+ - We shouldn't drop more lines than necessary to fit the content without
+   scrollbars.
+
+ - We should only drop data that's below the cursor's paragraph. (We don't
+   drop data that is under the cursor's row, but belongs to the same
+   paragraph).
+
+ - We track the character cell that immediately follows the cursor's
+   paragraph (that is, the line after this paragraph, first column), and see
+   how much it would get closer to the top of the window (assuming viewport is
+   scrolled to the bottom). The original bug is about that the cursor
+   shouldn't get closer to the top, with rewrapping I found that it's probably
+   not the cursor but the end of the cursor's paragraph that makes sense to
+   track. We shouldn't drop more lines than the amount by which this point
+   would get closer to the top.
+
+
+Implementation
+══════════════
+
+Storing lines
+─────────────
+
+Vte's ring was designed with rewrapping in mind, nevertheless it operates with
+rows. Changing it to work on paragraphs would require heavy refactoring, and
+would cause all sorts of troubles with overlong paragraphs. As the main
+features of terminals (showing content, scrolling etc.) are all built around
+rows, such a change for rewrapping only doesn't sound feasible. It's even
+unclear which approach would be better for a terminal built from scratch. So
+we decided to keep Vte operate with rows. Rewrapping is an expensive operation
+that builds up the notion of paragraphs from rows, and then cuts them to rows
+again.
+
+The scrollback buffer also remains defined in terms of lines, rather than
+paragraphs or memory. This also guarantees that the scrollbar's length cannot
+fluctuate.
+
+
+Ring
+────
+
+The ring contains some of the bottom rows in thawed state, while most of the
+scrollback buffer is frozen. Rewrapping is very complicated so we don't want
+the code to be duplicated. It is also computational heavy and we should try to
+be as fast as possible. Hence we work on frozen data structure in which most
+of the data lies, and we freeze all the rows for this purpose.
+
+The frozen text is stored in UTF-8. Care should be taken that the number of
+visual cells, number of Unicode characters, and number of bytes are three
+different values.
+
+The buffer is stored in three streams: text_stream contains the raw text
+encoded in UTF-8, with '\n' characters at paragraph boundaries; attr_stream
+contains records for each continuous run of identical attributes (same colors,
+character width, etc.) of text_stream (with the exception of '\n' where the
+attribute is ignored, e.g. it can be even embedded in a continuous run of
+double-width CJK characters); and row_stream consists of pointers into
+attr_steam and text_stream for every row. Out of these three, only row_stream
+needs to be regenerated.
+
+We start building up the new row stream beginning at new row number 0. We
+could make it any other arbitrary number, but we wouldn't be able to keep any
+of the old numbers unchanged (neither ring->start because lines can be dropped
+from the scrollback's top when narrowing the window, nor ring->end because we
+have no clue at the beginning how many rows we'll have), so there's no point
+even trying.
+
+
+Rewrapping
+──────────
+
+For higher performance, for each row we store whether it consists of ASCII
+32..126 characters only (excluding tabs too). (The flag can err in the safe
+way: it can be false even if the paragraph is ASCII only.) If a paragraph
+consists solely of such rows, we can rewrap it without looking at text_stream,
+since we know that all characters are stored as a single byte and all occupy a
+single cell.
+
+If it's not the case, we need to look at text_stream to be able to wrap the
+paragraph.
+
+Other than this, rewrapping is long, boring, but straightforward code without
+any further tricks.
+
+
+Markers
+───────
+
+There are some cell positions (I call them markers) that we need to keep track
+of, and tell where they moved during rewrapping. Such markers are the cursor,
+the saved cursor, the cell under the viewport's bottom left corner (for
+computing the new scrollbar offset) and the cell under the bottom left corner
+of the cursor's paragraph (for computing the number of lines to get dropped).
+
+A marker is a (row, column) pair where the row is either within the ring's
+range or in a further row, and the column is arbitrary.
+
+Before rewrapping, if the row is within the ring's range, the (row, column)
+pair is converted to a VteCellTextOffset which contains the text offset,
+fragment_cells denoting how many cells to walk from the first cell of a
+multicell character (i.e. 1 for the right half of a CJK), and eol_cells
+containing -1 if the cursor is over a character, 0 if the cursor is just after
+the last character, or more if the cursor is farther to the right. Example:
+
+  ▏width 24               ▏
+  Line 0 overflowing to LI
+  NE 1                    ▔
+
+If the cursor is over 'I' then text_offset is 23, eol_cells is -1.
+If the cursor is just after the 'I' (as shown) then text_offset is 24,
+eol_cells is 0.
+If the cursor is one n more cells further to the right then text_offset is 24,
+eol_cells is n.
+if the cursor is over 'N' then text_offset is 24 and eol_cells is -1.
+If the cursor is over 'E' then text_offset is 25 and eol_cells is -1.
+
+If the row is beyond the range covered by the ring, then text_offset will be
+text_stream's head for the immediate next row, one bigger for next row and so
+on, eol_cells will be set to the desired column, and fragment_cells is 0.
+Pretty much as if the ring continued with empty hard wrapped lines.
+
+After rewrapping, VteCellTextOffset is converted back to (row, column)
+according to the new width and new row numbering. This could be done solely
+based on VteCellTextOffset, but instead we update the row during rewrapping,
+and only compute the column afterwards. This is because we don't have a fast
+way of mapping text_offset to row number, this would require a binary search,
+it's much easier to remember this data when we're there anyway while
+rewrapping.
+
+
+Further optimization
+────────────────────
+
+In row_stream and attr_stream, along with the text offset we could similarly
+store the character offset (a counter that is increased by 1 on every Unicode
+character, in other words what the value of the text offset would be if we
+stored the text in UCS-4 rather than UTF-8).
+
+This, along with the fact that a cell's attribute contains the character
+width, and hence there is an attr change at every boundary where the character
+width changes, would enable us to compute the number of lines for each
+paragraph without looking at text_stream. This could be a huge win, since
+text_stream is by far the biggest of the three streams.
+
+The trick is however that we'd only know the number of lines for the
+paragraph, but not the text offsets for the inner lines. These would have to
+remain in a special uninitialized state in the new row_stream, and be computed
+lazily on demand. For storing that, streams would need to be writable at
+arbitrary positions, rather than just allowing appending of new data.
+
+Care should be taken that this "on demand" includes the case when they are
+being scrolled out from the scrollback buffer for good, because we'd still
+need to be able to tell the text offset for the remaining lines of the
+paragraph.
+
+
+Bugs
+════
+
+With the current design, the top of the scrollback buffer can easily contain a
+partial paragraph. After a subsequent resize, this might lead to the topmost
+row missing its first part. E.g. after executing "ls -l /bin" at width 40 and
+then widening the terminal, the first 40 characters of bash's paragraph can be
+cut off like this, because that used to form a row that got scrolled out:
+
+012 bash
+-rwxr-xr-x 3 root root   31152 Aug  3  2012 bunzip2
+-rwxr-xr-x 1 root root 1999912 Mar 13  2013 busybox
+
+With the current design I can't see any easy and clean workaround for this
+that wouldn't introduce other side effects or terribly complicated code. I'd
+say this is a small glitch we can easily live with.
+
+
+Caveats
+═══════
+
+With extremely large scrollback buffers (let's not forget: VTE supports
+infinite scrollback) rewrapping might become slow. On my computer (average
+laptop with Intel(R) Core(TM) i3 CPU , old-fashioned HDD) resizing 1 million
+lines take about 0.2 seconds wall clock time, this is close to the boundary of
+okay-ish speed.
+
+Developers writing Vte-based multi-tab terminal emulators are encouraged to
+resize only the visible Vte, the hidden ones should be resized when they
+become visible. This avoids the time it takes to rewrap the buffer to be
+multiplied with the number of tabs and so block the user for a long
+uninterrupted time when they resize the window.
+
diff --git a/src/ring.c b/src/ring.c
index c00acda..f36124d 100644
--- a/src/ring.c
+++ b/src/ring.c
@@ -35,8 +35,8 @@ _vte_ring_validate (VteRing * ring)
 {
        g_assert(ring != NULL);
        _vte_debug_print(VTE_DEBUG_RING,
-                       " Delta = %lu, Length = %lu, Max = %lu, Writable = %lu.\n",
-                       ring->start, ring->end - ring->start,
+                       " Delta = %lu, Length = %lu, Next = %lu, Max = %lu, Writable = %lu.\n",
+                       ring->start, ring->end - ring->start, ring->end,
                        ring->max, ring->end - ring->writable);
 
        g_assert (ring->start <= ring->writable);
@@ -100,8 +100,17 @@ _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 */
+       int soft_wrapped: 1;      /* end of line is not '\n' */
+       int is_ascii: 1;          /* for rewrapping speedup: guarantees that line contains 32..126 bytes 
only. Can be 0 even when ascii only. */
 } VteRowRecord;
 
+/* Represents a cell position, see ../doc/rewrap.txt */
+typedef struct _VteCellTextOffset {
+       gsize text_offset;    /* byte offset in text_stream (or perhaps beyond) */
+       gint fragment_cells;  /* extra number of cells to walk within a multicell character */
+       gint eol_cells;       /* -1 if over a character, >=0 if at EOL or beyond */
+} VteCellTextOffset;
+
 static gboolean
 _vte_ring_read_row_record (VteRing *ring, VteRowRecord *record, gulong position)
 {
@@ -124,8 +133,10 @@ _vte_ring_freeze_row (VteRing *ring, gulong position, const VteRowData *row)
 
        _vte_debug_print (VTE_DEBUG_RING, "Freezing row %lu.\n", position);
 
+       memset(&record, 0, sizeof (record));
        record.text_start_offset = _vte_stream_head (ring->text_stream);
        record.attr_start_offset = _vte_stream_head (ring->attr_stream);
+       record.is_ascii = 1;
 
        g_string_set_size (buffer, 0);
        for (i = 0, cell = row->cells; i < row->len; i++, cell++) {
@@ -171,11 +182,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);
@@ -339,9 +352,6 @@ _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);
-
        if (G_UNLIKELY (ring->writable == ring->start))
                _vte_ring_reset_streams (ring, ring->writable);
 
@@ -597,6 +607,412 @@ _vte_ring_set_visible_rows_hint (VteRing *ring, gulong rows)
 }
 
 
+/* Convert a (row,col) into a VteCellTextOffset.
+ * Requires the row to be frozen, or be outsize the range covered by the ring.
+ */
+static gboolean
+_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;
+       const VteRowData *row;
+       unsigned int i, num_chars, off;
+
+       if (position >= ring->end) {
+               offset->text_offset = _vte_stream_head (ring->text_stream) + position - ring->end;
+               offset->fragment_cells = 0;
+               offset->eol_cells = column;
+               return TRUE;
+       }
+
+       if (G_UNLIKELY (position < ring->start)) {
+               /* This happens when the marker (saved cursor position) is
+                  scrolled off at the top of the scrollback buffer. */
+               position = ring->start;
+               column = 0;
+               /* go on */
+       }
+
+       g_assert(position < ring->writable);
+       if (!_vte_ring_read_row_record (ring, &records[0], position))
+               return FALSE;
+       if ((position + 1) * sizeof (records[0]) < _vte_stream_head (ring->row_stream)) {
+               if (!_vte_ring_read_row_record (ring, &records[1], position + 1))
+                       return FALSE;
+       } else
+               records[1].text_start_offset = _vte_stream_head (ring->text_stream);
+
+       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 FALSE;
+
+       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 */
+
+       /* count the number of characters up to the given column */
+       offset->fragment_cells = 0;
+       offset->eol_cells = -1;
+       num_chars = 0;
+       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)) {
+                               offset->fragment_cells = column - i;
+                               break;
+                       }
+                       num_chars += _vte_unistr_strlen(cell->c);
+               }
+       }
+       if (i >= row->len) {
+               offset->eol_cells = column - i;
+       }
+
+       /* count the number of UTF-8 bytes for the given number of characters */
+       off = 0;
+       while (num_chars > 0 && off < buffer->len) {
+               off++;
+               if ((buffer->str[off] & 0xC0) != 0x80) num_chars--;
+       }
+       offset->text_offset = records[0].text_start_offset + off;
+       return TRUE;
+}
+
+
+/* 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, or be outsize the range covered by the ring.
+ */
+static gboolean
+_vte_frozen_row_text_offset_to_column (VteRing *ring,
+                                      gulong position,
+                                      const VteCellTextOffset *offset,
+                                      long *column)
+{
+       VteRowRecord records[2];
+       VteCell *cell;
+       GString *buffer = ring->utf8_buffer;
+       const VteRowData *row;
+       unsigned int i, off, num_chars, nc;
+
+       if (position >= ring->end) {
+               *column = offset->eol_cells;
+               return TRUE;
+       }
+
+       if (G_UNLIKELY (position < ring->start)) {
+               /* This happens when the marker (saved cursor position) is
+                  scrolled off at the top of the scrollback buffer. */
+               *column = 0;
+               return TRUE;
+       }
+
+       g_assert(position < ring->writable);
+       if (!_vte_ring_read_row_record (ring, &records[0], position))
+               return FALSE;
+       if ((position + 1) * sizeof (records[0]) < _vte_stream_head (ring->row_stream)) {
+               if (!_vte_ring_read_row_record (ring, &records[1], position + 1))
+                       return FALSE;
+       } else
+               records[1].text_start_offset = _vte_stream_head (ring->text_stream);
+
+       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 FALSE;
+
+       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 */
+
+       /* count the number of characters for the given UTF-8 text offset */
+       off = offset->text_offset - records[0].text_start_offset;
+       num_chars = 0;
+       for (i = 0; i < off && i < buffer->len; i++) {
+               if ((buffer->str[i] & 0xC0) != 0x80) num_chars++;
+       }
+
+       /* count the number of columns for the given number of characters */
+       for (i = 0, cell = row->cells; i < row->len; i++, cell++) {
+               if (G_LIKELY (!cell->attr.fragment)) {
+                       if (num_chars == 0) break;
+                       nc = _vte_unistr_strlen(cell->c);
+                       if (nc > num_chars) break;
+                       num_chars -= nc;
+               }
+       }
+
+       /* always add fragment_cells, but add eol_cells only if we're at eol */
+       i += offset->fragment_cells;
+       if (G_UNLIKELY (offset->eol_cells >= 0 && i == row->len))
+               i += offset->eol_cells;
+       *column = i;
+       return TRUE;
+}
+
+
+/**
+ * _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.
+ */
+/* See ../doc/rewrap.txt for design and implementation details. */
+void
+_vte_ring_rewrap (VteRing *ring,
+                 glong columns,
+                 VteVisualPosition **markers)
+{
+       gulong old_row_index, new_row_index;
+       int i;
+       int num_markers = 0;
+       VteCellTextOffset *marker_text_offsets;
+       VteVisualPosition *new_markers;
+       VteRowRecord old_record;
+       VteCellAttrChange attr_change;
+       VteStream *new_row_stream;
+       gsize paragraph_start_text_offset;
+       gsize paragraph_end_text_offset;
+       gsize paragraph_len;  /* excluding trailing '\n' */
+       gsize attr_offset;
+       gsize old_ring_end;
+
+       if (_vte_ring_length(ring) == 0)
+               return;
+       _vte_debug_print(VTE_DEBUG_RING, "Ring before rewrapping:\n");
+       _vte_ring_validate(ring);
+       new_row_stream = _vte_file_stream_new ();
+
+       /* 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++) {
+               /* Convert visual column into byte offset */
+               if (!_vte_frozen_row_column_to_text_offset(ring, markers[i]->row, markers[i]->col, 
&marker_text_offsets[i]))
+                       goto err;
+               new_markers[i].row = new_markers[i].col = -1;
+               _vte_debug_print(VTE_DEBUG_RING,
+                               "Marker #%d old coords:  row %ld  col %ld  ->  text_offset %ld  
fragment_cells %d  eol_cells %d\n",
+                               i, markers[i]->row, markers[i]->col, marker_text_offsets[i].text_offset,
+                               marker_text_offsets[i].fragment_cells, marker_text_offsets[i].eol_cells);
+       }
+
+       /* Prepare for rewrapping */
+       if (!_vte_ring_read_row_record(ring, &old_record, ring->start))
+               goto err;
+       paragraph_start_text_offset = old_record.text_start_offset;
+       paragraph_end_text_offset = _vte_stream_head (ring->text_stream);  /* initialized to silence gcc */
+       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);
+       }
+
+       old_row_index = ring->start + 1;
+       while (paragraph_start_text_offset < _vte_stream_head (ring->text_stream)) {
+               /* Find the boundaries of the next paragraph */
+               gboolean prev_record_was_soft_wrapped = FALSE;
+               gboolean paragraph_is_ascii = TRUE;
+               gsize paragraph_start_row = old_row_index - 1;
+               gsize paragraph_end_row;  /* points to beyond the end */
+               gsize text_offset = paragraph_start_text_offset;
+               VteRowRecord new_record;
+               glong col = 0;
+               _vte_debug_print(VTE_DEBUG_RING,
+                               "  Old paragraph:  row %ld  (text_offset %ld)  up to (exclusive)  ",  /* no 
'\n' */
+                               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);
+                       }
+                       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_RING,
+                               "row %ld  (text_offset %ld)%s  len %ld  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 */
+               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);
+                       }
+               }
+               memset(&new_record, 0, sizeof (new_record));
+               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. */
+                       gsize runlength;  /* number of bytes we process in one run: identical attributes, 
within 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);
+                               }
+                       }
+                       runlength = MIN(paragraph_len, attr_change.text_end_offset - text_offset);
+
+                       if (G_UNLIKELY (attr_change.attr.s.columns == 0)) {
+                               /* Combining characters all fit in the current row */
+                               text_offset += runlength;
+                               paragraph_len -= runlength;
+                       } else {
+                               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, (const char *) 
&new_record, sizeof (new_record));
+                                               _vte_debug_print(VTE_DEBUG_RING,
+                                                               "    New row %ld  text_offset %ld  
attr_offset %ld  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_RING,
+                                                                               "      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;
+                                       }
+                                       if (paragraph_is_ascii) {
+                                               /* Shortcut for quickly wrapping ASCII (excluding TAB) text.
+                                                  Don't read text_stream, and advance by a whole row of 
characters. */
+                                               int len = MIN(runlength, (gsize) (columns - col));
+                                               col += len;
+                                               text_offset += len;
+                                               paragraph_len -= len;
+                                               runlength -= len;
+                                       } else {
+                                               /* Process one character only. */
+                                               char textbuf[6];  /* fits at least one UTF-8 character */
+                                               int textbuf_len;
+                                               col += attr_change.attr.s.columns;
+                                               /* Find beginning of next UTF-8 character */
+                                               text_offset++; paragraph_len--; runlength--;
+                                               textbuf_len = MIN(runlength, sizeof (textbuf));
+                                               if (!_vte_stream_read(ring->text_stream, text_offset, 
textbuf, textbuf_len))
+                                                       goto err;
+                                               for (i = 0; i < textbuf_len && (textbuf[i] & 0xC0) == 0x80; 
i++) {
+                                                       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, (const char *) &new_record, sizeof (new_record));
+               _vte_debug_print(VTE_DEBUG_RING,
+                               "    New row %ld  text_offset %ld  attr_offset %ld\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_RING,
+                                               "      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. */
+       old_ring_end = ring->end;
+       g_object_unref(ring->row_stream);
+       ring->row_stream = new_row_stream;
+       ring->writable = ring->end = new_row_index;
+       ring->start = 0;
+       if (ring->end > ring->max)
+               ring->start = ring->end - ring->max;
+       ring->cached_row_num = (gulong) -1;
+
+       /* Find the markers. This requires that the ring is already updated. */
+       for (i = 0; i < num_markers; i++) {
+               /* Compute the row for markers beyond the ring */
+               if (new_markers[i].row == -1)
+                       new_markers[i].row = markers[i]->row - old_ring_end + ring->end;
+               /* Convert byte offset into visual column */
+               if (!_vte_frozen_row_text_offset_to_column(ring, new_markers[i].row, &marker_text_offsets[i], 
&new_markers[i].col))
+                       goto err;
+               _vte_debug_print(VTE_DEBUG_RING,
+                               "Marker #%d new coords:  text_offset %ld  fragment_cells %d  eol_cells %d  -> 
 row %ld  col %ld\n",
+                               i, marker_text_offsets[i].text_offset, marker_text_offsets[i].fragment_cells,
+                               marker_text_offsets[i].eol_cells, new_markers[i].row, new_markers[i].col);
+               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:
+#ifdef VTE_DEBUG
+       _vte_debug_print(VTE_DEBUG_RING,
+                       "Error while rewrapping\n");
+       g_assert_not_reached();
+#endif
+       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 bd98e4a..27a5ab9 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;
@@ -82,6 +86,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 121825c..561a652 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 c70ab48..274ec47 100644
--- a/src/vte.c
+++ b/src/vte.c
@@ -8083,37 +8083,72 @@ 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 do_rewrap)
 {
        VteRing *ring = screen->row_data;
+       VteVisualPosition cursor_saved_absolute;
+       VteVisualPosition below_viewport;
+       VteVisualPosition below_current_paragraph;
+       VteVisualPosition *markers[5];
        gboolean was_scrolled_to_top = (screen->scroll_delta == _vte_ring_delta(ring));
        gboolean was_scrolled_to_bottom = (screen->scroll_delta == screen->insert_delta);
+       glong old_top_lines;
        glong new_scroll_delta;
-       long cursor_saved_absolute_row = screen->insert_delta + screen->cursor_saved.row;
 
        _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"
+                       "     cursor_current (absolute)  row=%ld  (visual line %ld)  col=%ld\n"
+                       "     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);
 
        screen->scrolling_restricted = FALSE;
 
-       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.
-                  Drop lines from the bottom as XTerm does, see bug 708213 */
-               int drop_lines = MIN(
-                               old_rows - terminal->row_count,
-                               (screen->insert_delta + old_rows) - (screen->cursor_current.row + 1));
-               int new_ring_next = screen->insert_delta + old_rows - drop_lines;
-               _vte_debug_print(VTE_DEBUG_RESIZE,
-                               "Dropping %d rows at the bottom\n",
-                               drop_lines);
-               _vte_ring_shrink(ring, new_ring_next - _vte_ring_delta(ring));
+       cursor_saved_absolute.row = screen->cursor_saved.row + screen->insert_delta;
+       cursor_saved_absolute.col = screen->cursor_saved.col;
+       below_viewport.row = screen->scroll_delta + old_rows;
+       below_viewport.col = 0;
+       below_current_paragraph.row = screen->cursor_current.row + 1;
+       while (below_current_paragraph.row < _vte_ring_next(ring)
+           && _vte_ring_index(ring, below_current_paragraph.row - 1)->attr.soft_wrapped) {
+               below_current_paragraph.row++;
+       }
+       below_current_paragraph.col = 0;
+       markers[0] = &screen->cursor_current;
+       markers[1] = &cursor_saved_absolute;
+       markers[2] = &below_viewport;
+       markers[3] = &below_current_paragraph;
+       markers[4] = NULL;
+
+       old_top_lines = below_current_paragraph.row - screen->insert_delta;
+
+       if (do_rewrap && old_columns != terminal->column_count)
+               _vte_ring_rewrap(ring, terminal->column_count, markers);
+
+       if (_vte_ring_length(ring) > terminal->row_count) {
+               /* The content won't fit without scrollbars. Before figuring out the position, we might need 
to
+                  drop some lines from the ring if the cursor is not at the bottom, as XTerm does. See bug 
708213.
+                  This code is really tricky, see ../doc/rewrap.txt for details! */
+               glong new_top_lines, drop1, drop2, drop3, drop;
+               screen->insert_delta = _vte_ring_next(ring) - terminal->row_count;
+               new_top_lines = below_current_paragraph.row - screen->insert_delta;
+               drop1 = _vte_ring_length(ring) - terminal->row_count;
+               drop2 = _vte_ring_length(ring) - below_current_paragraph.row;
+               drop3 = old_top_lines - new_top_lines;
+               drop = MIN(MIN(drop1, drop2), drop3);
+               if (drop > 0) {
+                       int new_ring_next = screen->insert_delta + terminal->row_count - drop;
+                       _vte_debug_print(VTE_DEBUG_RESIZE,
+                                       "Dropping %ld [== MIN(%ld, %ld, %ld)] rows at the bottom\n",
+                                       drop, drop1, drop2, drop3);
+                       _vte_ring_shrink(ring, new_ring_next - _vte_ring_delta(ring));
+               }
        }
+
+       /* Figure out new insert and scroll deltas */
        if (_vte_ring_length(ring) <= terminal->row_count) {
                /* Everything fits without scrollbars. Align at top. */
                screen->insert_delta = _vte_ring_delta(ring);
@@ -8134,22 +8169,31 @@ 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 visible row stays. */
-                       new_scroll_delta = screen->scroll_delta + old_rows - terminal->row_count;
+                       /* Try to scroll so that the bottom visible 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? */
+                       new_scroll_delta = below_viewport.row - terminal->row_count;
                        _vte_debug_print(VTE_DEBUG_RESIZE,
                                        "Scroll so bottom row stays\n");
                }
        }
 
-       /* 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;
+       /* 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;
 
        _vte_debug_print(VTE_DEBUG_RESIZE,
-                       "New  ring_delta=%ld  ring_next=%ld\n"
-                       "New  insert_delta=%ld  scroll_delta=%ld\n\n",
-                       _vte_ring_delta(ring), _vte_ring_next(ring),
-                       screen->insert_delta, new_scroll_delta);
+                       "New  insert_delta=%ld  scroll_delta=%ld\n"
+                       "     cursor_current (absolute)  row=%ld  (visual line %ld)  col=%ld\n"
+                       "     cursor_saved (relative to insert_delta)  row=%ld  col=%ld\n\n",
+                       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 (
@@ -8202,10 +8246,12 @@ 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: bug 336238 
comment 60 */
                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);
 
+               _vte_terminal_adjust_adjustments_full (terminal);
                gtk_widget_queue_resize_no_redraw (&terminal->widget);
                /* Our visible text changed. */
                vte_terminal_emit_text_modified(terminal);
diff --git a/src/vteseq.c b/src/vteseq.c
index 975f3b2..0ed8165 100644
--- a/src/vteseq.c
+++ b/src/vteseq.c
@@ -1151,7 +1151,9 @@ vte_sequence_handler_cd (VteTerminal *terminal, GValueArray *params)
                        rowdata = _vte_terminal_ring_append (terminal, FALSE);
                }
                /* Pad out the row. */
-               _vte_row_data_fill (rowdata, &screen->fill_defaults, terminal->column_count);
+               if (screen->fill_defaults.attr.back != VTE_DEF_BG) {
+                       _vte_row_data_fill (rowdata, &screen->fill_defaults, terminal->column_count);
+               }
                rowdata->attr.soft_wrapped = 0;
                /* Repaint this row. */
                _vte_invalidate_cells(terminal,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]