fix(plugins): allow loading relative urls (#2539)
* fix(plugins): allow loading relative urls * style(fmt): rustfmt
This commit is contained in:
parent
59239cc113
commit
f19334754c
9 changed files with 73 additions and 46 deletions
|
|
@ -1,13 +1,16 @@
|
|||
---
|
||||
source: zellij-server/src/plugins/./unit/plugin_tests.rs
|
||||
assertion_line: 2496
|
||||
assertion_line: 2889
|
||||
expression: "format!(\"{:#?}\", new_tab_event)"
|
||||
---
|
||||
Some(
|
||||
StartOrReloadPluginPane(
|
||||
File(
|
||||
"/path/to/my/plugin.wasm",
|
||||
),
|
||||
RunPlugin {
|
||||
_allow_exec_host_cmd: false,
|
||||
location: File(
|
||||
"/path/to/my/plugin.wasm",
|
||||
),
|
||||
},
|
||||
None,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ use zellij_utils::{
|
|||
input::{
|
||||
actions::Action,
|
||||
command::{RunCommand, RunCommandAction, TerminalAction},
|
||||
layout::Layout,
|
||||
layout::{Layout, RunPlugin, RunPluginLocation},
|
||||
plugins::PluginType,
|
||||
},
|
||||
serde,
|
||||
|
|
@ -946,10 +946,19 @@ fn host_start_or_reload_plugin(env: &ForeignFunctionEnv) {
|
|||
env.plugin_env.name()
|
||||
)
|
||||
};
|
||||
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
||||
wasi_read_string(&env.plugin_env.wasi_env)
|
||||
.and_then(|url| Url::parse(&url).map_err(|e| anyhow!("Failed to parse url: {}", e)))
|
||||
.and_then(|url| {
|
||||
let action = Action::StartOrReloadPlugin(url);
|
||||
RunPluginLocation::parse(url.as_str(), Some(cwd))
|
||||
.map_err(|e| anyhow!("Failed to parse plugin location: {}", e))
|
||||
})
|
||||
.and_then(|run_plugin_location| {
|
||||
let run_plugin = RunPlugin {
|
||||
location: run_plugin_location,
|
||||
_allow_exec_host_cmd: false,
|
||||
};
|
||||
let action = Action::StartOrReloadPlugin(run_plugin);
|
||||
apply_action!(action, error_msg, env);
|
||||
Ok(())
|
||||
})
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use zellij_utils::{
|
|||
actions::{Action, SearchDirection, SearchOption},
|
||||
command::TerminalAction,
|
||||
get_mode_info,
|
||||
layout::{Layout, RunPluginLocation},
|
||||
layout::Layout,
|
||||
},
|
||||
ipc::{
|
||||
ClientAttributes, ClientToServerMsg, ExitReason, IpcReceiverWithContext, ServerToClientMsg,
|
||||
|
|
@ -615,14 +615,9 @@ pub(crate) fn route_action(
|
|||
))
|
||||
.with_context(err_context)?;
|
||||
},
|
||||
Action::StartOrReloadPlugin(url) => {
|
||||
let run_plugin_location =
|
||||
RunPluginLocation::parse(url.as_str()).with_context(err_context)?;
|
||||
Action::StartOrReloadPlugin(run_plugin) => {
|
||||
senders
|
||||
.send_to_screen(ScreenInstruction::StartOrReloadPluginPane(
|
||||
run_plugin_location,
|
||||
None,
|
||||
))
|
||||
.send_to_screen(ScreenInstruction::StartOrReloadPluginPane(run_plugin, None))
|
||||
.with_context(err_context)?;
|
||||
},
|
||||
Action::LaunchOrFocusPlugin(run_plugin, should_float) => {
|
||||
|
|
|
|||
|
|
@ -260,7 +260,7 @@ pub enum ScreenInstruction {
|
|||
NewTiledPluginPane(RunPluginLocation, Option<String>, ClientId), // Option<String> is
|
||||
// optional pane title
|
||||
NewFloatingPluginPane(RunPluginLocation, Option<String>, ClientId), // Option<String> is an
|
||||
StartOrReloadPluginPane(RunPluginLocation, Option<String>),
|
||||
StartOrReloadPluginPane(RunPlugin, Option<String>),
|
||||
// optional pane title
|
||||
AddPlugin(
|
||||
Option<bool>, // should_float
|
||||
|
|
@ -2573,14 +2573,10 @@ pub(crate) fn screen_thread_main(
|
|||
size,
|
||||
))?;
|
||||
},
|
||||
ScreenInstruction::StartOrReloadPluginPane(run_plugin_location, pane_title) => {
|
||||
ScreenInstruction::StartOrReloadPluginPane(run_plugin, pane_title) => {
|
||||
let tab_index = screen.active_tab_indices.values().next().unwrap_or(&1);
|
||||
let size = Size::default();
|
||||
let should_float = Some(false);
|
||||
let run_plugin = RunPlugin {
|
||||
_allow_exec_host_cmd: false,
|
||||
location: run_plugin_location,
|
||||
};
|
||||
screen
|
||||
.bus
|
||||
.senders
|
||||
|
|
|
|||
|
|
@ -375,7 +375,7 @@ pub enum CliAction {
|
|||
/// Query all tab names
|
||||
QueryTabNames,
|
||||
StartOrReloadPlugin {
|
||||
url: Url,
|
||||
url: String,
|
||||
},
|
||||
LaunchOrFocusPlugin {
|
||||
#[clap(short, long, value_parser)]
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use url::Url;
|
||||
|
||||
use crate::position::Position;
|
||||
|
||||
|
|
@ -233,7 +232,7 @@ pub enum Action {
|
|||
/// Open a new tiled (embedded, non-floating) plugin pane
|
||||
NewTiledPluginPane(RunPluginLocation, Option<String>), // String is an optional name
|
||||
NewFloatingPluginPane(RunPluginLocation, Option<String>), // String is an optional name
|
||||
StartOrReloadPlugin(Url),
|
||||
StartOrReloadPlugin(RunPlugin),
|
||||
}
|
||||
|
||||
impl Action {
|
||||
|
|
@ -287,14 +286,18 @@ impl Action {
|
|||
close_on_exit,
|
||||
start_suspended,
|
||||
} => {
|
||||
let current_dir = get_current_dir();
|
||||
let cwd = cwd
|
||||
.map(|cwd| current_dir.join(cwd))
|
||||
.or_else(|| Some(current_dir));
|
||||
if let Some(plugin) = plugin {
|
||||
if floating {
|
||||
let plugin = RunPluginLocation::parse(&plugin).map_err(|e| {
|
||||
let plugin = RunPluginLocation::parse(&plugin, cwd).map_err(|e| {
|
||||
format!("Failed to parse plugin loction {plugin}: {}", e)
|
||||
})?;
|
||||
Ok(vec![Action::NewFloatingPluginPane(plugin, name)])
|
||||
} else {
|
||||
let plugin = RunPluginLocation::parse(&plugin).map_err(|e| {
|
||||
let plugin = RunPluginLocation::parse(&plugin, cwd).map_err(|e| {
|
||||
format!("Failed to parse plugin location {plugin}: {}", e)
|
||||
})?;
|
||||
// it is intentional that a new tiled plugin pane cannot include a
|
||||
|
|
@ -310,10 +313,6 @@ impl Action {
|
|||
} else if !command.is_empty() {
|
||||
let mut command = command.clone();
|
||||
let (command, args) = (PathBuf::from(command.remove(0)), command);
|
||||
let current_dir = get_current_dir();
|
||||
let cwd = cwd
|
||||
.map(|cwd| current_dir.join(cwd))
|
||||
.or_else(|| Some(current_dir));
|
||||
let hold_on_start = start_suspended;
|
||||
let hold_on_close = !close_on_exit;
|
||||
let run_command_action = RunCommandAction {
|
||||
|
|
@ -474,9 +473,19 @@ impl Action {
|
|||
CliAction::PreviousSwapLayout => Ok(vec![Action::PreviousSwapLayout]),
|
||||
CliAction::NextSwapLayout => Ok(vec![Action::NextSwapLayout]),
|
||||
CliAction::QueryTabNames => Ok(vec![Action::QueryTabNames]),
|
||||
CliAction::StartOrReloadPlugin { url } => Ok(vec![Action::StartOrReloadPlugin(url)]),
|
||||
CliAction::StartOrReloadPlugin { url } => {
|
||||
let current_dir = get_current_dir();
|
||||
let run_plugin_location = RunPluginLocation::parse(&url, Some(current_dir))
|
||||
.map_err(|e| format!("Failed to parse plugin location: {}", e))?;
|
||||
let run_plugin = RunPlugin {
|
||||
location: run_plugin_location,
|
||||
_allow_exec_host_cmd: false,
|
||||
};
|
||||
Ok(vec![Action::StartOrReloadPlugin(run_plugin)])
|
||||
},
|
||||
CliAction::LaunchOrFocusPlugin { url, floating } => {
|
||||
let run_plugin_location = RunPluginLocation::parse(url.as_str())
|
||||
let current_dir = get_current_dir();
|
||||
let run_plugin_location = RunPluginLocation::parse(url.as_str(), Some(current_dir))
|
||||
.map_err(|e| format!("Failed to parse plugin location: {}", e))?;
|
||||
let run_plugin = RunPlugin {
|
||||
location: run_plugin_location,
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ pub enum RunPluginLocation {
|
|||
}
|
||||
|
||||
impl RunPluginLocation {
|
||||
pub fn parse(location: &str) -> Result<Self, PluginsConfigError> {
|
||||
pub fn parse(location: &str, cwd: Option<PathBuf>) -> Result<Self, PluginsConfigError> {
|
||||
let url = Url::parse(location)?;
|
||||
|
||||
let decoded_path = percent_encoding::percent_decode_str(url.path()).decode_utf8_lossy();
|
||||
|
|
@ -233,16 +233,29 @@ impl RunPluginLocation {
|
|||
// Path is absolute, its safe to use URL path.
|
||||
//
|
||||
// This is the case if the scheme and : delimiter are followed by a / slash
|
||||
decoded_path
|
||||
PathBuf::from(decoded_path.as_ref())
|
||||
} else if location.starts_with("file:~") {
|
||||
// Unwrap is safe here since location is a valid URL
|
||||
PathBuf::from(location.strip_prefix("file:").unwrap())
|
||||
} else {
|
||||
// URL dep doesn't handle relative paths with `file` schema properly,
|
||||
// it always makes them absolute. Use raw location string instead.
|
||||
//
|
||||
// Unwrap is safe here since location is a valid URL
|
||||
location.strip_prefix("file:").unwrap().into()
|
||||
let stripped = location.strip_prefix("file:").unwrap();
|
||||
match cwd {
|
||||
Some(cwd) => cwd.join(stripped),
|
||||
None => PathBuf::from(stripped),
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self::File(PathBuf::from(path.as_ref())))
|
||||
let path = match shellexpand::full(&path.to_string_lossy().to_string()) {
|
||||
Ok(s) => PathBuf::from(s.as_ref()),
|
||||
Err(e) => {
|
||||
log::error!("Failed to shell expand plugin path: {}", e);
|
||||
path
|
||||
},
|
||||
};
|
||||
Ok(Self::File(path))
|
||||
},
|
||||
_ => Err(PluginsConfigError::InvalidUrlScheme(url)),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -297,13 +297,14 @@ impl<'a> KdlLayoutParser<'a> {
|
|||
plugin_block.span().len(),
|
||||
),
|
||||
)?;
|
||||
let location = RunPluginLocation::parse(&string_url).map_err(|e| {
|
||||
ConfigError::new_layout_kdl_error(
|
||||
e.to_string(),
|
||||
url_node.span().offset(),
|
||||
url_node.span().len(),
|
||||
)
|
||||
})?;
|
||||
let location =
|
||||
RunPluginLocation::parse(&string_url, self.cwd_prefix(None)?).map_err(|e| {
|
||||
ConfigError::new_layout_kdl_error(
|
||||
e.to_string(),
|
||||
url_node.span().offset(),
|
||||
url_node.span().len(),
|
||||
)
|
||||
})?;
|
||||
Ok(Some(Run::Plugin(RunPlugin {
|
||||
_allow_exec_host_cmd,
|
||||
location,
|
||||
|
|
|
|||
|
|
@ -892,7 +892,8 @@ impl TryFrom<(&KdlNode, &Options)> for Action {
|
|||
let should_float = command_metadata
|
||||
.and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "floating"))
|
||||
.unwrap_or(false);
|
||||
let location = RunPluginLocation::parse(&plugin_path)?;
|
||||
let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
||||
let location = RunPluginLocation::parse(&plugin_path, Some(current_dir))?;
|
||||
let run_plugin = RunPlugin {
|
||||
location,
|
||||
_allow_exec_host_cmd: false,
|
||||
|
|
@ -1402,7 +1403,7 @@ impl Options {
|
|||
}
|
||||
|
||||
impl RunPlugin {
|
||||
pub fn from_kdl(kdl_node: &KdlNode) -> Result<Self, ConfigError> {
|
||||
pub fn from_kdl(kdl_node: &KdlNode, cwd: Option<PathBuf>) -> Result<Self, ConfigError> {
|
||||
let _allow_exec_host_cmd =
|
||||
kdl_get_child_entry_bool_value!(kdl_node, "_allow_exec_host_cmd").unwrap_or(false);
|
||||
let string_url = kdl_get_child_entry_string_value!(kdl_node, "location").ok_or(
|
||||
|
|
@ -1412,7 +1413,7 @@ impl RunPlugin {
|
|||
kdl_node.span().len(),
|
||||
),
|
||||
)?;
|
||||
let location = RunPluginLocation::parse(string_url).map_err(|e| {
|
||||
let location = RunPluginLocation::parse(string_url, cwd).map_err(|e| {
|
||||
ConfigError::new_layout_kdl_error(
|
||||
e.to_string(),
|
||||
kdl_node.span().offset(),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue