fix(links): five holes review found in the new path detection

The second column of a wide character is written as a space, and the
blank-cell shortcut read that as an empty cell. `logical_line_at` hands
a click there back to the character that owns it, so the underline was
going out on every other column of a path spelled in CJK or emoji. Read
a spacer as part of the glyph it belongs to.

Handing a file the built-in editor cannot read to the desktop is how a
click opens a PNG. On macOS it is also how a click *runs* a program:
`open` on a Mach-O binary launches it, and a build's output is full of
paths to programs. A file the execute bit is set on keeps the words it
had before.

`explorer /select,<path>` went through `Command::arg`, which quotes the
whole argument the moment the path holds a space. Explorer answers a
quoted switch by opening Documents and reporting success, so "Show in
Folder" silently showed the wrong folder. Write that command line by
hand instead, with the switch bare and the path quoted behind it.

The right-click menu resolved a path with no regard for the switch that
decides whether a path underlines at all, so a pane with link detection
turned off still offered to open files.

Finally, the `label:` left cut peeled anything after a colon, so
`branch:main` was probed as `main` and resolved against any directory
of that name. Require what follows to be written like a path too.

Claude-Session: https://claude.ai/code/session_01NE3M5Q94Jyxmj5Rdm9bcg4
This commit is contained in:
l0ng-ai
2026-09-09 16:16:21 +08:00
parent 0ab089c0b3
commit 262a166a8d
3 changed files with 192 additions and 7 deletions
+72 -1
View File
@@ -244,6 +244,47 @@ fn looks_binary(bytes: &[u8]) -> bool {
bytes.iter().take(8192).any(|b| *b == 0)
}
/// Whether handing this path to the desktop would run it rather than show it.
///
/// The execute bit is what `open` reads to decide between displaying a file
/// and launching it; Windows has no such bit, so there the extension is the
/// only thing that says so.
fn is_program(path: &Path) -> bool {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
return std::fs::metadata(path)
.is_ok_and(|m| m.is_file() && m.permissions().mode() & 0o111 != 0);
}
#[cfg(not(unix))]
{
let Some(ext) = path.extension().and_then(|e| e.to_str()) else {
return false;
};
matches!(
ext.to_ascii_lowercase().as_str(),
"exe"
| "com"
| "bat"
| "cmd"
| "scr"
| "pif"
| "msi"
| "ps1"
| "vbs"
| "js"
| "jse"
| "wsf"
| "wsh"
| "cpl"
| "msc"
| "hta"
| "reg"
| "lnk"
)
}
}
#[derive(Debug, PartialEq, Eq)]
enum ExternalChange {
Ignore,
@@ -590,6 +631,10 @@ impl Tty7App {
/// A click on a PNG or a `.zip` meant "open this", not "tell me it is not
/// text", and on this machine the desktop knows how. A file on another
/// machine has nobody here to hand it to, so that one gets the words.
///
/// A program does not: `open` on a Mach-O binary runs it, and a build's
/// output is full of paths to programs. Clicking a word in a terminal
/// must not be a way to execute one, so those keep the words too.
fn open_outside_the_editor(
&mut self,
host_id: HostId,
@@ -597,7 +642,7 @@ impl Tty7App {
window: &mut Window,
cx: &mut Context<Self>,
) {
if !host_id.is_local() || !self.can_spawn_locally(cx) {
if !host_id.is_local() || !self.can_spawn_locally(cx) || is_program(path) {
window.push_notification(
t_fmt(
L10nKey::EditorBinaryFile,
@@ -1485,6 +1530,32 @@ impl Tty7App {
mod tests {
use super::*;
/// Handing a file the editor cannot read to the desktop is how a click
/// opens a PNG. It must not be how a click runs a build's output.
#[cfg(unix)]
#[test]
fn a_file_the_desktop_would_run_is_not_handed_to_it() {
use std::os::unix::fs::PermissionsExt;
let dir = std::env::temp_dir().join(format!("tty7-program-{}", std::process::id()));
std::fs::create_dir_all(&dir).expect("create dir");
let image = dir.join("shot.png");
let program = dir.join("built");
std::fs::write(&image, b"\x89PNG\0\0").expect("write image");
std::fs::write(&program, b"\x7fELF\0\0").expect("write program");
std::fs::set_permissions(&program, std::fs::Permissions::from_mode(0o755))
.expect("mark executable");
assert!(!is_program(&image), "a picture is only ever shown");
assert!(is_program(&program), "a binary would be launched");
assert!(
!is_program(&dir),
"a directory is not a program, whatever its mode says"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn language_map_covers_common_extensions() {
for (path, lang) in [