Skip to content

Fix: Blank screen after Resume() — tcell cell cache not invalidated - lazydocker warp | kitty terminals related#101

Open
AbrhamSayd wants to merge 2 commits intojesseduffield:masterfrom
AbrhamSayd:fix/resume-blank-screen-sync
Open

Fix: Blank screen after Resume() — tcell cell cache not invalidated - lazydocker warp | kitty terminals related#101
AbrhamSayd wants to merge 2 commits intojesseduffield:masterfrom
AbrhamSayd:fix/resume-blank-screen-sync

Conversation

@AbrhamSayd
Copy link

Context

This is a proposed fix for a blank screen issue reported in jesseduffield/lazydocker (#611) where pressing ctrl+e to exec into a container shell and then exiting causes lazydocker to show a blank, unresponsive screen. The issue has been reproduced on Warp and Kitty terminals.

The root cause appears to be in how Resume() re-engages the terminal after a subprocess exits.


Suspected Root Cause

Resume() delegates to g.screen.Resume(), which calls tcell's engage(). Inside engage(), the physical screen is cleared:

// gdamore/tcell/v2 — tscreen.go, engage()
t.TPuts(ti.EnterCA)  // enter alternate screen buffer
t.TPuts(ti.Clear)    // physically clears the screen
// no cells.Invalidate() ←
go t.inputLoop(stopQ)
go t.mainLoop(stopQ)

tcell tracks what it last wrote to the terminal in an internal cell cache. engage() clears the physical screen but never invalidates that cache. When gocui's flush() runs Screen.Show(), tcell diffs the draw buffer against the stale cache, finds nothing dirty, and writes nothing — the screen stays blank.

flush() has one invalidation path, but it only fires on a terminal resize:

// gui.go — flush()
if maxX != g.maxX || maxY != g.maxY {  // only on size change
    for _, v := range g.views {
        v.clearViewLines()
    }
}

If the terminal size didn't change during the subprocess (the common case), no invalidation happens and the stale cache persists.


Proposed Fix

Call g.screen.Sync() after g.screen.Resume(). Sync() invalidates the entire cache and forces a full redraw:

// gdamore/tcell/v2 — tscreen.go, Sync()
func (t *tScreen) Sync() {
    t.Lock()
    t.resize()
    t.clear = true
    t.cells.Invalidate()  // marks all cells dirty
    t.draw()              // immediately redraws
    t.Unlock()
}

Diff:

 func (g *Gui) Resume() error {
-	return g.screen.Resume()
+	if err := g.screen.Resume(); err != nil {
+		return err
+	}
+	// engage() clears the physical screen but leaves tcell's "last drawn"
+	// cache stale. Sync() invalidates it so the next Show() does a full redraw.
+	g.screen.Sync()
+	return nil
 }

This change was tested locally against the lazydocker reproduction case and resolved the blank screen. Testing was done on Warp (macOS), Warp (Windows), and WSL2 under Debian. Broader testing across other terminals and platforms is welcome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant