This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch tmux-integration
in repository terminology.

View the commit online.

commit 70e1e3c1812be494205c3222136eedf0eadc253f
Author: Cedric BAIL <[email protected]>
AuthorDate: Mon Mar 30 19:16:42 2026 -0600

    feat(tmux): populate backlog from session history on attach
    
    When attaching to an existing tmux session, fetch scrollback history
    before the visible screen content so users can scroll up to see
    previous output with colors and attributes preserved.
    
    Bootstrap sequence now sends per pane:
      1. capture-pane -p -e -S -<N> -E -1  (history, N from config)
      2. capture-pane -p -e               (visible screen)
      3. list-panes -s                    (cursor positions, once)
    
    History content is fed through the VT state machine; as lines scroll
    off the top of the screen, termpty_text_save_top() pushes them into
    the ty->back ring buffer.  ESC[2J + ESC[H clears the screen and homes
    the cursor before the screen capture feed, without destroying the
    backlog (unlike ESC c / RIS which calls termpty_clear_backlog()).
    
    Also:
    - Invoke queued command callbacks on tmux_control_free() to prevent
      Tmux_Capture_Data leaks and dangling Win pointers on teardown
    - Remove dead tmux_pane_scrollback_fetch() API (replaced by the
      bootstrap-integrated approach)
    - Fix dead line_start assignment in trailing-blank-line strip logic
---
 src/bin/termpty.c      |  58 ++++++++
 src/bin/tmux_control.c |  28 ++--
 src/bin/tmux_control.h |   2 +-
 src/bin/tmux_pane.c    |  64 +--------
 src/bin/tmux_pane.h    |   4 -
 src/bin/win.c          | 370 +++++++++++++++++++++++++++++++++++--------------
 6 files changed, 347 insertions(+), 179 deletions(-)

diff --git a/src/bin/termpty.c b/src/bin/termpty.c
index 3c9829e8..ae741a4c 100644
--- a/src/bin/termpty.c
+++ b/src/bin/termpty.c
@@ -1459,6 +1459,64 @@ termpty_resize(Termpty *ty, int new_w, int new_h)
    struct screen_info new_si = {.screen = NULL};
    if ((ty->w == new_w) && (ty->h == new_h)) return;
 
+   /* Tmux-backend panes: tmux manages the content, so skip the full
+    * rewrap/backlog logic.  Reallocate screen buffers at the new
+    * dimensions, preserving as much of the old content as fits (copy
+    * row-by-row up to MIN(old, new) dimensions).  This avoids losing
+    * capture-pane content when a deferred size-job triggers a resize
+    * after the screen has already been populated.
+    *
+     * The backlog (ty->back) is left intact for scrollback viewing.
+     * The cursor is clamped to the new bounds rather than reset to 0,0
+     * so that a subsequent cursor-position restore from tmux lands on
+     * the correct row.
+     *
+     * The altbuf flag is preserved as-is — tmux manages alt-screen mode
+     * independently and will send the appropriate mode-switch sequences
+     * after the resize is acknowledged. */
+   if (ty->backend == TERMPTY_BACKEND_TMUX)
+     {
+        Termcell *s, *s2;
+        int copy_w, copy_h, r;
+
+        s = calloc(1, sizeof(Termcell) * new_w * new_h);
+        if (!s) return;
+        s2 = calloc(1, sizeof(Termcell) * new_w * new_h);
+        if (!s2)
+          {
+             free(s);
+             return;
+          }
+        copy_w = (old_w < new_w) ? old_w : new_w;
+        copy_h = (old_h < new_h) ? old_h : new_h;
+        for (r = 0; r < copy_h; r++)
+          {
+             int src_r = (r + ty->circular_offset) % old_h;
+             int src2_r = (r + ty->circular_offset2) % old_h;
+             memcpy(s  + (r * new_w),
+                    ty->screen  + (src_r  * old_w),
+                    copy_w * sizeof(Termcell));
+             memcpy(s2 + (r * new_w),
+                    ty->screen2 + (src2_r * old_w),
+                    copy_w * sizeof(Termcell));
+          }
+        free(ty->screen);
+        free(ty->screen2);
+        ty->screen = s;
+        ty->screen2 = s2;
+        ty->w = new_w;
+        ty->h = new_h;
+        if (ty->cursor_state.cx >= new_w)
+          ty->cursor_state.cx = new_w - 1;
+        if (ty->cursor_state.cy >= new_h)
+          ty->cursor_state.cy = new_h - 1;
+        ty->cursor_state.wrapnext = 0;
+        ty->circular_offset = 0;
+        ty->circular_offset2 = 0;
+        termpty_resize_tabs(ty, old_w, new_w);
+        return;
+     }
+
    termpty_backlog_lock();
 
    if (ty->altbuf)
diff --git a/src/bin/tmux_control.c b/src/bin/tmux_control.c
index 71272ac3..a3b2be0b 100644
--- a/src/bin/tmux_control.c
+++ b/src/bin/tmux_control.c
@@ -931,9 +931,14 @@ tmux_control_free(Tmux_Control *tc)
         tc->fd = -1;
      }
 
-   /* Free command queue */
+   /* Free command queue — invoke callbacks so callers can free their data.
+    * Pass NULL response and exit_code=-1 to signal teardown cancellation. */
    EINA_LIST_FREE(tc->cmd_queue, entry)
-     _cmd_entry_free(entry);
+     {
+        if (entry->cb)
+          entry->cb(entry->data, NULL, -1);
+        _cmd_entry_free(entry);
+     }
 
    /* Free line buffer */
    free(tc->line_buf);
@@ -942,7 +947,7 @@ tmux_control_free(Tmux_Control *tc)
    free(tc);
 }
 
-void
+Eina_Bool
 tmux_control_send_cmd(Tmux_Control *tc, const char *cmd, Tmux_Cmd_Cb cb, void *data)
 {
    Tmux_Cmd_Entry *entry;
@@ -951,10 +956,10 @@ tmux_control_send_cmd(Tmux_Control *tc, const char *cmd, Tmux_Cmd_Cb cb, void *d
    ssize_t written;
    size_t len;
 
-   if (!tc || tc->fd < 0) return;
+   if (!tc || tc->fd < 0) return EINA_FALSE;
 
    entry = calloc(1, sizeof(Tmux_Cmd_Entry));
-   if (!entry) return;
+   if (!entry) return EINA_FALSE;
 
    entry->cb   = cb;
    entry->data = ""
@@ -963,7 +968,7 @@ tmux_control_send_cmd(Tmux_Control *tc, const char *cmd, Tmux_Cmd_Cb cb, void *d
    if (!td)
      {
         free(entry);
-        return;
+        return EINA_FALSE;
      }
    td->tc    = tc;
    td->entry = entry;
@@ -985,6 +990,8 @@ tmux_control_send_cmd(Tmux_Control *tc, const char *cmd, Tmux_Cmd_Cb cb, void *d
      ERR("Failed to write command to tmux PTY: %s", strerror(errno));
    else if (written >= 0 && (size_t)written < len)
      WRN("Partial write to tmux PTY (%zd of %zu bytes)", written, len);
+
+   return EINA_TRUE;
 }
 
 void
@@ -1043,11 +1050,12 @@ tmux_control_reconnect(Tmux_Control *tc)
    tc->line_len     = 0;
    tc->current_response = NULL;
 
-   /* Flush pending commands */
-   while (tc->cmd_queue)
+   /* Flush pending commands — invoke callbacks so callers can free their
+    * data.  Pass NULL response and exit_code=-1 to signal cancellation. */
+   EINA_LIST_FREE(tc->cmd_queue, entry)
      {
-        entry = eina_list_data_get(tc->cmd_queue);
-        tc->cmd_queue = eina_list_remove_list(tc->cmd_queue, tc->cmd_queue);
+        if (entry->cb)
+          entry->cb(entry->data, NULL, -1);
         _cmd_entry_free(entry);
      }
 
diff --git a/src/bin/tmux_control.h b/src/bin/tmux_control.h
index 7fb1ca29..4d5ce24d 100644
--- a/src/bin/tmux_control.h
+++ b/src/bin/tmux_control.h
@@ -31,7 +31,7 @@ typedef struct _Tmux_Control_Callbacks {
 
 Tmux_Control *tmux_control_new(const char *tmux_path, const char *session_name);
 void          tmux_control_free(Tmux_Control *tc);
-void          tmux_control_send_cmd(Tmux_Control *tc, const char *cmd, Tmux_Cmd_Cb cb, void *data);
+Eina_Bool     tmux_control_send_cmd(Tmux_Control *tc, const char *cmd, Tmux_Cmd_Cb cb, void *data);
 void          tmux_control_callbacks_set(Tmux_Control *tc, const Tmux_Control_Callbacks *cbs);
 void          tmux_control_detach(Tmux_Control *tc);
 void          tmux_control_reconnect(Tmux_Control *tc);
diff --git a/src/bin/tmux_pane.c b/src/bin/tmux_pane.c
index 9e049cb2..603e3ae2 100644
--- a/src/bin/tmux_pane.c
+++ b/src/bin/tmux_pane.c
@@ -24,12 +24,6 @@ static int _tmux_pane_log_dom = -1;
 #define INF(...)      EINA_LOG_DOM_INFO(_tmux_pane_log_dom, __VA_ARGS__)
 #define DBG(...)      EINA_LOG_DOM_DBG(_tmux_pane_log_dom, __VA_ARGS__)
 
-typedef struct _Tmux_Scrollback_Req {
-   Tmux_Pane         *tp;
-   Tmux_Scrollback_Cb cb;
-   void              *data;
-} Tmux_Scrollback_Req;
-
 struct _Tmux_Pane {
    Tmux_Control  *tc;
    Termpty       *ty;
@@ -72,40 +66,6 @@ _tmux_pane_write_cb(void *data, const char *input, int len)
      }
 }
 
-/* ------------------------------------------------------------------ */
-/* Scrollback response callback                                         */
-/* ------------------------------------------------------------------ */
-
-static void
-_scrollback_cmd_cb(void *data, const char *response, int exit_code)
-{
-   Tmux_Scrollback_Req *req = data;
-   int count = 0;
-   const char *p;
-
-   if (!req) return;
-
-   if (exit_code != 0 || !response)
-     {
-        if (req->cb) req->cb(req->data, NULL, 0);
-        free(req);
-        return;
-     }
-
-   /* Count lines */
-   if (response && *response)
-     {
-        for (p = response; *p; p++)
-          if (*p == '\n') count++;
-        /* Account for final line without trailing newline */
-        if (p > response && *(p - 1) != '\n') count++;
-     }
-
-   if (req->cb)
-     req->cb(req->data, response, count);
-   free(req);
-}
-
 /* ------------------------------------------------------------------ */
 /* Log domain init                                                      */
 /* ------------------------------------------------------------------ */
@@ -221,32 +181,10 @@ tmux_pane_resize_axis(Tmux_Pane *tp, int w, int h, Eina_Bool horizontal)
    else
       snprintf(cmd, sizeof(cmd), "resize-pane -t %%%d -x %d",
                tp->pane_id, w);
+   INF("tmux cmd: %s", cmd);
    tmux_control_send_cmd(tp->tc, cmd, NULL, NULL);
 }
 
-void
-tmux_pane_scrollback_fetch(Tmux_Pane *tp, int start, int count,
-                           Tmux_Scrollback_Cb cb, void *data)
-{
-   Tmux_Scrollback_Req *req;
-   char cmd[256];
-
-   if (!tp || !tp->tc) return;
-
-   req = calloc(1, sizeof(Tmux_Scrollback_Req));
-   if (!req) return;
-
-   req->tp = tp;
-   req->cb = cb;
-   req->data = ""
-
-   /* capture-pane -p -S -<first_line> -E -<last_line> */
-   snprintf(cmd, sizeof(cmd),
-             "capture-pane -p -S -%d -E -%d -t %%%d",
-             start + count, start, tp->pane_id);
-   tmux_control_send_cmd(tp->tc, cmd, _scrollback_cmd_cb, req);
-}
-
 int
 tmux_pane_id_get(const Tmux_Pane *tp)
 {
diff --git a/src/bin/tmux_pane.h b/src/bin/tmux_pane.h
index 88646941..087a701a 100644
--- a/src/bin/tmux_pane.h
+++ b/src/bin/tmux_pane.h
@@ -12,9 +12,6 @@ struct tag_Termpty;
 typedef struct tag_Termpty Termpty;
 #endif
 
-/* Scrollback fetch callback: called with lines of text */
-typedef void (*Tmux_Scrollback_Cb)(void *data, const char *lines, int count);
-
 void        tmux_pane_init(void);
 void        tmux_pane_shutdown(void);
 
@@ -27,7 +24,6 @@ void        tmux_pane_feed(Tmux_Pane *tp, const char *data, size_t len);
 void        tmux_pane_write(Tmux_Pane *tp, const char *data, int len);
 void        tmux_pane_resize(Tmux_Pane *tp, int w, int h);
 void        tmux_pane_resize_axis(Tmux_Pane *tp, int w, int h, Eina_Bool horizontal);
-void        tmux_pane_scrollback_fetch(Tmux_Pane *tp, int start, int count, Tmux_Scrollback_Cb cb, void *data);
 
 int         tmux_pane_id_get(const Tmux_Pane *tp);
 
diff --git a/src/bin/win.c b/src/bin/win.c
index fcda1565..2f415e1e 100644
--- a/src/bin/win.c
+++ b/src/bin/win.c
@@ -201,8 +201,9 @@ typedef enum {
 } Tmux_Connection_State;
 
 typedef struct _Tmux_Capture_Data {
-   Win *wn;
-   int  pane_id;
+   Win      *wn;
+   int       pane_id;
+   Eina_Bool has_history; /* TRUE when history capture precedes this screen capture */
 } Tmux_Capture_Data;
 
 struct tag_Win
@@ -3259,7 +3260,7 @@ _split_detach(Term_Container *tc, Term_Container *solo_child)
 }
 
 static Eina_Bool
-_split_notify_tmux_resize(Term_Container *tc)
+_split_notify_tmux_resize(Term_Container *tc, Eina_Bool is_horizontal)
 {
    Split *sp;
    Solo *solo;
@@ -3283,20 +3284,24 @@ _split_notify_tmux_resize(Term_Container *tc)
         evas_object_geometry_get(term->termio, NULL, NULL, &ow, &oh);
         termio_character_size_get(term->termio, &chw, &chh);
         if (chw <= 0 || chh <= 0) return EINA_FALSE;
-         w = ow / chw;
-         h = oh / chh;
-         DBG("tmux resize pane %%%d: pixels %dx%d cell %dx%d -> %dx%d cells",
-             tmux_pane_id_get(tp), ow, oh, chw, chh, w, h);
-         tmux_pane_resize(tp, w, h);
+        w = ow / chw;
+        h = oh / chh;
+        DBG("tmux resize pane %%%d: pixels %dx%d cell %dx%d -> %dx%d cells"
+            " (axis=%s)",
+            tmux_pane_id_get(tp), ow, oh, chw, chh, w, h,
+            is_horizontal ? "horizontal" : "vertical");
+        tmux_pane_resize_axis(tp, w, h, is_horizontal);
         return EINA_TRUE;
      }
 
    if (tc->type == TERM_CONTAINER_TYPE_SPLIT)
      {
         sp = (Split *)tc;
-        if (_split_notify_tmux_resize(sp->tc1))
+        /* Use sp->is_horizontal when recursing into children — the inner
+         * split may have a different orientation than the outer caller. */
+        if (_split_notify_tmux_resize(sp->tc1, sp->is_horizontal))
           return EINA_TRUE;
-        return _split_notify_tmux_resize(sp->tc2);
+        return _split_notify_tmux_resize(sp->tc2, sp->is_horizontal);
      }
 
    return EINA_FALSE;
@@ -3311,10 +3316,11 @@ _split_panes_unpress_cb(void *data, Evas_Object *obj EINA_UNUSED,
    DBG("panes unpress: is_horizontal=%d left_size=%.4f",
        (int)split->is_horizontal,
        elm_panes_content_left_size_get(split->panes));
-   /* Change Set 6c (C4): We only send resize-pane for the first child (tc1).
-    * This is intentional — tmux automatically adjusts sibling panes when one
-    * is resized.  Sending resize for both sides causes commands to fight. */
-   _split_notify_tmux_resize(split->tc1);
+   /* Send axis-specific resize for the first child only.  tmux
+    * automatically adjusts sibling panes on the same axis.
+    * Using resize_axis (-x or -y) instead of resize (-x -y) avoids
+    * clobbering the orthogonal dimension and crushing siblings. */
+   _split_notify_tmux_resize(split->tc1, split->is_horizontal);
 }
 
 static Term_Container *
@@ -8310,8 +8316,50 @@ _tmux_cursor_list_cb(void *data, const char *response, int exit_code)
      }
 }
 
+/* Strip trailing whitespace-only lines from a capture-pane response.
+ * capture-pane outputs the full pane height with trailing spaces preserved,
+ * so blank lines appear as "          \n" rather than bare "\n".  Walk
+ * backwards, dropping each trailing line that contains only spaces/tabs.
+ * Returns the trimmed length. */
+static size_t
+_tmux_strip_trailing_blanks(const char *response, size_t resp_len)
+{
+   while (resp_len > 0)
+     {
+        size_t line_start;
+        size_t k;
+        Eina_Bool blank;
+
+        if (response[resp_len - 1] != '\n')
+          break;
+
+        for (line_start = resp_len - 1; line_start > 0; line_start--)
+          {
+             if (response[line_start - 1] == '\n')
+               break;
+          }
+
+        blank = EINA_TRUE;
+        for (k = line_start; k < resp_len - 1; k++)
+          {
+             if (response[k] != ' ' && response[k] != '\t')
+               {
+                  blank = EINA_FALSE;
+                  break;
+               }
+          }
+
+        if (!blank)
+          break;
+
+        resp_len = line_start;
+     }
+
+   return resp_len;
+}
+
 static void
-_tmux_capture_pane_cb(void *data, const char *response, int exit_code)
+_tmux_history_pane_cb(void *data, const char *response, int exit_code)
 {
    Tmux_Capture_Data *cd = data;
    Win *wn;
@@ -8329,55 +8377,117 @@ _tmux_capture_pane_cb(void *data, const char *response, int exit_code)
    pane_id = cd->pane_id;
    free(cd);
 
+   if (!wn) return;
+
+   DBG("history capture-pane callback for pane %d: response=%s exit_code=%d",
+       pane_id, response ? "present" : "NULL", exit_code);
+
+   if (!response || exit_code != 0)
+     {
+        /* Silently skip — screen capture will still populate visible content */
+        DBG("history capture-pane: skipping pane %d (exit_code=%d)",
+            pane_id, exit_code);
+        return;
+     }
+
+   if (!response[0])
+     {
+        DBG("history capture-pane: empty response for pane %d", pane_id);
+        return;
+     }
+
+   resp_len = _tmux_strip_trailing_blanks(response, strlen(response));
+   if (resp_len == 0) return;
+
+   term = eina_hash_find(wn->tmux_pane_to_term, &pane_id);
+   if (!term)
+     {
+        DBG("history capture-pane response: pane %d no longer mapped", pane_id);
+        return;
+     }
+
+   tp = evas_object_data_get(term->bg, "tmux_pane");
+   if (tp)
+     {
+        /* Expand \n → \r\n so the VT parser sees proper line endings.
+         * As content scrolls off the top, termpty_text_save_top() populates
+         * ty->back automatically — no direct ring-buffer access needed. */
+        nl_count = 0;
+        for (i = 0; i < resp_len; i++)
+          {
+             if (response[i] == '\n') nl_count++;
+          }
+
+        DBG("history capture-pane feeding %zu bytes (%zu lines) to pane %d",
+            resp_len, nl_count, pane_id);
+
+        buf = malloc(resp_len + nl_count + 1);
+        if (buf)
+          {
+             d = buf;
+             for (i = 0; i < resp_len; i++)
+               {
+                  if (response[i] == '\n')
+                    *d++ = '\r';
+                  *d++ = response[i];
+               }
+
+             tmux_pane_feed(tp, buf, (size_t)(d - buf));
+             free(buf);
+          }
+        else
+          {
+             /* OOM fallback: feed raw, history colors may be wrong */
+             tmux_pane_feed(tp, response, resp_len);
+          }
+     }
+   else
+     WRN("no Tmux_Pane stored for pane %d during history capture", pane_id);
+}
+
+static void
+_tmux_capture_pane_cb(void *data, const char *response, int exit_code)
+{
+   Tmux_Capture_Data *cd = data;
+   Win *wn;
+   int pane_id;
+   Eina_Bool has_history;
+   Term *term;
+   Tmux_Pane *tp;
+   size_t resp_len;
+   size_t nl_count;
+   size_t i;
+   char *buf;
+   char *d;
+
+   if (!cd) return;
+   wn          = cd->wn;
+   pane_id     = cd->pane_id;
+   has_history = cd->has_history;
+   free(cd);
+
+   if (!wn) return;
+
    DBG("capture-pane callback for pane %d: response=%s exit_code=%d",
        pane_id, response ? "present" : "NULL", exit_code);
 
    if (!response || exit_code != 0)
      {
-        WRN("capture-pane failed for pane %d (exit_code=%d)", pane_id, exit_code);
+        if (exit_code == -1)
+          DBG("capture-pane cancelled for pane %d (teardown)", pane_id);
+        else
+          WRN("capture-pane failed for pane %d (exit_code=%d)", pane_id, exit_code);
+         return;
+      }
+
+   if (!response[0])
+     {
+        DBG("capture-pane: empty response for pane %d", pane_id);
         return;
      }
 
-   /* Strip trailing whitespace-only lines — capture-pane -N outputs the full
-    * pane height with trailing spaces preserved, so blank lines are now
-    * "          \n" rather than bare "\n".  Walk backwards, dropping each
-    * trailing line that contains only spaces/tabs. */
-   resp_len = strlen(response);
-   while (resp_len > 0)
-     {
-        size_t line_start;
-        size_t k;
-        Eina_Bool blank;
-
-        /* find the start of the last line */
-        if (response[resp_len - 1] == '\n')
-          line_start = (resp_len >= 2) ? resp_len - 1 : 0;
-        else
-          break;
-
-        /* scan backwards past the '\n' to find the previous '\n' or start */
-        for (line_start = resp_len - 1; line_start > 0; line_start--)
-          {
-             if (response[line_start - 1] == '\n')
-               break;
-          }
-
-        /* check if [line_start .. resp_len-1) is all whitespace + '\n' */
-        blank = EINA_TRUE;
-        for (k = line_start; k < resp_len - 1; k++)
-          {
-             if (response[k] != ' ' && response[k] != '\t')
-               {
-                  blank = EINA_FALSE;
-                  break;
-               }
-          }
-
-        if (!blank)
-          break;
-
-        resp_len = line_start;
-     }
+   resp_len = _tmux_strip_trailing_blanks(response, strlen(response));
+   if (resp_len == 0) return;
 
    term = eina_hash_find(wn->tmux_pane_to_term, &pane_id);
    if (!term)
@@ -8389,13 +8499,40 @@ _tmux_capture_pane_cb(void *data, const char *response, int exit_code)
    tp = evas_object_data_get(term->bg, "tmux_pane");
    if (tp)
      {
+        /* When history was fetched before this screen capture, the VT screen
+         * still contains the tail end of the history feed.  Clear the screen
+         * and home the cursor before displaying the live content.
+         *
+         * We use ESC[2J (Erase in Display — all) + ESC[H (Cursor Home)
+         * instead of ESC c (RIS) because RIS calls termpty_reset_state()
+         * which invokes termpty_clear_backlog(), destroying all the history
+         * rows we just populated in ty->back.  ESC[2J only clears the
+         * screen cells and resets circular_offset without touching the
+         * scrollback ring buffer.
+         *
+         * Skip when no history was requested to avoid unnecessary screen
+         * flash and to preserve any non-default VT state. */
+         if (has_history)
+           tmux_pane_feed(tp, "\033[2J\033[H",
+                          sizeof("\033[2J\033[H") - 1);
+
+         /* Strip the final '\n' when history was fetched — the last screen
+          * row must not end with a newline because that would scroll the
+          * display when the cursor is on the last terminal row, pushing
+          * one line of screen content into the backlog and shifting the
+          * visible viewport up by one.  Without history the backlog is
+          * empty, so the extra scroll is harmless and the newline is
+          * needed for correct content positioning. */
+         if (has_history && resp_len > 0 && response[resp_len - 1] == '\n')
+           resp_len--;
+
         nl_count = 0;
         for (i = 0; i < resp_len; i++)
           {
              if (response[i] == '\n') nl_count++;
           }
 
-         DBG("capture-pane feeding %zu bytes (%zu lines) to pane %d",
+        DBG("capture-pane feeding %zu bytes (%zu lines) to pane %d",
             resp_len, nl_count, pane_id);
 
         buf = malloc(resp_len + nl_count + 1);
@@ -8417,8 +8554,7 @@ _tmux_capture_pane_cb(void *data, const char *response, int exit_code)
              /* OOM fallback: feed raw, better than nothing */
              tmux_pane_feed(tp, response, resp_len);
           }
-
-      }
+     }
    else
      WRN("no Tmux_Pane stored for pane %d during capture", pane_id);
 }
@@ -8654,54 +8790,86 @@ _tmux_on_layout_apply(void *data, int window_id, Tmux_Layout_Node *root)
     * layout (initial connect / reconnect) — after that, live %output
     * streaming keeps pane content current. */
    if (first_layout)
-   {
-      Eina_Iterator *it;
-      void *hash_data;
+      {
+         Eina_Iterator *it;
+         void *hash_data;
+         int fetch_size;
 
-       DBG("Starting capture-pane for %d panes",
-           (int)eina_hash_population(wn->tmux_pane_to_term));
+         fetch_size = wn->config->tmux_scrollback_fetch_size;
 
-      it = eina_hash_iterator_data_new(wn->tmux_pane_to_term);
-      if (it)
-        {
-           while (eina_iterator_next(it, &hash_data))
-             {
-                Term *t = hash_data;
-                Tmux_Pane *tp;
-                Tmux_Capture_Data *cd;
-                int pid;
-
-                tp = evas_object_data_get(t->bg, "tmux_pane");
-                if (!tp) continue;
-                pid = tmux_pane_id_get(tp);
-                if (pid < 0) continue;
-
-                cd = calloc(1, sizeof(Tmux_Capture_Data));
-                if (!cd) continue;
-                cd->wn = wn;
-                cd->pane_id = pid;
+         DBG("Starting capture-pane for %d panes (history fetch_size=%d)",
+             (int)eina_hash_population(wn->tmux_pane_to_term), fetch_size);
 
+         it = eina_hash_iterator_data_new(wn->tmux_pane_to_term);
+         if (it)
+           {
+              while (eina_iterator_next(it, &hash_data))
                 {
+                   Term *t = hash_data;
+                   Tmux_Pane *tp;
+                   Tmux_Capture_Data *cd;
+                   Eina_Bool history_queued;
                    char cmd[128];
-                   snprintf(cmd, sizeof(cmd),
-                             "capture-pane -p -e -t %%%d", pid);
-                    DBG("Sending: %s", cmd);
-                    tmux_control_send_cmd(wn->tmux_ctrl, cmd,
-                                         _tmux_capture_pane_cb, cd);
-                }
-             }
-           eina_iterator_free(it);
+                   int pid;
 
-            {
-               char cmd[128];
-                snprintf(cmd, sizeof(cmd),
-                         /* Change Set 6b: -s scopes to current session only, not all. */
-                         "list-panes -s -F '#{pane_id} #{cursor_x} #{cursor_y}'");
-               tmux_control_send_cmd(wn->tmux_ctrl, cmd,
-                                     _tmux_cursor_list_cb, wn);
-            }
-         }
-    }
+                   tp = evas_object_data_get(t->bg, "tmux_pane");
+                   if (!tp) continue;
+                   pid = tmux_pane_id_get(tp);
+                   if (pid < 0) continue;
+
+                   /* Step 1 (optional): history capture — lines -fetch_size
+                    * to -1.  Queued before the screen capture so it is
+                    * processed first.  _tmux_history_pane_cb feeds content
+                    * through the VT parser; lines scroll off the top and
+                    * populate ty->back automatically. */
+                   history_queued = EINA_FALSE;
+                   if (fetch_size > 0)
+                     {
+                        cd = calloc(1, sizeof(Tmux_Capture_Data));
+                        if (cd)
+                          {
+                             cd->wn = wn;
+                             cd->pane_id = pid;
+                             snprintf(cmd, sizeof(cmd),
+                                      "capture-pane -p -e -S -%d -E -1 -t %%%d",
+                                      fetch_size, pid);
+                             DBG("Sending history: %s", cmd);
+                             if (tmux_control_send_cmd(wn->tmux_ctrl, cmd,
+                                                       _tmux_history_pane_cb, cd))
+                               history_queued = EINA_TRUE;
+                             else
+                               free(cd);
+                          }
+                     }
+
+                   /* Step 2: visible screen capture — current viewport only.
+                    * _tmux_capture_pane_cb sends ESC[2J + ESC[H to clear
+                    * the screen (without destroying the backlog) before
+                    * filling with live content. */
+                   cd = calloc(1, sizeof(Tmux_Capture_Data));
+                   if (!cd) continue;
+                   cd->wn = wn;
+                   cd->pane_id = pid;
+                   cd->has_history = history_queued;
+                   snprintf(cmd, sizeof(cmd),
+                            "capture-pane -p -e -t %%%d", pid);
+                   DBG("Sending screen: %s", cmd);
+                   if (!tmux_control_send_cmd(wn->tmux_ctrl, cmd,
+                                              _tmux_capture_pane_cb, cd))
+                     free(cd);
+                }
+              eina_iterator_free(it);
+
+              /* Step 3: cursor position query — once for all panes. */
+              {
+                 char cmd[128];
+                 snprintf(cmd, sizeof(cmd),
+                          "list-panes -s -F '#{pane_id} #{cursor_x} #{cursor_y}'");
+                 tmux_control_send_cmd(wn->tmux_ctrl, cmd,
+                                       _tmux_cursor_list_cb, wn);
+              }
+           }
+      }
 
    DBG("tmux layout applied: window @%d", window_id);
    wn->tmux_layout_applying = EINA_FALSE;

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to