From 0c398c893a4db873c344ee8b775c8f44de72e8ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christopher=20L=C3=BCbbemeier?= Date: Mon, 19 Mar 2018 19:00:40 +0100 Subject: [PATCH] Open files with drag and drop, escape filenames --- Cargo.lock | 9 +++++++++ Cargo.toml | 3 +++ src/file_browser.rs | 2 ++ src/main.rs | 10 +++++++++- src/misc.rs | 38 ++++++++++++++++++++++++++++++++++++++ src/shell.rs | 23 +++++++++++++++++++++++ 6 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 src/misc.rs diff --git a/Cargo.lock b/Cargo.lock index d84f4da..36c8973 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -350,14 +350,17 @@ dependencies = [ "gtk 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "gtk-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "neovim-lib 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "pango 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "pango-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "pangocairo 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "pangocairo-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", "phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -419,6 +422,11 @@ dependencies = [ "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "phf" version = "0.7.21" @@ -750,6 +758,7 @@ dependencies = [ "checksum pango-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94039b3921a4af4058a3e4335e5d15099101f298a92f5afc40bab3a3027594a1" "checksum pangocairo 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "db4130d0e0567b177178c9568466277ff167cb43a245b2881e203509ea5fbd84" "checksum pangocairo-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "decc9523cd5bbba49a67ed8d2ef7dfca957f4760bf420a1ea4a82634da26381c" +"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" "checksum phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "cb325642290f28ee14d8c6201159949a872f220c62af6e110a56ea914fbe42fc" "checksum phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d62594c0bb54c464f633175d502038177e90309daf2e0158be42ed5f023ce88f" "checksum phf_generator 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "6b07ffcc532ccc85e3afc45865469bf5d9e4ef5bfcf9622e3cfe80c2d275ec03" diff --git a/Cargo.toml b/Cargo.toml index d98b19f..bfbe349 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,9 @@ phf = "0.7" log = "0.4" env_logger = "0.5" htmlescape = "0.3" +percent-encoding = "1.0" +regex = "0.2" +lazy_static = "1.0" serde = "1.0" serde_derive = "1.0" diff --git a/src/file_browser.rs b/src/file_browser.rs index 810546f..9865296 100644 --- a/src/file_browser.rs +++ b/src/file_browser.rs @@ -15,6 +15,7 @@ use gtk::prelude::*; use neovim_lib::{NeovimApi, NeovimApiAsync}; +use misc::escape_filename; use nvim::{ErrorReport, NeovimClient, NeovimRef}; use shell; @@ -267,6 +268,7 @@ impl FileBrowserWidget { } else { &file_path }; + let file_path = escape_filename(file_path); nvim_ref.nvim().unwrap().command_async(&format!(":e {}", file_path)) .cb(|r| r.report_err()) .call(); diff --git a/src/main.rs b/src/main.rs index 75f9600..95a7794 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,13 +11,17 @@ extern crate gtk; extern crate gtk_sys; extern crate htmlescape; #[macro_use] +extern crate lazy_static; +#[macro_use] extern crate log; extern crate neovim_lib; extern crate pango; extern crate pango_cairo_sys; extern crate pango_sys; extern crate pangocairo; +extern crate percent_encoding; extern crate phf; +extern crate regex; extern crate serde; #[macro_use] @@ -50,6 +54,7 @@ mod tabline; mod error; mod file_browser; mod subscriptions; +mod misc; use std::env; use std::time::Duration; @@ -58,6 +63,7 @@ use gio::prelude::*; use ui::Ui; +use misc::escape_filename; use shell::ShellOptions; const BIN_PATH_ARG: &str = "--nvim-bin-path"; @@ -101,7 +107,9 @@ fn open(app: >k::Application, files: &[gio::File], _: &str) { for f in files { let mut ui = Ui::new(ShellOptions::new( nvim_bin_path(std::env::args()), - f.get_path().and_then(|p| p.to_str().map(str::to_owned)), + f.get_path().and_then(|p| { + p.to_str().map(|path| escape_filename(path).to_string()) + }), nvim_timeout(std::env::args()), )); diff --git a/src/misc.rs b/src/misc.rs new file mode 100644 index 0000000..2c3c768 --- /dev/null +++ b/src/misc.rs @@ -0,0 +1,38 @@ +use std::borrow::Cow; + +use regex::Regex; +use percent_encoding::percent_decode; + +/// Escape special ASCII characters with a backslash. +pub fn escape_filename<'t>(filename: &'t str) -> Cow<'t, str> { + lazy_static! { + static ref SPECIAL_CHARS: Regex = if cfg!(target_os = "windows") { + // On Windows, don't escape `:` and `\`, as these are valid components of the path. + Regex::new(r"[[:ascii:]&&[^0-9a-zA-Z._:\\-]]").unwrap() + } else { + // Similarly, don't escape `/` on other platforms. + Regex::new(r"[[:ascii:]&&[^0-9a-zA-Z._/-]]").unwrap() + }; + } + SPECIAL_CHARS.replace_all(&*filename, r"\$0") +} + +/// Decode a file URI. +/// +/// - On UNIX: `file:///path/to/a%20file.ext` -> `/path/to/a file.ext` +/// - On Windows: `file:///C:/path/to/a%20file.ext` -> `C:\path\to\a file.ext` +pub fn decode_uri(uri: &str) -> Option { + let path = match uri.split_at(8) { + ("file:///", path) => path, + _ => return None, + }; + let path = percent_decode(path.as_bytes()).decode_utf8().ok()?; + if cfg!(target_os = "windows") { + lazy_static! { + static ref SLASH: Regex = Regex::new(r"/").unwrap(); + } + Some(String::from(SLASH.replace_all(&*path, r"\"))) + } else { + Some("/".to_owned() + &path) + } +} diff --git a/src/shell.rs b/src/shell.rs index 48cca72..5fc1688 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -18,6 +18,7 @@ use pangocairo; use neovim_lib::{Neovim, NeovimApi, NeovimApiAsync, Value}; use neovim_lib::neovim_api::Tabpage; +use misc::{decode_uri, escape_filename}; use settings::{FontSource, Settings}; use ui_model::{Attrs, ModelRect, UiModel}; use color::{Color, ColorModel, COLOR_BLACK, COLOR_RED, COLOR_WHITE}; @@ -567,6 +568,28 @@ impl Shell { state.drawing_area.connect_size_allocate(move |_, _| { init_nvim(&ref_state); }); + + let ref_state = self.state.clone(); + let targets = vec![ + gtk::TargetEntry::new("text/uri-list", gtk::TargetFlags::OTHER_APP, 0), + ]; + state + .drawing_area + .drag_dest_set(gtk::DestDefaults::ALL, &targets, gdk::DragAction::COPY); + state + .drawing_area + .connect_drag_data_received(move |_, _, _, _, s, _, _| { + let uris = s.get_uris(); + let command = uris.iter() + .filter_map(|uri| decode_uri(uri)) + .fold(":ar".to_owned(), |command, filename| { + let filename = escape_filename(&filename); + command + " " + &filename + }); + let state = ref_state.borrow_mut(); + let mut nvim = state.nvim().unwrap(); + nvim.command_async(&command).cb(|r| r.report_err()).call() + }); } #[cfg(unix)]