Skip to content

InputSource: expose isCanonicalEmpty for stdin-or-cwd routing#19

Merged
odrobnik merged 1 commit into
mainfrom
feat/input-source-canonical-empty
Jun 11, 2026
Merged

InputSource: expose isCanonicalEmpty for stdin-or-cwd routing#19
odrobnik merged 1 commit into
mainfrom
feat/input-source-canonical-empty

Conversation

@odrobnik

Copy link
Copy Markdown
Contributor

Commands that emulate tools with "read stdin OR walk the cwd" behaviour (ripgrep's no-path default) need to know whether the embedder attached real input. The host process's fd 0 can't answer that for an embedded shell — an iBash echo x | rg pat must read the pipe even though the app's fd 0 is a terminal or /dev/null. The binding can answer: shells bind the canonical .empty when no pipe or redirect feeds a command (SwiftBash does exactly this), anything else was attached on purpose.

isCanonicalEmpty is identity-based on the shared cursor, so copies of .empty stay canonical while a manually built zero-byte stream counts as attached-but-empty — matching bash, where true | cmd hands cmd a real (empty) pipe that it reads to EOF.

Wanted by Cocoanetics/SwiftPorts#71 (rg no-path routing for SwiftPorts#65): its review flagged that sampling fd 0 regresses the in-process InputSource contract; with this seam the routing comes from the binding itself. SwiftPorts pins branch: main, so merging this unblocks that PR's follow-up commit.

59 tests pass (2 new).

🤖 Generated with Claude Code

Commands that emulate tools with 'read stdin OR walk the cwd'
behaviour (ripgrep's no-path default) must know whether the embedder
attached real input. The host process's fd 0 can't answer that for
an embedded shell — an iBash 'echo x | rg pat' must read the pipe
even though the app's fd 0 is a terminal or /dev/null. The binding
can answer: shells bind the canonical .empty when no pipe or
redirect feeds a command, anything else was attached on purpose.

Identity-based on the shared cursor, so copies stay canonical while
a manually built zero-byte stream counts as attached-but-empty —
matching bash, where 'true | cmd' hands cmd a real (empty) pipe.

Wanted by Cocoanetics/SwiftPorts#71 (rg no-path routing, issue
SwiftPorts#65) to honour the in-process InputSource contract flagged
in its review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
odrobnik added a commit to Cocoanetics/SwiftPorts that referenced this pull request Jun 11, 2026
Review feedback: defaulting the routing to the host process's fd 0
regressed the in-process entry point — an embedder that binds a real
InputSource while the app's fd 0 is /dev/null (GUI hosts, CI) or a
terminal would walk the cwd instead of reading the input it was
handed, unless it learned to pass the new boolean.

The default is now context-derived (stdinIsReadable: Bool? = nil):
standalone — Shell.current is the process-default shell — keeps real
rg's is_readable_stdin() against fd 0; an embedded shell answers
from the binding itself via ShellKit's new
InputSource.isCanonicalEmpty — .empty means the shell attached
nothing (walk), anything else was bound on purpose (read). That also
fixes the embedded pipeline path on terminal hosts, where fd-0
sampling ignored a bound pipe even before this PR.

Requires Cocoanetics/ShellKit#19 (the pin follows ShellKit main —
merge that first).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@odrobnik odrobnik merged commit 9426231 into main Jun 11, 2026
6 checks passed
@odrobnik odrobnik deleted the feat/input-source-canonical-empty branch June 11, 2026 21:31
odrobnik added a commit to Cocoanetics/SwiftPorts that referenced this pull request Jun 11, 2026
* rg: walk the cwd unless stdin is actually readable input

`rg PATTERN` with no path argument read (empty) stdin and reported no
matches whenever fd 0 wasn't a TTY — a GUI host or CI harness hands
its children /dev/null, which is neither a terminal nor readable
input, and real rg walks the cwd there. Mirror upstream's
is_readable_stdin(): consume stdin only when it's a regular file,
FIFO, or socket (new TTY.isStdinReadable; GetFileType disk/pipe on
Windows), walk the cwd otherwise. Detection errors mean 'walk', which
also keeps a closed fd 0 out of the stdin-reading path.

The decision threads through RgExecutable.run as a defaulted
parameter so the tests pin both routes deterministically instead of
inheriting whatever the test harness bound to fd 0.

Also align the no-path display shape with real rg: the default walk
prints bare relative paths (a.txt, not ./a.txt) — root display "."
instead of "./", the same shape fd's default already uses.

Fixes #65

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* rg: derive the no-path stdin routing from the binding when embedded

Review feedback: defaulting the routing to the host process's fd 0
regressed the in-process entry point — an embedder that binds a real
InputSource while the app's fd 0 is /dev/null (GUI hosts, CI) or a
terminal would walk the cwd instead of reading the input it was
handed, unless it learned to pass the new boolean.

The default is now context-derived (stdinIsReadable: Bool? = nil):
standalone — Shell.current is the process-default shell — keeps real
rg's is_readable_stdin() against fd 0; an embedded shell answers
from the binding itself via ShellKit's new
InputSource.isCanonicalEmpty — .empty means the shell attached
nothing (walk), anything else was bound on purpose (read). That also
fixes the embedded pipeline path on terminal hosts, where fd-0
sampling ignored a bound pipe even before this PR.

Requires Cocoanetics/ShellKit#19 (the pin follows ShellKit main —
merge that first).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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