Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hyperlink support to fd #1571

Merged
merged 5 commits into from
Jul 16, 2024
Merged

Add hyperlink support to fd #1571

merged 5 commits into from
Jul 16, 2024

Conversation

tmccombs
Copy link
Collaborator

@tmccombs tmccombs commented Jun 8, 2024

Fixes: #1295
Fixes: #1563

@sharkdp
Copy link
Owner

sharkdp commented Jun 9, 2024

Thank you! Should we maybe make this an option instead of a flag, to avoid future breaking changes in case we see the need for e.g. --color=never --hyperlink=always or potentially other ways of printing hyperlinks?

@097115
Copy link

097115 commented Jun 9, 2024

Something wrong happens if a filename contains non-latin characters.

The problem, is I based the code on the implementation in ripgrep. But
while ripgrep is writing directly to the stream, I am using a Formatter,
which means I have to write characters, not raw bytes.

Thus we need to percent encode all non-ascii bytes (or we could switch
to writing bytes directly, but that would be more complicated, and I
think percent encoding is safer anyway).
@097115
Copy link

097115 commented Jun 10, 2024

@tmccombs This latest commit seems to fix the bug with non-latin chars, thanks :)

The value can be auto, always, or never.

Currently, the default is "never", and if the option is used without an argument "auto" is
used. In the future this may be changed to "auto" and "always".
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although I love OSC8 as well (thank you for the PR ❤️ ), I'm not sure they're so mainstream that we should go in that direction :/

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My question is how do terminals that don't support it handle the sequence? If most terminals silently ignore unrecognized OSC sequences, then it probably doesn't hurt to emit it even if it isn't supported. We'll need to test that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weston-terminal at least simply ignores the OSC 8 sequence.

Copy link

@BuonOmo BuonOmo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, LGTM!

The print separator change is quite nice as well, I hope it won't make too noise for the PR to be unchecked though :/ (please add links!)


impl fmt::Display for PathUrl {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "file://{}", host())?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need the hostname? Does file:///home/user/... not work?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does. As long as fd is running on the same host as the terminal. Including the hostname is useful if you run the command over ssh or in a container or VM.

See https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda#file-uris-and-the-hostname

src/hyperlink.rs Outdated
#[cfg(windows)]
b'\\' => f.write_char('/'),
_ => {
write!(f, "%{:X}", byte)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RFC 3986 says that the byte 1 should become %01 not %1. But to avoid having to know that, maybe we could just use https://crates.io/crates/urlencoding?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortanetely, I don't think that urlencoding would work, because it would percent encode "/", which isn't what we want.

When the first digit is 0
Copy link
Owner

@sharkdp sharkdp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much. Looks great.

@tmccombs tmccombs merged commit cfced97 into sharkdp:master Jul 16, 2024
18 checks passed
@tmccombs tmccombs deleted the hyperlink branch July 16, 2024 21:57
@dandavison
Copy link

dandavison commented Jul 22, 2024

Hi, this is a great development, but I think that it would be very valuable to allow more customization of the format of the hyperlink. For example, in my case I'd like to be able to specify the protocol (e.g. vscode://file/... instead of file://). I think that we could re-use a lot of the design work (and some code maybe?) that was done for ripgrep in BurntSushi/ripgrep#2483.

Here's the current full text of the entry for --hyperlink-format=FORMAT in rg --help:

`rg --help` output
    --hyperlink-format=FORMAT
        Set the format of hyperlinks to use when printing results. Hyperlinks
        make certain elements of ripgrep's output, such as file paths,
        clickable. This generally only works in terminal emulators that support
        OSC-8 hyperlinks. For example, the format file://{host}{path} will emit
        an RFC 8089 hyperlink. To see the format that ripgrep is using, pass
        the --debug flag.
    Alternatively, a format string may correspond to one of the following
    aliases: default, none, file, grep+, kitty, macvim, textmate, vscode,
    vscode-insiders, vscodium. The alias will be replaced with a format
    string that is intended to work for the corresponding application.

    The following variables are available in the format string:

    {path}: Required. This is replaced with a path to a matching file. The
    path is guaranteed to be absolute and percent encoded such that it is
    valid to put into a URI. Note that a path is guaranteed to start with a
    /.

    {host}: Optional. This is replaced with your system's hostname. On
    Unix, this corresponds to calling gethostname. On Windows, this
    corresponds to calling GetComputerNameExW to fetch the system's
    "physical DNS hostname." Alternatively, if --hostname-bin was provided,
    then the hostname returned from the output of that program will be
    returned. If no hostname could be found, then this variable is replaced
    with the empty string.

    {line}: Optional. If appropriate, this is replaced with the line number
    of a match. If no line number is available (for example, if
    --no-line-number was given), then it is automatically replaced with the
    value 1.

    {column}: Optional, but requires the presence of {line}. If
    appropriate, this is replaced with the column number of a match. If no
    column number is available (for example, if --no-column was given),
    then it is automatically replaced with the value 1.

    {wslprefix}: Optional. This is a special value that is set to
    wsl$/WSL_DISTRO_NAME, where WSL_DISTRO_NAME corresponds to the value of
    the equivalent environment variable. If the system is not Unix or if
    the WSL_DISTRO_NAME environment variable is not set, then this is
    replaced with the empty string.

    A format string may be empty. An empty format string is equivalent to
    the none alias. In this case, hyperlinks will be disabled.

    At present, ripgrep does not enable hyperlinks by default. Users must
    opt into them. If you aren't sure what format to use, try default.

    Like colors, when ripgrep detects that stdout is not connected to a
    tty, then hyperlinks are automatically disabled, regardless of the
    value of this flag. Users can pass --color=always to forcefully emit
    hyperlinks.

    Note that hyperlinks are only written when a path is also in the output                                                                                                                    11:55:48 [409/3626]
    and colors are enabled. To write hyperlinks without colors, you'll need
    to configure ripgrep to not colorize anything without actually
    disabling all ANSI escape codes completely:

        --colors 'path:none' \
        --colors 'line:none' \
        --colors 'column:none' \
        --colors 'match:none'

    ripgrep works this way because it treats the --color flag as a proxy
    for whether ANSI escape codes should be used at all. This means that
    environment variables like NO_COLOR=1 and TERM=dumb not only disable
    colors, but hyperlinks as well. Similarly, colors and hyperlinks are
    disabled when ripgrep is not writing to a tty. (Unless one forces the
    issue by setting --color=always.)

    If you're searching a file directly, for example:

        rg foo path/to/file

    then hyperlinks will not be emitted since the path given does not
    appear in the output. To make the path appear, and thus also a
    hyperlink, use the -H/--with-filename flag.

    For more information on hyperlinks in terminal emulators, see:
    https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda        

@tmccombs
Copy link
Collaborator Author

It's sort of possible to do that today using the --format option. That is, you could do something like:

fd --absolute-path --format $'\e]8;;vscode://file/{}\e\\{}\e]8;;\e\\' ...

I'm not completely opposed to implement multiple formats, but I didn't want to add that level of complexity in the initial implementation.

@dandavison
Copy link

Thanks @tmccombs, I've moved this discussion to the issue: #1563

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
6 participants