Skip to content

Commit

Permalink
ls: implement --hyperlink
Browse files Browse the repository at this point in the history
  • Loading branch information
cakebaker committed Dec 6, 2023
1 parent 6b0eff6 commit 4e2f18e
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 2 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/uu/ls/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ uucore = { workspace = true, features = [
] }
once_cell = { workspace = true }
selinux = { workspace = true, optional = true }
hostname = "0.3"

[[bin]]
name = "ls"
Expand Down
50 changes: 48 additions & 2 deletions src/uu/ls/src/ls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ pub mod options {
pub static GROUP_DIRECTORIES_FIRST: &str = "group-directories-first";
pub static ZERO: &str = "zero";
pub static DIRED: &str = "dired";
pub static HYPERLINK: &str = "hyperlink";
}

const DEFAULT_TERM_WIDTH: u16 = 80;
Expand Down Expand Up @@ -418,6 +419,7 @@ pub struct Config {
group_directories_first: bool,
line_ending: LineEnding,
dired: bool,
hyperlink: bool,
}

// Fields that can be removed or added to the long format
Expand Down Expand Up @@ -566,6 +568,25 @@ fn extract_color(options: &clap::ArgMatches) -> bool {
}
}

/// Extracts the hyperlink option to use based on the options provided.
///
/// # Returns
///
/// A boolean representing whether to hyperlink files.
fn extract_hyperlink(options: &clap::ArgMatches) -> bool {
let hyperlink = options
.get_one::<String>(options::HYPERLINK)
.unwrap()
.as_str();

match hyperlink {
"always" | "yes" | "force" => true,
"auto" | "tty" | "if-tty" => std::io::stdout().is_terminal(),
"never" | "no" | "none" => false,
_ => unreachable!("should be handled by clap"),

Check warning on line 586 in src/uu/ls/src/ls.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/ls/src/ls.rs#L586

Added line #L586 was not covered by tests
}
}

/// Extracts the quoting style to use based on the options provided.
///
/// # Arguments
Expand Down Expand Up @@ -736,10 +757,9 @@ impl Config {
}

let sort = extract_sort(options);

let time = extract_time(options);

let mut needs_color = extract_color(options);
let hyperlink = extract_hyperlink(options);

let cmd_line_bs = options.get_one::<String>(options::size::BLOCK_SIZE);
let opt_si = cmd_line_bs.is_some()
Expand Down Expand Up @@ -1022,6 +1042,7 @@ impl Config {
group_directories_first: options.get_flag(options::GROUP_DIRECTORIES_FIRST),
line_ending: LineEnding::from_zero_flag(options.get_flag(options::ZERO)),
dired,
hyperlink,
})
}
}
Expand Down Expand Up @@ -1156,6 +1177,19 @@ pub fn uu_app() -> Command {
.help("generate output designed for Emacs' dired (Directory Editor) mode")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::HYPERLINK)
.long(options::HYPERLINK)
.help("hyperlink file names WHEN")
.value_parser([
"always", "yes", "force", "auto", "tty", "if-tty", "never", "no", "none",
])
.require_equals(true)
.num_args(0..=1)
.default_missing_value("always")
.default_value("never")
.value_name("WHEN"),
)
// The next four arguments do not override with the other format
// options, see the comment in Config::from for the reason.
// Ideally, they would use Arg::override_with, with their own name
Expand Down Expand Up @@ -2961,6 +2995,18 @@ fn display_file_name(
// infer it because the color codes mess up term_grid's width calculation.
let mut width = name.width();

if config.hyperlink {
let hostname = hostname::get().unwrap_or(OsString::from(""));
let hostname = hostname.to_string_lossy();

let absolute_path = fs::canonicalize(&path.p_buf).unwrap_or_default();
let absolute_path = absolute_path.to_string_lossy();

// TODO encode path
// \x1b = ESC, \x07 = BEL
name = format!("\x1b]8;;file://{hostname}{absolute_path}\x07{name}\x1b]8;;\x07");
}

if let Some(ls_colors) = &config.color {
let md = path.md(out);
name = if md.is_some() {
Expand Down
30 changes: 30 additions & 0 deletions tests/by-util/test_ls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3828,3 +3828,33 @@ fn test_ls_cf_output_should_be_delimited_by_tab() {
.succeeds()
.stdout_is("a2345/\tb/\n");
}

#[test]
fn test_ls_hyperlink() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file = "a.txt";

at.touch(file);

let path = at.root_dir_resolved();
let separator = std::path::MAIN_SEPARATOR_STR;

let result = scene.ucmd().arg("--hyperlink").succeeds();
assert!(result.stdout_str().contains("\x1b]8;;file://"));
assert!(result
.stdout_str()
.contains(&format!("{path}{separator}{file}\x07{file}\x1b]8;;\x07")));

let result = scene.ucmd().arg("--hyperlink=always").succeeds();
assert!(result.stdout_str().contains("\x1b]8;;file://"));
assert!(result
.stdout_str()
.contains(&format!("{path}{separator}{file}\x07{file}\x1b]8;;\x07")));

scene
.ucmd()
.arg("--hyperlink=never")
.succeeds()
.stdout_is(format!("{file}\n"));
}

0 comments on commit 4e2f18e

Please sign in to comment.