sharkdp/fd
Output
Active contributors: sharkdp, tmccombs, tavianator
Purpose
When fd is not running an external command, the receiver writes each match to stdout. The output subsystem is responsible for: choosing whether to colorise, splitting the path between styled "parent" and styled "basename", appending a trailing separator for directories, applying any --path-separator rewrite, wrapping in OSC 8 hyperlinks, applying a --format template, and ending each entry with either \n or \0.
Directory layout
src/
├── output.rs # print_entry, print_entry_format, _colorized, _uncolorized,
│ # print_trailing_slash, replace_path_separator
├── dir_entry.rs # DirEntry::stripped_path / .style / Colorable impl
└── hyperlink.rs # PathUrl percent-encoder, host() lookupKey abstractions
| Item | File | Purpose |
|---|---|---|
print_entry |
src/output.rs |
Top-level entry point used by ReceiverBuffer::print. Decides between format/colorized/uncolorized printers and wraps in a hyperlink if needed. |
print_entry_format |
src/output.rs |
Used when Config::format is set. Calls FormatTemplate::generate on the stripped path. |
print_entry_colorized |
src/output.rs |
Splits the path at the last component, styles the parent with Indicator::Directory, styles the basename with the entry-specific style, and writes both. |
print_entry_uncolorized |
src/output.rs |
The plain printer. On Unix without a path-separator rewrite, writes raw bytes to preserve invalid-UTF-8 filenames. |
print_trailing_slash |
src/output.rs |
Writes actual_path_separator after directories, optionally styled. |
PathUrl |
src/hyperlink.rs |
Display implementation that percent-encodes path bytes and produces a file://hostname/... URL. |
How a single entry is printed
graph TD
entry[DirEntry] --> stripped[entry.stripped_path]
stripped --> hyperlink_q{config.hyperlink?}
hyperlink_q -->|yes| osc_open[Write OSC 8 open<br/>'\x1B]8;;<url>\x1B\\']
hyperlink_q -->|no| pick
osc_open --> pick{which printer?}
pick -->|config.format set| fmt[print_entry_format]
pick -->|else, config.ls_colors| col[print_entry_colorized]
pick -->|else| plain[print_entry_uncolorized]
fmt --> trail[print_trailing_slash]
col --> trail
plain --> trail
trail --> hyperlink_close{had hyperlink?}
hyperlink_close -->|yes| osc_close[Write OSC 8 close<br/>'\x1B]8;;\x1B\\']
hyperlink_close -->|no| sep
osc_close --> sep[Separator: '\\n' or '\\0']The separator decision is made in print_entry:
if config.null_separator {
write!(stdout, "\0")
} else {
writeln!(stdout)
}-0/--print0 flips this. It is also passed through to --exec so per-result blocks are \0-terminated.
Path stripping
DirEntry::stripped_path(config) returns strip_current_dir(self.path()) when Config::strip_cwd_prefix is true. The CLI rule (in Opts::strip_cwd_prefix) is:
- No positional search path was given, and
--strip-cwd-prefixwas not explicitly set toNever, and either- It was explicitly set to
Always, or - The default rule applies:
!(null_separator || has_command)— i.e. plain interactive output, no--print0, no-x/-X/-l.
- It was explicitly set to
This mirrors what most users expect: piping or executing yields paths starting with ./, while a plain fd README shows README rather than ./README.
Color selection
The colorised printer splits the path so the parent segments are coloured by the directory indicator (Indicator::Directory from LsColors) while the last component carries its own style (looked up via lscolors::style_for_path_with_metadata against the cached Style). This is what gives fd its characteristic look where directory components are uniformly styled and only the file basename varies in color.
The actual style for the basename is computed once and cached on DirEntry::style (OnceCell<Option<Style>>). The walker calls entry.style(ls_colors) inside spawn_senders while it is still in worker threads so style computation is parallelised; the receiver only does the writes.
A subtle but important separator handling: print_entry_colorized walks the path string character by character starting at parent.to_string_lossy().len() and increments offset past every is_separator(c) it encounters. This guarantees that paths like dir/foo and dir//foo both split correctly between styled-parent and styled-basename.
Custom path separator
Config::path_separator (set by --path-separator) flows two ways:
- In
print_entry_uncolorized_baseand the colorized printer,replace_path_separator(path, sep)rewrites everyMAIN_SEPARATORto the user's separator. - In
print_trailing_slash, the rewrite is applied viaConfig::actual_path_separator(which is always populated, falling back toMAIN_SEPARATOR.to_string()).
On Windows there is also a default_path_separator (src/filesystem.rs) that returns Some("/") when the MSYSTEM env var is set, so MSYS2/Git Bash/Cygwin shells get forward slashes by default.
Trailing slashes for directories
print_trailing_slash writes Config::actual_path_separator after every directory entry, optionally with the directory style. This is why fd -td prints src/ rather than just src. The check is entry.file_type().is_some_and(|ft| ft.is_dir()).
OSC 8 hyperlinks
--hyperlink[=auto|always|never] controls Config::hyperlink. When true, print_entry brackets each output with the OSC 8 escape sequences:
\x1B]8;;<url>\x1B\\<text>\x1B]8;;\x1B\\The URL is constructed by PathUrl::new(path), which:
- Calls
absolute_path(path)fromsrc/filesystem.rs(handles Windows verbatim prefix stripping). - On Unix, looks up the hostname once via
nix::unistd::gethostname()and caches it in aOnceLock<String>. - Percent-encodes bytes that are not in the ASCII unreserved set +
/,:,-,.,_,~. - On Windows, converts
\to/rather than encoding it.
The fallback on Windows is host() returning "/", which yields URLs of the form file:///<encoded-path>.
The unit tests in src/hyperlink.rs cover the encoding (e.g., "$*\x1bßé/∫😃\x07" → "%24%2A%1B%C3%9F%C3%A9/%E2%88%AB%F0%9F%98%83%07").
The Auto mode of --hyperlink resolves to whatever colored_output was decided as. Many terminals implement OSC 8 only in colored output anyway, so this matches user expectations.
--format
When Config::format is set, the colorised path layout is bypassed and FormatTemplate::generate produces a single OsString from the stripped path, which is written via to_string_lossy(). The trailing slash, hyperlink wrapping, and separator choice still apply on top.
Performance notes
src/output.rs carries four // TODO: this function is performance critical and can probably be optimized comments above print_entry, print_entry_format, print_entry_colorized, and print_entry_uncolorized_base. The relevant micro-optimisations already in place:
- The output is wrapped in
io::BufWriter::new(io::stdout().lock())(set up inWorkerState::receive). - Style is precomputed in worker threads, not in the printer.
- On Unix without a separator rewrite,
print_entry_uncolorizedwrites raw bytes viawrite_all(stripped.as_os_str().as_bytes()), sidesteppingto_string_lossy()and preserving non-UTF-8 filenames.
Entry points for modification
- Change directory-trailing-slash behaviour. Edit
print_trailing_slash. The check is currently a strictis_dir()— symlinks to directories are not given a trailing slash today. - Change the hyperlink URL format. Edit
PathUrl'sDisplayimpl insrc/hyperlink.rs. - Add a new top-level decoration (e.g., a prefix per result). Edit
print_entry. Be mindful that--formatand--execpaths flow through the same function.
Related pages
- Walker — the producer side of the path stream that ends up here.
- Format templates — the engine used by
--format. - CLI parsing — where color/hyperlink/strip-cwd decisions are made.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.