Repository: asciinema/asciinema (language: rust)

Issue:
Will not survive window resizing
When you start asciinema in a large terminal window, then resize the window (during recording) to smaller size, the video comes out all wrong.

Interestingly enough, it survives well in the opposite way.

In this example, I run [`sl` - my favourite program](http://manpages.ubuntu.com/manpages/wily/man6/sl.6.html) - in a big terminal window, then resize the window to small window (that's the "awkward pause" - me resizing the window) and run `sl` again. The second run looks OK, but the first is mangled.

https://asciinema.org/a/5wvarzb4545pohhfqnj8g9fta

For illustration, this is opposite example. Starting small, then resizing to big. Both runs work well.

http://asciinema.org/a/d9u278gcavlymvywrleh0kbup



Discussion / hints:
asciinema doesn't record screen but the stdout stream. It also saves the current terminal dimensions **at the end** of the recording session. Some terminal apps (as `sl`) are depending on the actual terminal size. They produce escape sequences that are suited for the **current** terminal width/height at any given point in time. Because asciinema player actually interprets/executes recorded stdout, these escape sequences are executed against the current asciinema terminal size.

In other words, to solve this problem asciinema would need to save the terminal resize events (this can be done) and then use them to resize its own "terminal" during the playback. However, that would cause the resizing of the player itself, which would look funny (when replayed on asciinema.org) and be impractical (when embedded on a site).

I understand the challenges and I understand if you don't want to fix it, I just wanted to let you know.

It could be solved by making the player the maximal width of all the widths and the maximal height of all the heights and the text could be in top left - it would be the same case as if you start with small terminal and end with big terminal. I don't know if it's worth fixing though :)

It's not that I don't want to fix it, but there are more subtleties with this (your proposed solution would still give bad results in some cases). Anyway, this is kind of an edge case, and I'm too not sure if it's worth fixing.

I'm leaving this open though.

This is issue would prohibit casual use of asciinema to record all daily work, creating an irritating limitation to never shrink terminal windows.

I think there must be a flexibility for dimens. I had recorded a video ([here](https://asciinema.org/a/c69u59rtbb8d774ll1syepzap)) and it came out in awkward dimens, i.e. the embedded video took more that the height of the page. Now, I have no choice other than recording again. 
I even tried to change the width and height in the `.json`, which of course didn't work well, as I was having overlapping ([this](https://asciinema.org/a/cmhktih41mk23ru0kxkj76jl9))

**P.S** Even though this is off topic of the above discussion, this was the closest issue I could find regarding this.
@xloem @mukkachaitanya I am designing new recording format (https://github.com/asciinema/asciinema/pull/196). Maybe we could make it support "terminal resize" event... 
I need some util to "backup" my terminal activity. And, of course, this util should "just work" even if I resize terminal. Maybe there are other programs out there, which support the feature?
could you shed some light on how to capture the terminal resize event? Would be really useful in a branch I'm working on for realtime asciicasts (and the resize case during recording I'm sure)
@jiangts here's the place where the recorder handles terminal resize (SIGWINCH signal): https://github.com/asciinema/asciinema/blob/develop/asciinema/pty_recorder.py#L108

Fix patch:
diff --git a/asciinema/asciicast/raw.py b/asciinema/asciicast/raw.py
index 874385a1e..b8df4c322 100644
--- a/asciinema/asciicast/raw.py
+++ b/asciinema/asciicast/raw.py
@@ -1,7 +1,7 @@
 import os
 import sys
 from os import path
-from typing import Any, Callable, Optional
+from typing import Any, Callable, Optional, Tuple
 
 from ..file_writer import file_writer
 
@@ -44,13 +44,16 @@ def __enter__(self) -> Any:
     def write_stdout(self, _ts: float, data: Any) -> None:
         self._write(data)
 
-    # pylint: disable=no-self-use
     def write_stdin(self, ts: float, data: Any) -> None:
         pass
 
     def write_marker(self, ts: float) -> None:
         pass
 
+    def write_resize(self, ts: float, size: Tuple[int, int]) -> None:
+        cols, rows = size
+        self._write(f"\x1b[8;{rows};{cols}t".encode("utf-8"))
+
     # pylint: disable=consider-using-with
     def _open_file(self) -> None:
         if self.path == "-":
diff --git a/asciinema/asciicast/v2.py b/asciinema/asciicast/v2.py
index 0ec4765df..b2df14508 100644
--- a/asciinema/asciicast/v2.py
+++ b/asciinema/asciicast/v2.py
@@ -12,6 +12,7 @@
     List,
     Optional,
     TextIO,
+    Tuple,
     Union,
 )
 
@@ -148,6 +149,10 @@ def write_stdin(self, ts: float, data: Union[str, bytes]) -> None:
     def write_marker(self, ts: float) -> None:
         self.__write_event(ts, "m", "")
 
+    def write_resize(self, ts: float, size: Tuple[int, int]) -> None:
+        cols, rows = size
+        self.__write_event(ts, "r", f"{cols}x{rows}")
+
     # pylint: disable=consider-using-with
     def _open_file(self) -> None:
         if self.path == "-":
diff --git a/asciinema/pty_.py b/asciinema/pty_.py
index ce5ecd57e..4f5f18adf 100644
--- a/asciinema/pty_.py
+++ b/asciinema/pty_.py
@@ -41,8 +41,14 @@ def record(
     add_marker_key = key_bindings.get("add_marker")
     input_data = bytes()
 
-    def set_pty_size() -> None:
-        cols, rows = get_tty_size()
+    def handle_resize() -> None:
+        size = get_tty_size()
+        set_pty_size(size)
+        assert start_time is not None
+        writer.write_resize(time.perf_counter() - start_time, size)
+
+    def set_pty_size(size: Tuple[int, int]) -> None:
+        cols, rows = size
         buf = array.array("h", [rows, cols, 0, 0])
         fcntl.ioctl(pty_fd, termios.TIOCSWINSZ, buf)
 
@@ -149,7 +155,7 @@ def copy(signal_fd: int) -> None:  # pylint: disable=too-many-branches
                         if sig in EXIT_SIGNALS:
                             crfds.remove(signal_fd)
                         if sig == signal.SIGWINCH:
-                            set_pty_size()
+                            handle_resize()
 
             if pty_fd in wfds:
                 n = os.write(pty_fd, input_data)
@@ -164,7 +170,7 @@ def copy(signal_fd: int) -> None:  # pylint: disable=too-many-branches
     fcntl.fcntl(pty_fd, fcntl.F_SETFL, flags)
 
     start_time = time.perf_counter()
-    set_pty_size()
+    set_pty_size(get_tty_size())
 
     with SignalFD(EXIT_SIGNALS + [signal.SIGWINCH]) as sig_fd:
         with raw(tty_stdin_fd):
diff --git a/asciinema/recorder.py b/asciinema/recorder.py
index c6814aba9..460b4ff73 100644
--- a/asciinema/recorder.py
+++ b/asciinema/recorder.py
@@ -134,6 +134,9 @@ def write_stdout(self, ts: float, data: Any) -> None:
     def write_marker(self, ts: float) -> None:
         self.enqueue([ts, "m", None])
 
+    def write_resize(self, ts: float, size: Tuple[int, int]) -> None:
+        self.enqueue([ts, "r", size])
+
     def run(self) -> None:
         try:
             with self.writer as w:
@@ -148,6 +151,8 @@ def run(self) -> None:
                         w.write_stdin(self.time_offset + ts, data)
                     elif etype == "m":
                         w.write_marker(self.time_offset + ts)
+                    elif etype == "r":
+                        w.write_resize(self.time_offset + ts, data)
         except IOError:
             for event in iter(self.queue.get, None):
                 pass
diff --git a/doc/asciicast-v2.md b/doc/asciicast-v2.md
index e7f6e8243..626b613f9 100644
--- a/doc/asciicast-v2.md
+++ b/doc/asciicast-v2.md
@@ -15,6 +15,7 @@ Example file:
 [1.001376, "o", "That was ok\rThis is better."]
 [1.500000, "m", ""]
 [2.143733, "o", "Now... "]
+[4.050000, "r", "80x24"]
 [6.541828, "o", "Bye!"]
 ```
 
@@ -115,7 +116,7 @@ Where:
 
 * `time` (float) - indicates when this event happened, represented as the number
   of seconds since the beginning of the recording session,
-* `event-type` (string) - one of: `"o"`, `"i"`, `"m"`
+* `event-type` (string) - one of: `"o"`, `"i"`, `"m"`, `"r"`
 * `event-data` (any) - event specific data, described separately for each event
   type.
 
@@ -176,6 +177,13 @@ the user to resume.
 `event-data` can be used to annotate a marker. Annotations may be used to e.g.
 show a list of named "chapters".
 
+#### "r" - resize
+
+Event of type `"r"` represents terminal resize.
+
+`event-data` contains new terminal size (columns + rows) formatted as
+`"{COLS}x{ROWS}"`, e.g. `"80x24"`.
+
 ## Notes on compatibility
 
 Version 2 of asciicast file format solves several problems which couldn't be


Test patch: