Repository navigation
feat(hook): let programs that run commands keep the terminal - #53
Merged
Merged
Conversation
__cdg is dot-sourced into the global scope at the prompt, so its local $__cdgLine was the global one and its cleanup removed it before __cdgRestoreHistory could read it. Fixes #51.
…T_RUNNERS, __cdg_run) A runner such as hanfix's hf lists its names in CODIGEST_RUNNERS, so its own line is not captured, and hands each command line it runs to __cdg_run, which applies the hook's rules to that line. The PowerShell hook also calls a PSConsoleHostReadLine defined before or after it (VS Code's shell integration, atuin) instead of replacing it. Refs #52.
This was referenced Oct 28, 2026
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.
A program that runs command lines for you, such as
hffrom hanfix (it re-runs the previous command typed with the Korean layout on), was captured as a whole: the hook wrapped thehfline, so the commandhfran got the capture pipe instead of the terminal. An interactive one then fails, for examplecodex resume --lastwith "stdout is not a terminal". This adds a small contract so such programs keep the terminal and what they run is still captured by the hook's own rules.Contract
CODIGEST_RUNNERS: a runner appends its command names (PowerShell$global:CODIGEST_RUNNERS += 'hf'; bash/zshCODIGEST_RUNNERS="${CODIGEST_RUNNERS-} hf"). The hook does not capture a line that starts with one of them. It is only read when Enter is pressed, so it does not matter whether the runner or codigest loads first.__cdg_run <line>: defined by both hooks. It applies the same checks as for a typed line (skip list,CODIGEST_CAP_SKIP,CODIGEST_NOCAP, runners) and runs the line captured or plainly; plainly as well while a capture is already running. A runner looks it up when it runs a line, and usesInvoke-Expression/evalwhen it is not there. PowerShell callers dot-source it (. __cdg_run $line) so the line runs in their scope, asInvoke-Expressiondoes.Both are in the README (English and Korean) under "Programs that run other commands", with the thefuck line:
fuckcan be listed inCODIGEST_RUNNERS, so what it runs keeps the terminal and is not captured. hanfix will use the contract in its next release; with an older hanfix, or an older codigest, the behaviour is the same as now.Other changes
PSConsoleHostReadLinethat someone else defined. One defined before it (atuin) is called for the line; one defined after it (VS Code's shell integration, which saves the current function and wraps it) is detected on its first call, and the hook puts itself back on top around it. That other function then sees the line as typed: VS Code's command detection (OSC 633;E) used to get. __cdg { <line> }. A program loaded later that never calls the function it replaced (atuin reads the line itself) used to stop the capture for good; aPowerShell.OnIdlesubscription (-SupportEvent, so not inGet-Job) puts the hook back on top around it while the shell waits for input. The first line after such a program loads is not captured. Loading the hook again (. $PROFILE) does not chain it to itself (marker commentcodigest-readline).__cdg's local$__cdgLinewas the global one once dot-sourced at the prompt, and its cleanup removed it. Fixed in its own commit, fixes PowerShell hook: engine history keeps the wrapped line in an interactive session #51.bind -x) triggers the DEBUG trap, and VS Code evaluates the trap it chained only for that first event. The test for it is markedxfail(strict=True).Tests (
tests/test_hook_terminal.py)Each test types lines into an interactive shell on a pseudo-terminal (stdlib
ptyon Linux and macOS; ConPTY via pywinpty on Windows, skipped when pywinpty is missing). A probe program records whether its stdout is a terminal, and a fakecdgrecords whatcdg term ingestgets.__cdg_run, a line on the skip list keeps the terminal and is not captured, another one gets a pipe and is captured under that line with its output.-PredictionSource Historyon and no other read-line present.PSConsoleHostReadLinedefined after the hook that reads the line itself (as atuin's does) is called by the hook from the next prompt on; lines are captured again and the histories stay as typed. Fails without the OnIdle subscription.Other terminal programs (PowerShell, Windows, not in the suite)
Run with each program's release build in a scratch folder, all of its state pinned there, in both load orders (program then hook, hook then program). In all 8 cases a captured line gets a pipe, the runner and a skipped line keep the terminal, PSReadLine and engine history keep the lines as typed, and the program still does its job:
zoxide queryfinds the folderbash-preexec 0.6.0 with the bash hook: ok in both orders (Git Bash).
. __cdg { wrapped }).__cdg_runcapture started inside a function,jobsis empty and the next lines run (the process-substitution question from the__cdg_opencomment).The PowerShell tests set the PSReadLine history path to the test folder before the first prompt, so they neither read nor write the user's history.
local-ci: f96ead6 | pytest 296 passed, 1 skipped (rest of the suite, at fd48fc7; later commits touch only the hook and its terminal tests); terminal tests on Windows with ConPTY: pwsh 5 passed, Git Bash 5.3 2 passed + 1 xfailed (#52), zsh skipped (not installed); new atuin-style test fails with the OnIdle block disabled; matrix above; ruff 0.17.1 check and format; actionlint ci.yml; bash -n hook.sh; conventions.py check: 0 findings | zsh and pwsh on Linux and the Rust tests only in CI; real ssh, macOS terminals, atuin/starship/oh-my-posh/zoxide with bash or zsh not run