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