Add support for --vroot

--vroot helps isolating navigation of an xplr session inside a specific
directory. However, interaction still requires passing full paths
(`/tmp/vroot`). Shell scripts and Lua functions can still access files
outside the virtual root.

This PR also fixes unwanted dot (.) and extra slash (//) issues in paths.
pull/524/head
Arijit Basu 2 years ago committed by Arijit Basu
parent 00ffd077aa
commit a62b72bf2a

19
Cargo.lock generated

@ -732,6 +732,24 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "path-absolutize"
version = "3.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f1d4993b16f7325d90c18c3c6a3327db7808752db8d208cea0acee0abd52c52"
dependencies = [
"path-dedot",
]
[[package]]
name = "path-dedot"
version = "3.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a81540d94551664b72b72829b12bd167c73c9d25fbac0e04fafa8023f7e4901"
dependencies = [
"once_cell",
]
[[package]]
name = "pkg-config"
version = "0.3.25"
@ -1340,6 +1358,7 @@ dependencies = [
"mime_guess",
"mlua",
"natord",
"path-absolutize",
"regex",
"serde",
"serde_json",

@ -34,6 +34,7 @@ regex = "1.6.0"
gethostname = "0.3.0"
fuzzy-matcher = "0.3.7"
serde_json = "1.0.87"
path-absolutize = "3.0.14"
[dependencies.lazy_static]
version = "1.4.0"

@ -65,6 +65,9 @@ compatibility.
- on_load
- on_focus_change
- on_directory_change
- Use `--vroot` to isolate navigation of an xplr session inside a specific
directory. Interaction still requires passing full path, and shell,
lua functions etc still can access paths outside vroot.
#### [v0.18.0][46] -> [v0.19.4][47]

@ -24,6 +24,7 @@ use anyhow::{bail, Result};
use chrono::{DateTime, Local};
use gethostname::gethostname;
use indexmap::set::IndexSet;
use path_absolutize::*;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::collections::VecDeque;
@ -166,6 +167,7 @@ pub struct App {
pub version: String,
pub config: Config,
pub hooks: Hooks,
pub vroot: String,
pub pwd: String,
pub directory_buffer: Option<DirectoryBuffer>,
pub last_focus: HashMap<String, Option<String>>,
@ -188,6 +190,7 @@ pub struct App {
impl App {
pub fn create(
bin: String,
vroot: PathBuf,
pwd: PathBuf,
lua: &mlua::Lua,
config_file: Option<PathBuf>,
@ -199,14 +202,14 @@ impl App {
let config_file = if let Some(path) = config_file {
Some(path)
} else if let Some(dir) = dirs::home_dir() {
let path = dir.join(".config").join("xplr").join("init.lua");
let path = dir.join(".config/xplr/init.lua");
if path.exists() {
Some(path)
} else {
None
}
} else {
let path = PathBuf::from("/").join("etc").join("xplr").join("init.lua");
let path = PathBuf::from("/etc/xplr/init.lua");
if path.exists() {
Some(path)
} else {
@ -294,7 +297,16 @@ impl App {
};
let hostname = gethostname().to_string_lossy().to_string();
if !pwd.starts_with(&vroot) {
bail!(
"{:?} is outside of virtual root {:?}",
pwd.to_string_lossy(),
vroot.to_string_lossy()
)
}
let pwd = pwd.to_string_lossy().to_string();
let vroot = vroot.to_string_lossy().to_string();
env::set_current_dir(&pwd)?;
let input = InputBuffer {
@ -306,6 +318,7 @@ impl App {
bin,
version: VERSION.to_string(),
config,
vroot,
pwd,
directory_buffer: Default::default(),
last_focus: Default::default(),
@ -743,9 +756,15 @@ impl App {
}
fn change_directory(mut self, dir: &str, save_history: bool) -> Result<Self> {
let mut dir = PathBuf::from(dir);
if dir.is_relative() {
dir = PathBuf::from(self.pwd.clone()).join(dir);
let dir = PathBuf::from(dir).absolutize()?.to_path_buf();
let vroot = &self.vroot.clone();
if !dir.starts_with(&self.vroot) {
return self.log_error(format!(
"{:?} is outside of virtual root {:?}",
dir.to_string_lossy(),
vroot,
));
}
match env::set_current_dir(&dir) {
@ -971,10 +990,7 @@ impl App {
}
pub fn focus_path(self, path: &str, save_history: bool) -> Result<Self> {
let mut pathbuf = PathBuf::from(path);
if pathbuf.is_relative() {
pathbuf = PathBuf::from(self.pwd.clone()).join(pathbuf);
}
let pathbuf = PathBuf::from(path).absolutize()?.to_path_buf();
if let Some(parent) = pathbuf.parent() {
if let Some(filename) = pathbuf.file_name() {
self.change_directory(&parent.to_string_lossy(), false)?
@ -1216,10 +1232,7 @@ impl App {
}
pub fn select_path(mut self, path: String) -> Result<Self> {
let mut path = PathBuf::from(path);
if path.is_relative() {
path = PathBuf::from(self.pwd.clone()).join(path);
}
let path = PathBuf::from(path).absolutize()?.to_path_buf();
let parent = path.parent().map(|p| p.to_string_lossy().to_string());
let filename = path.file_name().map(|p| p.to_string_lossy().to_string());
if let (Some(p), Some(n)) = (parent, filename) {
@ -1246,10 +1259,7 @@ impl App {
}
pub fn un_select_path(mut self, path: String) -> Result<Self> {
let mut pathbuf = PathBuf::from(path);
if pathbuf.is_relative() {
pathbuf = PathBuf::from(self.pwd.clone()).join(pathbuf);
}
let pathbuf = PathBuf::from(path).absolutize()?.to_path_buf();
self.selection
.retain(|n| PathBuf::from(&n.absolute_path) != pathbuf);
Ok(self)
@ -1286,10 +1296,7 @@ impl App {
}
fn toggle_selection_by_path(self, path: String) -> Result<Self> {
let mut pathbuf = PathBuf::from(&path);
if pathbuf.is_relative() {
pathbuf = PathBuf::from(self.pwd.clone()).join(pathbuf);
}
let pathbuf = PathBuf::from(&path).absolutize()?.to_path_buf();
if self
.selection
.iter()

@ -1,7 +1,6 @@
#![allow(clippy::too_many_arguments)]
use std::env;
use xplr::cli::{self, Cli};
use xplr::runner;
@ -36,7 +35,9 @@ fn main() {
-c, --config <PATH> Specifies a custom config file (default is
"$HOME/.config/xplr/init.lua")
-C, --extra-config <PATH>... Specifies extra config files to load
--on-load <MESSAGE>... Sends messages when xplr loads"###;
--on-load <MESSAGE>... Sends messages when xplr loads
--vroot <PATH> Treats the specified path as the virtual root for
navigation, but uses full path for interaction"###;
let args = r###"
<PATH> Path to focus on, or enter if directory, (default is `.`)

@ -1,6 +1,7 @@
use crate::{app, yaml};
use anyhow::{bail, Context, Result};
use app::ExternalMsg;
use path_absolutize::*;
use serde_json as json;
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
@ -18,6 +19,7 @@ pub struct Cli {
pub print_pwd_as_result: bool,
pub read0: bool,
pub write0: bool,
pub vroot: Option<PathBuf>,
pub config: Option<PathBuf>,
pub extra_config: Vec<PathBuf>,
pub on_load: Vec<app::ExternalMsg>,
@ -32,7 +34,7 @@ impl Cli {
bail!("empty string passed")
};
let path = PathBuf::from(arg);
let path = PathBuf::from(arg).absolutize()?.to_path_buf();
if path.exists() {
self.paths.push(path);
Ok(())
@ -94,13 +96,24 @@ impl Cli {
}
// Options
"-c" | "--config" => cli.config = args.next().map(PathBuf::from),
"-c" | "--config" => {
if let Some(p) = args.next().map(PathBuf::from) {
cli.config = Some(p.absolutize()?.to_path_buf());
};
}
"--vroot" => {
if let Some(p) = args.next().map(PathBuf::from) {
cli.vroot = Some(p.absolutize()?.to_path_buf());
}
}
"-C" | "--extra-config" => {
while let Some(path) =
args.next_if(|path| !path.starts_with('-'))
{
cli.extra_config.push(PathBuf::from(path));
cli.extra_config
.push(PathBuf::from(path).absolutize()?.to_path_buf());
}
}

@ -102,6 +102,7 @@ pub(crate) fn explore_async(
pub(crate) fn explore_recursive_async(
config: ExplorerConfig,
root: PathBuf,
parent: PathBuf,
focused_path: Option<PathBuf>,
fallback_focus: usize,
@ -115,13 +116,16 @@ pub(crate) fn explore_recursive_async(
tx_msg_in.clone(),
);
if let Some(grand_parent) = parent.parent() {
explore_recursive_async(
config,
grand_parent.into(),
parent.file_name().map(|p| p.into()),
0,
tx_msg_in,
);
if grand_parent.starts_with(&root) {
explore_recursive_async(
config,
root,
grand_parent.into(),
parent.file_name().map(|p| p.into()),
0,
tx_msg_in,
);
}
}
}

@ -191,6 +191,7 @@ fn start_fifo(path: &str, focus_path: &str) -> Result<fs::File> {
pub struct Runner {
bin: String,
vroot: PathBuf,
pwd: PathBuf,
focused_path: Option<PathBuf>,
config_file: Option<PathBuf>,
@ -210,25 +211,24 @@ impl Runner {
/// Create a new runner object passing the given arguments
pub fn from_cli(cli: Cli) -> Result<Self> {
let basedir = get_current_dir()?;
let basedir_clone = basedir.clone();
let mut paths = cli.paths.into_iter().map(|p| {
if p.is_relative() {
basedir_clone.join(p)
} else {
p
}
});
let mut pwd = paths.next().unwrap_or_else(|| basedir.clone());
let currdir = get_current_dir()?;
let mut paths = cli.paths.into_iter();
let mut pwd = paths
.next()
.or_else(|| cli.vroot.clone())
.unwrap_or_else(|| currdir.clone());
let mut focused_path = None;
if cli.force_focus || pwd.is_file() {
focused_path = pwd.file_name().map(|p| p.into());
pwd = pwd.parent().map(|p| p.into()).unwrap_or(basedir);
pwd = pwd.parent().map(|p| p.into()).unwrap_or(currdir);
}
let root = cli.vroot.unwrap_or_else(|| "/".into());
Ok(Self {
bin: cli.bin,
vroot: root,
pwd,
focused_path,
config_file: cli.config,
@ -247,6 +247,7 @@ impl Runner {
let lua = unsafe { mlua::Lua::unsafe_new() };
let mut app = app::App::create(
self.bin,
self.vroot,
self.pwd,
&lua,
self.config_file,
@ -277,6 +278,7 @@ impl Runner {
explorer::explore_recursive_async(
app.explorer_config.clone(),
app.vroot.clone().into(),
app.pwd.clone().into(),
self.focused_path,
app.directory_buffer.as_ref().map(|d| d.focus).unwrap_or(0),
@ -442,6 +444,7 @@ impl Runner {
ExploreParentsAsync => {
explorer::explore_recursive_async(
app.explorer_config.clone(),
app.vroot.clone().into(),
app.pwd.clone().into(),
app.focused_node()
.map(|n| n.relative_path.clone().into()),

@ -669,6 +669,14 @@ fn draw_table<B: Backend>(
.map(|c| c.to_tui(screen_size, layout_size))
.collect();
let pwd = app
.pwd
.strip_prefix(&app.vroot)
.unwrap_or(&app.pwd)
.trim_matches('/')
.replace('\\', "\\\\")
.replace('\n', "\\n");
let table = Table::new(rows)
.widths(&table_constraints)
.style(app_config.general.table.style.to_owned().into())
@ -677,8 +685,8 @@ fn draw_table<B: Backend>(
.block(block(
config,
format!(
" {} ({}) ",
app.pwd.replace('\\', "\\\\").replace('\n', "\\n"),
" /{} ({}) ",
pwd,
app.directory_buffer
.as_ref()
.map(|d| d.total)

Loading…
Cancel
Save