Support custom pagers and passphrase prompts on Windows - #5740
Merged
Merged
Conversation
This was referenced Jun 30, 2026
stefanhaller
force-pushed
the
windows-pty
branch
2 times, most recently
from
July 2, 2026 09:24
8a41897 to
47bbc2f
Compare
The env var was previously set only on Windows, where the no-op pty stub was just running the command without a pty and needed to expose the width to pager scripts another way. With ConPTY coming to Windows the rationale disappears there, but the env var is documented in docs/Custom_Pagers.md for pager scripts that can't query the terminal width directly. Set it on every platform so those scripts remain portable, regardless of whether a pty is in play. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Windows ConPTY can't attach a child process to a pseudoconsole via os/exec — Go's stdlib doesn't expose PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE (golang/go#62708). The ConPTY path has to call CreateProcess directly, so it can't hand an *exec.Cmd back to the task runner. Widen NewCmdTask to accept a small Cmd interface satisfied by both *exec.Cmd (via the ExecCmd adapter) and the Windows ConPTY command type we're about to add. Change TerminateProcessGracefully to take *os.Process, which both cmd shapes can provide. Behavior is unchanged on every platform. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the pty master behind a small interface (Read/Write/Close/Resize), and push the actual startup into a platform-specific StartPty function in pkg/commands/oscommands. The Unix implementation still uses creack/pty; the Windows implementation is a stub that returns ErrPtyUnsupported, at which point newPtyTask falls back to a plain cmd task — matching the existing Windows behavior. The primitive lives in oscommands rather than pkg/gui because the cmd_obj_runner pty handler (also in oscommands) is going to consume it too, and tasks → oscommands is the existing dependency direction. Same observable behavior on every platform; this just carves out a seam for a real ConPTY implementation on Windows. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the StartPty stub with a real ConPTY implementation: CreatePipe + CreatePseudoConsole + PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE + CreateProcess. Pagers and external diff tools now get real terminal behavior instead of being handed pipes. One Windows-specific quirk worth flagging: ConPTY does not EOF the output pipe when the child exits; conhost keeps it alive until ClosePseudoConsole is called explicitly. A background waiter goroutine calls ClosePseudoConsole as soon as proc.Wait returns, so callers see EOF on outRead — restoring the Unix master-fd-EOFs-when-slave-closes semantics they depend on. The ErrPtyUnsupported sentinel and the no-pty fallback in newPtyTask are gone now that both platforms have a real implementation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The per-platform getCmdHandlerPty split existed because the Unix side had creack/pty and the Windows side had nothing — so it fell back to a non-pty handler. Now that oscommands.StartPty provides a pty on both platforms, the two files collapse into one cross-platform implementation and the stub is gone. cmdHandler grows a 'wait' field because the pty path on Windows spawns via CreateProcess and never runs exec.Cmd.Start — so cmd.Wait wouldn't work there. Non-pty handlers set wait = cmd.Wait; pty handlers set it to the wait closure StartPty returns. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ConPTY presents its child's stdout as a screen buffer and uses CUP (`\x1b[<row>;<col>H`) to skip over blank rows rather than emitting LFs for them. Our escape interpreter swallows CUP via the catch-all "valid CSI final byte we don't implement" branch, so the blank rows the child put between non-blank ones disappear and the surrounding lines collapse together — which is what makes the delta-rendered diff in the screenshot look like its blank lines and section breaks were removed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ConPTY presents its child's output as a screen buffer and uses CUP / CUD / CNL / VPA to skip over blank rows rather than emitting LFs. The previous behaviour swallowed all of those and the visible content collapsed together. Now the escape parser tracks the screen-relative cursor row, and any CSI that moves the cursor past the current row emits a cursorDown instruction that the view turns into the matching number of empty lines. Column tracking is deliberately omitted: doing it correctly would mean duplicating the view's grapheme-cluster width math in the parser, and ConPTY in practice positions to column 1 after a CR-equivalent, which the existing wx-reset path already handles. ConPTY-internal scrolling needs no special handling either: it only emits cursor-positioning escapes within the first, un-scrolled screenful — once its screen scrolls it switches to plain linefeeds, which the view advances on directly regardless of the tracked cursor. Backward cursor moves are silently dropped — the view's buffer is append-style and can't undo earlier writes. The exception is cursor-home (CUP to row 1): ConPTY emits it at the start of every screen, so rather than drop it we re-anchor the row tracking to the current write position. Without that, a view not rewound in lockstep with ConPTY's screen (the command log, which streams pty output without a rewind) accumulates drift, and every later absolute CUP becomes a dropped backward move that collapses the rows ConPTY positioned with. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ConPTY compresses runs of default-colored spaces into ECH + CUF (\x1b[NX\x1b[NC) rather than emitting them literally. Both currently fall through the parser's swallow path, so the gap they describe collapses entirely and content that the child wrote with leading indentation ends up slid left against the previous cell. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ConPTY compresses runs of default-colored spaces into ECH + CUF (\x1b[NX\x1b[NC) instead of emitting them literally. ECH is still a no-op for us — our buffer is built sequentially and has nothing to erase — but CUF has to materialize as N visible space cells so the gap actually appears, otherwise content the child wrote with leading indentation slides left against the preceding cell. The view's cursorForward branch reuses the same machinery as tab expansion: substitute the trigger byte for a space and let the repeatCount path emit the cells under the parser-tracked SGR. The existing notifyCellsWritten plumbing then advances screenCol over the gap, keeping subsequent CUP targets aligned. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
stefanhaller
enabled auto-merge
July 3, 2026 16:47
tmeijn
pushed a commit
to tmeijn/dotfiles
that referenced
this pull request
Jul 9, 2026
This MR contains the following updates: | Package | Update | Change | |---|---|---| | [jesseduffield/lazygit](https://github.com/jesseduffield/lazygit) | minor | `v0.62.2` → `v0.63.0` | MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot). **Proposed changes to behavior should be submitted there as MRs.** --- ### Release Notes <details> <summary>jesseduffield/lazygit (jesseduffield/lazygit)</summary> ### [`v0.63.0`](https://github.com/jesseduffield/lazygit/releases/tag/v0.63.0) [Compare Source](jesseduffield/lazygit@v0.62.2...v0.63.0) <!-- Release notes generated using configuration in .github/release.yml at v0.63.0 --> #### What's Changed ##### Enhancements 🔥 - Add direnv support by [@​stefanhaller](https://github.com/stefanhaller) in [#​5660](jesseduffield/lazygit#5660) - Improve cycling through multiple pagers by [@​stefanhaller](https://github.com/stefanhaller) in [#​5678](jesseduffield/lazygit#5678) - Detect external repo changes via background polling by [@​stefanhaller](https://github.com/stefanhaller) in [#​5662](jesseduffield/lazygit#5662) - Make the side panels configurable by [@​stefanhaller](https://github.com/stefanhaller) in [#​5702](jesseduffield/lazygit#5702) - Add a global keybinding for editing the config file by [@​stefanhaller](https://github.com/stefanhaller) in [#​5728](jesseduffield/lazygit#5728) - Improve resolving non-textual and submodule merge conflicts by [@​stefanhaller](https://github.com/stefanhaller) in [#​5735](jesseduffield/lazygit#5735) - Auto-dismiss the continue-rebase prompt when it becomes stale by [@​stefanhaller](https://github.com/stefanhaller) in [#​5758](jesseduffield/lazygit#5758) - Support custom pagers and passphrase prompts on Windows by [@​stefanhaller](https://github.com/stefanhaller) in [#​5740](jesseduffield/lazygit#5740) - Make creating worktrees simpler and less error-prone by [@​stefanhaller](https://github.com/stefanhaller) in [#​5741](jesseduffield/lazygit#5741) - Improve deleting worktrees and their branches by [@​stefanhaller](https://github.com/stefanhaller) in [#​5748](jesseduffield/lazygit#5748) - Allow overriding the platform used for default keybindings by [@​stefanhaller](https://github.com/stefanhaller) in [#​5671](jesseduffield/lazygit#5671) - Add `gui.shrinkSidePanelsToContent` option by [@​stefanhaller](https://github.com/stefanhaller) in [#​5754](jesseduffield/lazygit#5754) - Show renamed files in the custom patch builder by [@​stefanhaller](https://github.com/stefanhaller) in [#​5759](jesseduffield/lazygit#5759) ##### Fixes 🔧 - Fix unstaging a submodule with dirty content by [@​stefanhaller](https://github.com/stefanhaller) in [#​5666](jesseduffield/lazygit#5666) - Fix coloring of wrapped delta lines by [@​stefanhaller](https://github.com/stefanhaller) in [#​5711](jesseduffield/lazygit#5711) - Fix Files Panel artefacts during rebase commands by [@​stefanhaller](https://github.com/stefanhaller) in [#​5661](jesseduffield/lazygit#5661) - Keep selected commits stable across refreshes by [@​stefanhaller](https://github.com/stefanhaller) in [#​5717](jesseduffield/lazygit#5717) - Fix quoting of shell commands on Windows by [@​stefanhaller](https://github.com/stefanhaller) in [#​5704](jesseduffield/lazygit#5704) - Silently consume unrecognized or malformed escape sequences by [@​stefanhaller](https://github.com/stefanhaller) in [#​5738](jesseduffield/lazygit#5738) - Don't include common ancestor when picking "both" for a conflict in diff3 style by [@​stefanhaller](https://github.com/stefanhaller) in [#​5747](jesseduffield/lazygit#5747) ##### Maintenance ⚙️ - Some fixes to our infrastructure by [@​stefanhaller](https://github.com/stefanhaller) in [#​5705](jesseduffield/lazygit#5705) - Restructure the `just` recipes for running integration tests by [@​stefanhaller](https://github.com/stefanhaller) in [#​5720](jesseduffield/lazygit#5720) - Fix flaky TestNewCmdTaskInstantStop test by [@​stefanhaller](https://github.com/stefanhaller) in [#​5743](jesseduffield/lazygit#5743) - Bump golang.org/x/sync from 0.20.0 to 0.21.0 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​5684](jesseduffield/lazygit#5684) - Bump golang.org/x/sys from 0.45.0 to 0.46.0 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​5685](jesseduffield/lazygit#5685) - Bump github.com/sahilm/fuzzy from 0.1.2 to 0.1.3 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​5706](jesseduffield/lazygit#5706) - Bump actions/cache from 5 to 6 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​5722](jesseduffield/lazygit#5722) - Bump actions/checkout from 6 to 7 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​5723](jesseduffield/lazygit#5723) - Bump goreleaser/goreleaser-action from 7.2.2 to 7.2.3 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​5745](jesseduffield/lazygit#5745) - Bump golangci/golangci-lint-action from 9.2.0 to 9.3.0 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​5746](jesseduffield/lazygit#5746) - Bump golang.org/x/net from 0.47.0 to 0.55.0 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​5752](jesseduffield/lazygit#5752) - Pin gofumpt version to 0.9.2 by [@​stefanhaller](https://github.com/stefanhaller) in [#​5753](jesseduffield/lazygit#5753) - Fix a few flaky tests by [@​stefanhaller](https://github.com/stefanhaller) in [#​5756](jesseduffield/lazygit#5756) ##### I18n 🌎 - Update translations from Crowdin by [@​stefanhaller](https://github.com/stefanhaller) in [#​5760](jesseduffield/lazygit#5760) ##### Performance Improvements 📊 - Prevent staging from becoming slower over time by [@​stefanhaller](https://github.com/stefanhaller) in [#​5712](jesseduffield/lazygit#5712) **Full Changelog**: <jesseduffield/lazygit@v0.62.2...v0.63.0> </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this MR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box --- This MR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNTYuMCIsInVwZGF0ZWRJblZlciI6IjQzLjI1Ni4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiLCJhdXRvbWF0aW9uOmJvdC1hdXRob3JlZCIsImRlcGVuZGVuY3ktdHlwZTo6bWlub3IiXX0=-->
social4hyq
pushed a commit
to social4hyq/homebrew-core
that referenced
this pull request
Sep 20, 2026
lazygit 0.63.0 Created-by: HarmonybrewBot Commit-by: HarmonybrewBot Merged-by: HarmonybrewBot Description: Created by `brew bump` --- Created with `brew bump-formula-pr`.<details> <summary>release notes</summary> <pre><!-- Release notes generated using configuration in .github/release.yml at v0.63.0 --> ## What's Changed ### Enhancements 🔥 * Add direnv support by @stefanhaller in jesseduffield/lazygit#5660 * Improve cycling through multiple pagers by @stefanhaller in jesseduffield/lazygit#5678 * Detect external repo changes via background polling by @stefanhaller in jesseduffield/lazygit#5662 * Make the side panels configurable by @stefanhaller in jesseduffield/lazygit#5702 * Add a global keybinding for editing the config file by @stefanhaller in jesseduffield/lazygit#5728 * Improve resolving non-textual and submodule merge conflicts by @stefanhaller in jesseduffield/lazygit#5735 * Auto-dismiss the continue-rebase prompt when it becomes stale by @stefanhaller in jesseduffield/lazygit#5758 * Support custom pagers and passphrase prompts on Windows by @stefanhaller in jesseduffield/lazygit#5740 * Make creating worktrees simpler and less error-prone by @stefanhaller in jesseduffield/lazygit#5741 * Improve deleting worktrees and their branches by @stefanhaller in jesseduffield/lazygit#5748 * Allow overriding the platform used for default keybindings by @stefanhaller in jesseduffield/lazygit#5671 * Add `gui.shrinkSidePanelsToContent` option by @stefanhaller in jesseduffield/lazygit#5754 * Show renamed files in the custom patch builder by @stefanhaller in jesseduffield/lazygit#5759 ### Fixes 🔧 * Fix unstaging a submodule with dirty content by @stefanhaller in jesseduffield/lazygit#5666 * Fix coloring of wrapped delta lines by @stefanhaller in jesseduffield/lazygit#5711 * Fix Files Panel artefacts during rebase commands by @stefanhaller in jesseduffield/lazygit#5661 * Keep selected commits stable across refreshes by @stefanhaller in jesseduffield/lazygit#5717 * Fix quoting of shell commands on Windows by @stefanhaller in jesseduffield/lazygit#5704 * Silently consume unrecognized or malformed escape sequences by @stefanhaller in jesseduffield/lazygit#5738 * Don't include common ancestor when picking "both" for a conflict in diff3 style by @stefanhaller in jesseduffield/lazygit#5747 ### Maintenance ⚙️ * Some fixes to our infrastructure by @stefanhaller in jesseduffield/lazygit#5705 * Restructure the `just` recipes for running integration tests by @stefanhaller in jesseduffield/lazygit#5720 * Fix flaky TestNewCmdTaskInstantStop test by @stefanhaller in jesseduffield/lazygit#5743 * Bump golang.org/x/sync from 0.20.0 to 0.21.0 by @dependabot[bot] in jesseduffield/lazygit#5684 * Bump golang.org/x/sys from 0.45.0 to 0.46.0 by @dependabot[bot] in jesseduffield/lazygit#5685 * Bump github.com/sahilm/fuzzy from 0.1.2 to 0.1.3 by @dependabot[bot] in jesseduffield/lazygit#5706 * Bump actions/cache from 5 to 6 by @dependabot[bot] in jesseduffield/lazygit#5722 * Bump actions/checkout from 6 to 7 by @dependabot[bot] in jesseduffield/lazygit#5723 * Bump goreleaser/goreleaser-action from 7.2.2 to 7.2.3 by @dependabot[bot] in jesseduffield/lazygit#5745 * Bump golangci/golangci-lint-action from 9.2.0 to 9.3.0 by @dependabot[bot] in jesseduffield/lazygit#5746 * Bump golang.org/x/net from 0.47.0 to 0.55.0 by @dependabot[bot] in jesseduffield/lazygit#5752 * Pin gofumpt version to 0.9.2 by @stefanhaller in jesseduffield/lazygit#5753 * Fix a few flaky tests by @stefanhaller in jesseduffield/lazygit#5756 ### I18n 🌎 * Update translations from Crowdin by @stefanhaller in jesseduffield/lazygit#5760 ### Performance Improvements 📊 * Prevent staging from becoming slower over time by @stefanhaller in jesseduffield/lazygit#5712 **Full Changelog**: jesseduffield/lazygit@v0.62.2...v0.63.0 </pre> <p>View the full release notes at <a href="https://github.com/jesseduffield/lazygit/releases/tag/v0.63.0">https://github.com/jesseduffield/lazygit/releases/tag/v0.63.0</a>.</p> </details> <hr> See merge request: Harmonybrew/homebrew-core!13513
stefanhaller
added a commit
that referenced
this pull request
Sep 25, 2026
In #5740 we implemented PTY support for Windows; back then we thought this is a prerequisite for supporting custom diff renderers (which were still called "custom pagers" back then), because git will only use the GIT_PAGER env var when it is running in a PTY. The problem is that the Windows PTY, being based on ConPTY, does not behave like a Unix PTY, which basically just passes through all data from the client. ConPTY renders what it receives from the client into its own screen buffer, and then re-encodes it from there for the terminal side. This has already caused problems that are awkward to work around (e.g. ConPTY will convert a series of multiple blank lines to a cursor positioning escape sequence, so we need to parse that and convert it back, see 180fe0c); but now, with the upcoming OSC 1717 work, it turns out that it's impossible to attach OSC 1717 metadata records to the cells they belong to, because ConPTY sends those immediately to the terminal, but the rest of the cell data some time later, and it's impossible to reconstruct the original stream. So use an ordinary pipe on Windows, where we start git and the diff renderer on our side instead of telling git to drive the renderer. It's a shame that we didn't realize it's possible; we could have done this years ago without having to wait for a working Windows PTY. One downside is that the diff renderer can no longer ask the terminal how wide it is, so if it needs to know that (e.g. for a side-by-side diff, or for horizontal lines that should be as wide as the view), then it needs another way to find out. We set the `COLUMNS` environment variable, which delta, difftastic and diff-so-fancy all support in their latest versions, and for those renderers that don't, we provide a `{{width}}` template variable that can be used in a diff renderer command to pass it as a command-line argument.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Custom pagers were not available on Windows because the feature depends on a working PTY implementation, and the PTY library we are using doesn't have support for Windows (see creack/pty#155). This PR adds this support on our side; the same PTY library is still used on Mac and Linux, but on Windows we now have our own implementation using ConPTY.
This also enables prompting for SSH passphrases for users who don't have an ssh-agent running, which previously also didn't work on Windows. There's one limitation here: pressing
fin the Files panel meansfetch --allby default (unless you turn that off usinggit.fetchAll: false), in which case passphrase prompting still doesn't work; the reason is that git spawns a bunch of child processes for each remote to be fetched, and on Windows these don't inherit the PTY like they do on Mac and Linux.Fixes #1453
Fixes #5525