From 15a26b87c913b48b391ffa3b3df28578879390a7 Mon Sep 17 00:00:00 2001
From: Michael Davis <mcarsondavis@gmail.com>
Date: Tue, 9 Jul 2024 22:40:43 -0500
Subject: [PATCH] Expand tilde for selected paths in goto_file (#10964)

Previously `gf` on `~/.config/helix` for example would error if the
entire path was selected but succeed and open a picker for the directory
contents if the selection was one one-width cursor. We need to expand
tildes for all paths instead of just the auto-detected paths.

This also refactors the `goto_file` blocks a little so that we construct
`paths` once instead of creating the Vec and immediately clearing it
when the selection is one single-width cursor.
---
 helix-term/src/commands.rs | 40 +++++++++++++++++---------------------
 1 file changed, 18 insertions(+), 22 deletions(-)

diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 67955aec..5f73a23c 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -1203,19 +1203,15 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
     let (view, doc) = current_ref!(cx.editor);
     let text = doc.text();
     let selections = doc.selection(view.id);
+    let primary = selections.primary();
     let rel_path = doc
         .relative_path()
         .map(|path| path.parent().unwrap().to_path_buf())
         .unwrap_or_default();
-    let mut paths: Vec<_> = selections
-        .iter()
-        .map(|r| text.slice(r.from()..r.to()).to_string())
-        .collect();
-    let primary = selections.primary();
-    // Checks whether there is only one selection with a width of 1
-    if selections.len() == 1 && primary.len() == 1 {
-        paths.clear();
 
+    let paths: Vec<_> = if selections.len() == 1 && primary.len() == 1 {
+        // Secial case: if there is only one one-width selection, try to detect the
+        // path under the cursor.
         let is_valid_path_char = |c: &char| {
             #[cfg(target_os = "windows")]
             let valid_chars = &[
@@ -1250,29 +1246,29 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
             .take_while(is_valid_path_char)
             .count();
 
-        let path: Cow<str> = text
+        let path: String = text
             .slice((start_pos - prefix_len)..(start_pos + postfix_len))
             .into();
-        log::debug!("Goto file path: {}", path);
+        log::debug!("goto_file auto-detected path: {}", path);
 
-        match expand_tilde(PathBuf::from(path.as_ref())).to_str() {
-            Some(path) => paths.push(path.to_string()),
-            None => cx.editor.set_error("Couldn't get string out of path."),
-        };
-    }
+        vec![path]
+    } else {
+        // Otherwise use each selection, trimmed.
+        selections
+            .fragments(text.slice(..))
+            .map(|sel| sel.trim().to_string())
+            .filter(|sel| !sel.is_empty())
+            .collect()
+    };
 
     for sel in paths {
-        let p = sel.trim();
-        if p.is_empty() {
-            continue;
-        }
-
-        if let Ok(url) = Url::parse(p) {
+        if let Ok(url) = Url::parse(&sel) {
             open_url(cx, url, action);
             continue;
         }
 
-        let path = &rel_path.join(p);
+        let path = expand_tilde(Cow::from(PathBuf::from(sel)));
+        let path = &rel_path.join(path);
         if path.is_dir() {
             let picker = ui::file_picker(path.into(), &cx.editor.config());
             cx.push_layer(Box::new(overlaid(picker)));