mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-22 01:16:18 +04:00
open urls with goto_file command (#5820)
* feat(commands): open urls with goto_file command Add capability for `goto_file` command to open an URL under cursor. Fixes: https://github.com/helix-editor/helix/issues/1472 Superseds: https://github.com/helix-editor/helix/pull/4398 * open files inside helix * address code review * bump deps * fix based on code review comments
This commit is contained in:
parent
bfd60a5b39
commit
3052050ee0
38
Cargo.lock
generated
38
Cargo.lock
generated
@ -1237,6 +1237,7 @@ dependencies = [
|
||||
"log",
|
||||
"nucleo",
|
||||
"once_cell",
|
||||
"open",
|
||||
"pulldown-cmark",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@ -1247,6 +1248,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"toml",
|
||||
"url",
|
||||
"which",
|
||||
]
|
||||
|
||||
@ -1410,6 +1412,25 @@ version = "2.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
|
||||
|
||||
[[package]]
|
||||
name = "is-docker"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-wsl"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
|
||||
dependencies = [
|
||||
"is-docker",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.6"
|
||||
@ -1610,6 +1631,17 @@ version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||
|
||||
[[package]]
|
||||
name = "open"
|
||||
version = "5.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfabf1927dce4d6fdf563d63328a0a506101ced3ec780ca2135747336c98cef8"
|
||||
dependencies = [
|
||||
"is-wsl",
|
||||
"libc",
|
||||
"pathdiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
@ -1639,6 +1671,12 @@ dependencies = [
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathdiff"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.0"
|
||||
|
@ -57,6 +57,10 @@ pulldown-cmark = { version = "0.9", default-features = false }
|
||||
# file type detection
|
||||
content_inspector = "0.2.4"
|
||||
|
||||
# opening URLs
|
||||
open = "5.0.0"
|
||||
url = "2.4.1"
|
||||
|
||||
# config
|
||||
toml = "0.7"
|
||||
|
||||
|
@ -60,8 +60,13 @@
|
||||
|
||||
use crate::job::{self, Jobs};
|
||||
use futures_util::{stream::FuturesUnordered, TryStreamExt};
|
||||
use std::{collections::HashMap, fmt, future::Future};
|
||||
use std::{collections::HashSet, num::NonZeroUsize};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fmt,
|
||||
future::Future,
|
||||
io::Read,
|
||||
num::NonZeroUsize,
|
||||
};
|
||||
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
@ -70,6 +75,7 @@
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::de::{self, Deserialize, Deserializer};
|
||||
use url::Url;
|
||||
|
||||
use grep_regex::RegexMatcherBuilder;
|
||||
use grep_searcher::{sinks, BinaryDetection, SearcherBuilder};
|
||||
@ -331,7 +337,7 @@ pub fn doc(&self) -> &str {
|
||||
goto_implementation, "Goto implementation",
|
||||
goto_file_start, "Goto line number <n> else file start",
|
||||
goto_file_end, "Goto file end",
|
||||
goto_file, "Goto files in selection",
|
||||
goto_file, "Goto files/URLs in selection",
|
||||
goto_file_hsplit, "Goto files in selection (hsplit)",
|
||||
goto_file_vsplit, "Goto files in selection (vsplit)",
|
||||
goto_reference, "Goto references",
|
||||
@ -1190,10 +1196,53 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
for sel in paths {
|
||||
let p = sel.trim();
|
||||
if !p.is_empty() {
|
||||
let path = &rel_path.join(p);
|
||||
if p.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok(url) = Url::parse(p) {
|
||||
return open_url(cx, url, action);
|
||||
}
|
||||
|
||||
let path = &rel_path.join(p);
|
||||
if path.is_dir() {
|
||||
let picker = ui::file_picker(path.into(), &cx.editor.config());
|
||||
cx.push_layer(Box::new(overlaid(picker)));
|
||||
} else if let Err(e) = cx.editor.open(path, action) {
|
||||
cx.editor.set_error(format!("Open file failed: {:?}", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens the given url. If the URL points to a valid textual file it is open in helix.
|
||||
// Otherwise, the file is open using external program.
|
||||
fn open_url(cx: &mut Context, url: Url, action: Action) {
|
||||
let doc = doc!(cx.editor);
|
||||
let rel_path = doc
|
||||
.relative_path()
|
||||
.map(|path| path.parent().unwrap().to_path_buf())
|
||||
.unwrap_or_default();
|
||||
|
||||
if url.scheme() != "file" {
|
||||
return open_external_url(cx, url);
|
||||
}
|
||||
|
||||
let content_type = std::fs::File::open(url.path()).and_then(|file| {
|
||||
// Read up to 1kb to detect the content type
|
||||
let mut read_buffer = Vec::new();
|
||||
let n = file.take(1024).read_to_end(&mut read_buffer)?;
|
||||
Ok(content_inspector::inspect(&read_buffer[..n]))
|
||||
});
|
||||
|
||||
// we attempt to open binary files - files that can't be open in helix - using external
|
||||
// program as well, e.g. pdf files or images
|
||||
match content_type {
|
||||
Ok(content_inspector::ContentType::BINARY) => open_external_url(cx, url),
|
||||
Ok(_) | Err(_) => {
|
||||
let path = &rel_path.join(url.path());
|
||||
if path.is_dir() {
|
||||
let picker = ui::file_picker(path.into(), &cx.editor.config());
|
||||
cx.push_layer(Box::new(overlaid(picker)));
|
||||
@ -1204,6 +1253,23 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens URL in external program.
|
||||
fn open_external_url(cx: &mut Context, url: Url) {
|
||||
let commands = open::commands(url.as_str());
|
||||
cx.jobs.callback(async {
|
||||
for cmd in commands {
|
||||
let mut command = tokio::process::Command::new(cmd.get_program());
|
||||
command.args(cmd.get_args());
|
||||
if command.output().await.is_ok() {
|
||||
return Ok(job::Callback::Editor(Box::new(|_| {})));
|
||||
}
|
||||
}
|
||||
Ok(job::Callback::Editor(Box::new(move |editor| {
|
||||
editor.set_error("Opening URL in external program failed")
|
||||
})))
|
||||
});
|
||||
}
|
||||
|
||||
fn extend_word_impl<F>(cx: &mut Context, extend_fn: F)
|
||||
where
|
||||
F: Fn(RopeSlice, Range, usize) -> Range,
|
||||
|
@ -26,7 +26,7 @@ helix-vcs = { version = "0.6", path = "../helix-vcs" }
|
||||
|
||||
# Conversion traits
|
||||
once_cell = "1.18"
|
||||
url = "2"
|
||||
url = "2.4.1"
|
||||
|
||||
arc-swap = { version = "1.6.0" }
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user