|
|
|
@ -60,8 +60,13 @@ use crate::{
|
|
|
|
|
|
|
|
|
|
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 std::{
|
|
|
|
|
|
|
|
|
|
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 @@ impl MappableCommand {
|
|
|
|
|
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,
|
|
|
|
|