Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ fundu = "2.0.0"
gcd = "2.3"
glob = "0.3.1"
half = "2.3"
hostname = "0.3"
indicatif = "0.17"
itertools = "0.12.0"
libc = "0.2.150"
Expand Down
2 changes: 1 addition & 1 deletion src/uu/hostname/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ path = "src/hostname.rs"

[dependencies]
clap = { workspace = true }
hostname = { version = "0.3", features = ["set"] }
hostname = { workspace = true, features = ["set"] }
uucore = { workspace = true, features = ["wide"] }

[target.'cfg(target_os = "windows")'.dependencies]
Expand Down
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 = { workspace = true }

[[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"),
}
}

/// 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 opt_block_size = options.get_one::<String>(options::size::BLOCK_SIZE);
let opt_si = opt_block_size.is_some()
Expand Down Expand Up @@ -1020,6 +1040,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 @@ -1154,6 +1175,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 @@ -2959,6 +2993,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 @@ -3855,3 +3855,33 @@ fn test_posixly_correct() {
.succeeds()
.stdout_contains_line("total 8");
}

#[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"));
}