Repository: BurntSushi/ripgrep
File: crates/printer/src/hyperlink.rs (Rust)

Code before:
                | b'a'..=b'z'
                | b'/'
                | b':'
                | b'-'
                | b'.'
                | b'_'
                | b'~'
                | 128.. => {
                    result.push(byte);
                }
                #[cfg(windows)]
                b'\\' => {
                    result.push(b'/');
                }
                _ => {
                    const HEX: &[u8] = b"0123456789ABCDEF";
                    result.push(b'%');
                    result.push(HEX[(byte >> 4) as usize]);
                    result.push(HEX[(byte & 0xF) as usize]);
                }
            }
        }
        HyperlinkPath(result)
    }
    #[cfg(target_os = "wasi")]
    pub(crate) fn from_path(original_path: &Path) -> Option<HyperlinkPath> {
        use std::os::wasi::ffi::OsStrExt;

        // We canonicalize the path in order to get an absolute version of it
        // without any `.` or `..` or superfluous separators. Unfortunately,
        // this does also remove symlinks, and in theory, it would be nice to
        // retain them. Perhaps even simpler, we could just join the current
        // working directory with the path and be done with it. There was
        // some discussion about this on PR#2483, and there generally appears
        // to be some uncertainty about the extent to which hyperlinks with
        // things like `..` in them actually work. So for now, we do the safest
        // thing possible even though I think it can result in worse user
        // experience. (Because it means the path you click on and the actual
        // path that gets followed are different, even though they ostensibly
        // refer to the same file.)
        //
        // There's also the potential issue that path canonicalization is
        // expensive since it can touch the file system. That is probably
        // less of an issue since hyperlinks are only created when they're
        // supported, i.e., when writing to a tty.
        //
        // [1]: https://github.com/BurntSushi/ripgrep/pull/2483
        let path = match original_path.canonicalize() {
            Ok(path) => path,
            Err(err) => {
                log::debug!(

Review comment (suggestion):
The [WASI spec](https://github.com/WebAssembly/wasi-filesystem/blob/174dbd7fa134672e594fd38dba9acfb53afaf312/imports.md#import-interface-wasifilesystemtypes020) seems to ban absolute paths and the [stdlib](https://github.com/rust-lang/rust/blob/c67277301c896857d0534f2bb7431680796833fb/library/std/src/sys/pal/wasi/fs.rs#L655) doesn't implement `canonicalize`. I don't know how black-and-white this is (especially in the long term) but right now this might as well be stubbed out with `None`.

When running on top of Windows I imagine you'd want to return a Windows path here, even though WASI is more Unix-like internally.

Code after:
    /// creates invalid file:// URLs on that platform.
    fn encode(input: &[u8]) -> HyperlinkPath {
        let mut result = Vec::with_capacity(input.len());
        for &byte in input.iter() {
            match byte {
                b'0'..=b'9'
                | b'A'..=b'Z'
                | b'a'..=b'z'
                | b'/'
                | b':'
                | b'-'
                | b'.'
                | b'_'
                | b'~'
                | 128.. => {
                    result.push(byte);
                }
                #[cfg(windows)]
                b'\\' => {
                    result.push(b'/');
                }
                _ => {
                    const HEX: &[u8] = b"0123456789ABCDEF";
                    result.push(b'%');
                    result.push(HEX[(byte >> 4) as usize]);
                    result.push(HEX[(byte & 0xF) as usize]);
                }
            }
        }
        HyperlinkPath(result)
    }
}

#[cfg(test)]
mod tests {
    use std::str::FromStr;

    use super::*;

    #[test]
    fn build_format() {
        let format = FormatBuilder::new()
            .append_slice(b"foo://")
            .append_slice(b"bar-")
            .append_slice(b"baz")
            .append_var("path")
            .unwrap()
            .build()
            .unwrap();

        assert_eq!(format.to_string(), "foo://bar-baz{path}");