feat: print target and package names formatted as file hyperlinks (#15405)

Resolves #15401

Here is an example of the feature in
[kitty](https://sw.kovidgoyal.net/kitty/) `0.40.1` with the following
config set in `~/.config/kitty/kitty.conf`

```conf
underline_hyperlinks always
show_hyperlink_targets yes
allow_hyperlinks yes
# ...
```

![cargo-target-file-hyperlik-kitty-showcase](https://github.com/user-attachments/assets/04155d5a-a254-4e80-a35e-a02cc4671ae4)

Tested on `uname -a`:
```
Linux nixos 6.14.0 #1-NixOS SMP PREEMPT_DYNAMIC Mon Mar 24 14:02:41 UTC 2025 x86_64 GNU/Linux
```
Terminals tested with:
- [x] [kitty](https://sw.kovidgoyal.net/kitty/) `0.40.1`
- [x] [ghostty](https://ghostty.org/) `1.1.4-6f1b22a-nix`

![image](https://github.com/user-attachments/assets/afeb250f-009b-429a-8854-6c3053449f9e)
- [x] [alacritty](https://alacritty.org/index.html) `0.15.1`

![image](https://github.com/user-attachments/assets/9742cd72-13e5-4e90-8ece-dd101553e5cc)

- [x] VScode's version `1.98` integrated terminal aka.
[xterm.js](https://xtermjs.org/)

![image](https://github.com/user-attachments/assets/f02ce77a-18e4-47dd-b428-7b82bc013021)

The following `cargo` invocations will have their output be modified by
this change:
```shell
cargo run # If multiple binaries are defined in the manifest and [package.default-bin] is not defined
cargo run --bin
cargo run --example
cargo run --package
cargo build --bin
cargo build --example
cargo build --package
cargo test --test
cargo test --bench
```

In addition I have done a slight refactor to have the printed indent of
targets and packages be the same by using a shared constant named `const
ITEM_INDENT: &str = " ";`

This is my first PR to the cargo codebase, so I am not familiar with
what is expected in terms of test for a feature such as this.

<!--
Thanks for submitting a pull request 🎉! Here are some tips for you:

* If this is your first contribution, read "Cargo Contribution Guide"
first:
  https://doc.crates.io/contrib/
* Run `cargo fmt --all` to format your code changes.
* Small commits and pull requests are always preferable and easy to
review.
* If your idea is large and needs feedback from the community, read how:
  https://doc.crates.io/contrib/process/#working-on-large-features
* Cargo takes care of compatibility. Read our design principles:
  https://doc.crates.io/contrib/design.html
* When changing help text of cargo commands, follow the steps to
generate docs:

https://github.com/rust-lang/cargo/tree/master/src/doc#building-the-man-pages
* If your PR is not finished, set it as "draft" PR or add "WIP" in its
title.
* It's ok to use the CI resources to test your PR, but please don't
abuse them.

### What does this PR try to resolve?

Explain the motivation behind this change.
A clear overview along with an in-depth explanation are helpful.

You can use `Fixes #<issue number>` to associate this PR to an existing
issue.

### How should we test and review this PR?

Demonstrate how you test this change and guide reviewers through your
PR.
With a smooth review process, a pull request usually gets reviewed
quicker.

If you don't know how to write and run your tests, please read the
guide:
https://doc.crates.io/contrib/tests

### Additional information

Other information you want to mention in this PR, such as prior arts,
future extensions, an unresolved problem, or a TODO list.
-->
This commit is contained in:
Weihang Lo 2025-04-07 21:33:37 +00:00 committed by GitHub
commit 9bdf6b0595
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -7,13 +7,16 @@ use anyhow::bail;
use cargo_util::paths::normalize_path;
use cargo_util::ProcessBuilder;
use std::fmt::Write;
use std::path::Path;
use std::path::PathBuf;
const ITEM_INDENT: &str = " ";
fn get_available_targets<'a>(
filter_fn: fn(&Target) -> bool,
ws: &'a Workspace<'_>,
options: &'a CompileOptions,
) -> CargoResult<Vec<&'a str>> {
) -> CargoResult<Vec<(&'a str, &'a Path)>> {
let packages = options.spec.get_packages(ws)?;
let mut targets: Vec<_> = packages
@ -24,7 +27,12 @@ fn get_available_targets<'a>(
.iter()
.filter(|target| filter_fn(target))
})
.map(Target::name)
.map(|target| {
(
target.name(),
target.src_path().path().expect("Target is not a `Metabuild` but one of `Bin` | `Test` | `Bench` | `ExampleBin`")
)
})
.collect();
targets.sort();
@ -48,8 +56,10 @@ fn print_available_targets(
writeln!(output, "No {} available.", plural_name)?;
} else {
writeln!(output, "Available {}:", plural_name)?;
for target in targets {
writeln!(output, " {}", target)?;
let mut shell = ws.gctx().shell();
for (name, src_path) in targets {
let link = shell.err_file_hyperlink(src_path);
writeln!(output, "{ITEM_INDENT}{link}{}{link:#}", name)?;
}
}
bail!("{}", output)
@ -58,7 +68,7 @@ fn print_available_targets(
pub fn print_available_packages(ws: &Workspace<'_>) -> CargoResult<()> {
let packages = ws
.members()
.map(|pkg| pkg.name().as_str())
.map(|pkg| (pkg.name().as_str(), pkg.manifest_path()))
.collect::<Vec<_>>();
let mut output = "\"--package <SPEC>\" requires a SPEC format value, \
@ -72,8 +82,10 @@ pub fn print_available_packages(ws: &Workspace<'_>) -> CargoResult<()> {
writeln!(output, "No packages available.")?;
} else {
writeln!(output, "Possible packages/workspace members:")?;
for package in packages {
writeln!(output, " {}", package)?;
let mut shell = ws.gctx().shell();
for (name, manifest_path) in packages {
let link = shell.err_file_hyperlink(manifest_path);
writeln!(output, "{ITEM_INDENT}{link}{}{link:#}", name)?;
}
}
bail!("{}", output)