aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-server/src
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2024-07-17 16:08:31 +0200
committerGitHub <noreply@github.com>2024-07-17 16:08:31 +0200
commit84ff29dd024c73e94f3e65d56d907ae903fe3752 (patch)
treeb3f01370b3b1af71f018eeb27a1437b75e959721 /zellij-server/src
parentdocs(changelog): scroll region fix (diff)
downloadsl0ppij-84ff29dd024c73e94f3e65d56d907ae903fe3752.tar.gz
sl0ppij-84ff29dd024c73e94f3e65d56d907ae903fe3752.tar.xz
sl0ppij-84ff29dd024c73e94f3e65d56d907ae903fe3752.zip
feat(ui): configuration screen + configure non-colliding keys at runtime (#3502)
* rebind => reconfigure * persist keybinds and mode info to new tabs * add configuration plugin * make tests pass * remove warnings * style(fmt): rustfmt
Diffstat (limited to 'zellij-server/src')
-rw-r--r--zellij-server/src/lib.rs69
-rw-r--r--zellij-server/src/plugins/mod.rs16
-rw-r--r--zellij-server/src/plugins/plugin_loader.rs24
-rw-r--r--zellij-server/src/plugins/plugin_map.rs8
-rw-r--r--zellij-server/src/plugins/unit/plugin_tests.rs10
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__granted_permission_request_result.snap4
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__reconfigure_plugin_command.snap11
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__request_plugin_permissions.snap4
-rw-r--r--zellij-server/src/plugins/wasm_bridge.rs66
-rw-r--r--zellij-server/src/plugins/zellij_exports.rs11
-rw-r--r--zellij-server/src/route.rs46
-rw-r--r--zellij-server/src/screen.rs49
-rw-r--r--zellij-server/src/unit/screen_tests.rs12
13 files changed, 283 insertions, 47 deletions
diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs
index 9a3231b3..fbde2f86 100644
--- a/zellij-server/src/lib.rs
+++ b/zellij-server/src/lib.rs
@@ -97,7 +97,7 @@ pub enum ServerInstruction {
DisconnectAllClientsExcept(ClientId),
ChangeMode(ClientId, InputMode),
ChangeModeForAllClients(InputMode),
- RebindKeys(ClientId, String), // String -> stringified keybindings
+ Reconfigure(ClientId, String), // String -> stringified configuration
}
impl From<&ServerInstruction> for ServerContext {
@@ -129,7 +129,7 @@ impl From<&ServerInstruction> for ServerContext {
ServerInstruction::ChangeModeForAllClients(..) => {
ServerContext::ChangeModeForAllClients
},
- ServerInstruction::RebindKeys(..) => ServerContext::RebindKeys,
+ ServerInstruction::Reconfigure(..) => ServerContext::Reconfigure,
}
}
}
@@ -149,7 +149,7 @@ pub(crate) struct SessionMetaData {
pub config_options: Box<Options>,
pub client_keybinds: HashMap<ClientId, Keybinds>,
pub client_input_modes: HashMap<ClientId, InputMode>,
- pub default_mode: InputMode,
+ pub default_mode: HashMap<ClientId, InputMode>,
screen_thread: Option<thread::JoinHandle<()>>,
pty_thread: Option<thread::JoinHandle<()>>,
plugin_thread: Option<thread::JoinHandle<()>>,
@@ -570,6 +570,10 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
default_mode,
&attrs,
session_data.capabilities,
+ session_data
+ .client_keybinds
+ .get(&client_id)
+ .unwrap_or(&session_data.client_attributes.keybinds),
Some(default_mode),
);
session_data
@@ -911,24 +915,57 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.unwrap()
.change_mode_for_all_clients(input_mode);
},
- ServerInstruction::RebindKeys(client_id, new_keybinds) => {
+ ServerInstruction::Reconfigure(client_id, new_config) => {
+ let mut new_default_mode = None;
+ match Options::from_string(&new_config) {
+ Ok(mut new_config_options) => {
+ if let Some(default_mode) = new_config_options.default_mode.take() {
+ new_default_mode = Some(default_mode);
+ session_data
+ .write()
+ .unwrap()
+ .as_mut()
+ .unwrap()
+ .default_mode
+ .insert(client_id, default_mode);
+ }
+ },
+ Err(e) => {
+ log::error!("Failed to parse config: {}", e);
+ },
+ }
+
let new_keybinds = session_data
.write()
.unwrap()
.as_mut()
.unwrap()
- .rebind_keys(client_id, new_keybinds)
+ .rebind_keys(client_id, new_config)
.clone();
- if let Some(new_keybinds) = new_keybinds {
- session_data
- .write()
- .unwrap()
- .as_ref()
- .unwrap()
- .senders
- .send_to_screen(ScreenInstruction::RebindKeys(new_keybinds, client_id))
- .unwrap();
- }
+ session_data
+ .write()
+ .unwrap()
+ .as_ref()
+ .unwrap()
+ .senders
+ .send_to_screen(ScreenInstruction::Reconfigure {
+ client_id,
+ keybinds: new_keybinds.clone(),
+ default_mode: new_default_mode,
+ })
+ .unwrap();
+ session_data
+ .write()
+ .unwrap()
+ .as_ref()
+ .unwrap()
+ .senders
+ .send_to_plugin(PluginInstruction::Reconfigure {
+ client_id,
+ keybinds: new_keybinds,
+ default_mode: new_default_mode,
+ })
+ .unwrap();
},
}
}
@@ -1167,7 +1204,7 @@ fn init_session(
plugin_thread: Some(plugin_thread),
pty_writer_thread: Some(pty_writer_thread),
background_jobs_thread: Some(background_jobs_thread),
- default_mode,
+ default_mode: HashMap::new(),
}
}
diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs
index 639a389e..223ecb55 100644
--- a/zellij-server/src/plugins/mod.rs
+++ b/zellij-server/src/plugins/mod.rs
@@ -31,6 +31,7 @@ use zellij_utils::{
errors::{prelude::*, ContextType, PluginContext},
input::{
command::TerminalAction,
+ keybinds::Keybinds,
layout::{FloatingPaneLayout, Layout, Run, RunPlugin, RunPluginOrAlias, TiledPaneLayout},
plugins::PluginAliases,
},
@@ -142,6 +143,11 @@ pub enum PluginInstruction {
message: MessageToPlugin,
},
UnblockCliPipes(Vec<PluginRenderAsset>),
+ Reconfigure {
+ client_id: ClientId,
+ keybinds: Option<Keybinds>,
+ default_mode: Option<InputMode>,
+ },
WatchFilesystem,
Exit,
}
@@ -182,6 +188,7 @@ impl From<&PluginInstruction> for PluginContext {
PluginInstruction::WatchFilesystem => PluginContext::WatchFilesystem,
PluginInstruction::KeybindPipe { .. } => PluginContext::KeybindPipe,
PluginInstruction::DumpLayoutToPlugin(..) => PluginContext::DumpLayoutToPlugin,
+ PluginInstruction::Reconfigure { .. } => PluginContext::Reconfigure,
}
}
}
@@ -734,6 +741,15 @@ pub(crate) fn plugin_thread_main(
.context("failed to unblock input pipe");
}
},
+ PluginInstruction::Reconfigure {
+ client_id,
+ keybinds,
+ default_mode,
+ } => {
+ wasm_bridge
+ .reconfigure(client_id, keybinds, default_mode)
+ .non_fatal();
+ },
PluginInstruction::WatchFilesystem => {
wasm_bridge.start_fs_watcher_if_not_started();
},
diff --git a/zellij-server/src/plugins/plugin_loader.rs b/zellij-server/src/plugins/plugin_loader.rs
index 6ed36c05..51ca3e16 100644
--- a/zellij-server/src/plugins/plugin_loader.rs
+++ b/zellij-server/src/plugins/plugin_loader.rs
@@ -29,6 +29,7 @@ use zellij_utils::{
data::{InputMode, PluginCapabilities},
errors::prelude::*,
input::command::TerminalAction,
+ input::keybinds::Keybinds,
input::layout::Layout,
input::plugins::PluginConfig,
ipc::ClientAttributes,
@@ -69,6 +70,7 @@ pub struct PluginLoader<'a> {
default_layout: Box<Layout>,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
+ keybinds: Option<Keybinds>,
}
impl<'a> PluginLoader<'a> {
@@ -89,6 +91,7 @@ impl<'a> PluginLoader<'a> {
default_layout: Box<Layout>,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
+ keybinds: &HashMap<ClientId, Keybinds>,
) -> Result<()> {
let err_context = || format!("failed to reload plugin {plugin_id} from memory");
let mut connected_clients: Vec<ClientId> =
@@ -97,6 +100,7 @@ impl<'a> PluginLoader<'a> {
return Err(anyhow!("No connected clients, cannot reload plugin"));
}
let first_client_id = connected_clients.remove(0);
+ let keybinds = keybinds.get(&first_client_id).cloned();
let mut plugin_loader = PluginLoader::new_from_existing_plugin_attributes(
&plugin_cache,
@@ -115,6 +119,7 @@ impl<'a> PluginLoader<'a> {
default_layout,
layout_dir,
default_mode,
+ keybinds,
)?;
plugin_loader
.load_module_from_memory()
@@ -152,6 +157,7 @@ impl<'a> PluginLoader<'a> {
skip_cache: bool,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
+ keybinds: Option<Keybinds>,
) -> Result<()> {
let err_context = || format!("failed to start plugin {plugin_id} for client {client_id}");
let mut plugin_loader = PluginLoader::new(
@@ -173,6 +179,7 @@ impl<'a> PluginLoader<'a> {
default_layout,
layout_dir,
default_mode,
+ keybinds,
)?;
if skip_cache {
plugin_loader
@@ -226,6 +233,7 @@ impl<'a> PluginLoader<'a> {
default_layout: Box<Layout>,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
+ keybinds: Option<Keybinds>,
) -> Result<()> {
let mut new_plugins = HashSet::new();
for plugin_id in plugin_map.lock().unwrap().plugin_ids() {
@@ -249,6 +257,7 @@ impl<'a> PluginLoader<'a> {
default_layout.clone(),
layout_dir.clone(),
default_mode,
+ keybinds.clone(),
)?;
plugin_loader
.load_module_from_memory()
@@ -278,6 +287,7 @@ impl<'a> PluginLoader<'a> {
default_layout: Box<Layout>,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
+ keybinds: &HashMap<ClientId, Keybinds>,
) -> Result<()> {
let err_context = || format!("failed to reload plugin id {plugin_id}");
@@ -287,6 +297,7 @@ impl<'a> PluginLoader<'a> {
return Err(anyhow!("No connected clients, cannot reload plugin"));
}
let first_client_id = connected_clients.remove(0);
+ let keybinds = keybinds.get(&first_client_id).cloned();
let mut plugin_loader = PluginLoader::new_from_existing_plugin_attributes(
&plugin_cache,
@@ -305,6 +316,7 @@ impl<'a> PluginLoader<'a> {
default_layout,
layout_dir,
default_mode,
+ keybinds,
)?;
plugin_loader
.compile_module()
@@ -338,6 +350,7 @@ impl<'a> PluginLoader<'a> {
default_layout: Box<Layout>,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
+ keybinds: Option<Keybinds>,
) -> Result<Self> {
let plugin_own_data_dir = ZELLIJ_SESSION_CACHE_DIR
.join(Url::from(&plugin.location).to_string())
@@ -366,6 +379,7 @@ impl<'a> PluginLoader<'a> {
default_layout,
layout_dir,
default_mode,
+ keybinds,
})
}
pub fn new_from_existing_plugin_attributes(
@@ -385,6 +399,7 @@ impl<'a> PluginLoader<'a> {
default_layout: Box<Layout>,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
+ keybinds: Option<Keybinds>,
) -> Result<Self> {
let err_context = || "Failed to find existing plugin";
let (running_plugin, _subscriptions, _workers) = {
@@ -420,6 +435,7 @@ impl<'a> PluginLoader<'a> {
default_layout,
layout_dir,
default_mode,
+ keybinds,
)
}
pub fn new_from_different_client_id(
@@ -439,6 +455,7 @@ impl<'a> PluginLoader<'a> {
default_layout: Box<Layout>,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
+ keybinds: Option<Keybinds>,
) -> Result<Self> {
let err_context = || "Failed to find existing plugin";
let running_plugin = {
@@ -475,6 +492,7 @@ impl<'a> PluginLoader<'a> {
default_layout,
layout_dir,
default_mode,
+ keybinds,
)
}
pub fn load_module_from_memory(&mut self) -> Result<Module> {
@@ -731,6 +749,7 @@ impl<'a> PluginLoader<'a> {
self.default_layout.clone(),
self.layout_dir.clone(),
self.default_mode,
+ self.keybinds.clone(),
)?;
plugin_loader_for_client
.load_module_from_memory()
@@ -832,6 +851,11 @@ impl<'a> PluginLoader<'a> {
layout_dir: self.layout_dir.clone(),
default_mode: self.default_mode.clone(),
subscriptions: Arc::new(Mutex::new(HashSet::new())),
+ keybinds: self
+ .keybinds
+ .as_ref()
+ .unwrap_or_else(|| &self.client_attributes.keybinds)
+ .clone(),
stdin_pipe,
stdout_pipe,
};
diff --git a/zellij-server/src/plugins/plugin_map.rs b/zellij-server/src/plugins/plugin_map.rs
index c5ac6d5f..6390841a 100644
--- a/zellij-server/src/plugins/plugin_map.rs
+++ b/zellij-server/src/plugins/plugin_map.rs
@@ -22,6 +22,7 @@ use zellij_utils::{
data::InputMode,
data::PluginCapabilities,
input::command::TerminalAction,
+ input::keybinds::Keybinds,
input::layout::{Layout, PluginUserConfiguration, RunPlugin, RunPluginLocation},
input::plugins::PluginConfig,
ipc::ClientAttributes,
@@ -289,6 +290,7 @@ pub struct PluginEnv {
pub subscriptions: Arc<Mutex<Subscriptions>>,
pub stdin_pipe: Arc<Mutex<VecDeque<u8>>>,
pub stdout_pipe: Arc<Mutex<VecDeque<u8>>>,
+ pub keybinds: Keybinds,
}
#[derive(Clone)]
@@ -424,4 +426,10 @@ impl RunningPlugin {
false
}
}
+ pub fn update_keybinds(&mut self, keybinds: Keybinds) {
+ self.store.data_mut().keybinds = keybinds;
+ }
+ pub fn update_default_mode(&mut self, default_mode: InputMode) {
+ self.store.data_mut().default_mode = default_mode;
+ }
}
diff --git a/zellij-server/src/plugins/unit/plugin_tests.rs b/zellij-server/src/plugins/unit/plugin_tests.rs
index c1f4fcd5..5ef220db 100644
--- a/zellij-server/src/plugins/unit/plugin_tests.rs
+++ b/zellij-server/src/plugins/unit/plugin_tests.rs
@@ -6492,7 +6492,7 @@ pub fn disconnect_other_clients_plugins_command() {
#[test]
#[ignore]
-pub fn rebind_keys_plugin_command() {
+pub fn reconfigure_plugin_command() {
let temp_folder = tempdir().unwrap(); // placed explicitly in the test scope because its
// destructor removes the directory
let plugin_host_folder = PathBuf::from(temp_folder.path());
@@ -6527,7 +6527,7 @@ pub fn rebind_keys_plugin_command() {
let received_server_instruction = Arc::new(Mutex::new(vec![]));
let server_thread = log_actions_in_thread!(
received_server_instruction,
- ServerInstruction::RebindKeys,
+ ServerInstruction::Reconfigure,
server_receiver,
1
);
@@ -6555,20 +6555,20 @@ pub fn rebind_keys_plugin_command() {
std::thread::sleep(std::time::Duration::from_millis(500));
teardown();
server_thread.join().unwrap(); // this might take a while if the cache is cold
- let rebind_keys_event = received_server_instruction
+ let reconfigure_event = received_server_instruction
.lock()
.unwrap()
.iter()
.rev()
.find_map(|i| {
- if let ServerInstruction::RebindKeys(..) = i {
+ if let ServerInstruction::Reconfigure(..) = i {
Some(i.clone())
} else {
None
}
})
.clone();
- assert_snapshot!(format!("{:#?}", rebind_keys_event));
+ assert_snapshot!(format!("{:#?}", reconfigure_event));
}
#[test]
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__granted_permission_request_result.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__granted_permission_request_result.snap
index 465f1088..28c7dedc 100644
--- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__granted_permission_request_result.snap
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__granted_permission_request_result.snap
@@ -1,6 +1,6 @@
---
source: zellij-server/src/plugins/./unit/plugin_tests.rs
-assertion_line: 5500
+assertion_line: 5505
expression: "format!(\"{:#?}\", permissions)"
---
Some(
@@ -14,6 +14,6 @@ Some(
WebAccess,
ReadCliPipes,
MessageAndLaunchOtherPlugins,
- RebindKeys,
+ Reconfigure,
],
)
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__reconfigure_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__reconfigure_plugin_command.snap
new file mode 100644
index 00000000..3f788d7d
--- /dev/null
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__reconfigure_plugin_command.snap
@@ -0,0 +1,11 @@
+---
+source: zellij-server/src/plugins/./unit/plugin_tests.rs
+assertion_line: 6571
+expression: "format!(\"{:#?}\", reconfigure_event)"
+---
+Some(
+ Reconfigure(
+ 1,
+ "\n keybinds {\n locked {\n bind \"a\" { NewTab; }\n }\n }\n ",
+ ),
+)
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__request_plugin_permissions.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__request_plugin_permissions.snap
index a0fec410..75f47da7 100644
--- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__request_plugin_permissions.snap
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__request_plugin_permissions.snap
@@ -1,6 +1,6 @@
---
source: zellij-server/src/plugins/./unit/plugin_tests.rs
-assertion_line: 5409
+assertion_line: 5414
expression: "format!(\"{:#?}\", new_tab_event)"
---
Some(
@@ -16,6 +16,6 @@ Some(
WebAccess,
ReadCliPipes,
MessageAndLaunchOtherPlugins,
- RebindKeys,
+ Reconfigure,
],
)
diff --git a/zellij-server/src/plugins/wasm_bridge.rs b/zellij-server/src/plugins/wasm_bridge.rs
index 2abc22d3..00a7795b 100644
--- a/zellij-server/src/plugins/wasm_bridge.rs
+++ b/zellij-server/src/plugins/wasm_bridge.rs
@@ -21,6 +21,7 @@ use zellij_utils::async_std::task::{self, JoinHandle};
use zellij_utils::consts::ZELLIJ_CACHE_DIR;
use zellij_utils::data::{InputMode, PermissionStatus, PermissionType, PipeMessage, PipeSource};
use zellij_utils::downloader::Downloader;
+use zellij_utils::input::keybinds::Keybinds;
use zellij_utils::input::permission::PermissionCache;
use zellij_utils::notify_debouncer_full::{notify::RecommendedWatcher, Debouncer, FileIdMap};
use zellij_utils::plugin_api::event::ProtobufEvent;
@@ -103,6 +104,7 @@ pub struct WasmBridge {
pending_pipes: PendingPipes,
layout_dir: Option<PathBuf>,
default_mode: InputMode,
+ keybinds: HashMap<ClientId, Keybinds>,
}
impl WasmBridge {
@@ -149,6 +151,7 @@ impl WasmBridge {
pending_pipes: Default::default(),
layout_dir,
default_mode,
+ keybinds: HashMap::new(),
}
}
pub fn load_plugin(
@@ -206,6 +209,7 @@ impl WasmBridge {
let default_layout = self.default_layout.clone();
let layout_dir = self.layout_dir.clone();
let default_mode = self.default_mode;
+ let keybinds = self.keybinds.get(&client_id).cloned();
async move {
let _ = senders.send_to_background_jobs(
BackgroundJob::AnimatePluginLoading(plugin_id),
@@ -254,6 +258,7 @@ impl WasmBridge {
skip_cache,
layout_dir,
default_mode,
+ keybinds,
) {
Ok(_) => handle_plugin_successful_loading(&senders, plugin_id),
Err(e) => handle_plugin_loading_failure(
@@ -346,6 +351,7 @@ impl WasmBridge {
let default_layout = self.default_layout.clone();
let layout_dir = self.layout_dir.clone();
let default_mode = self.default_mode;
+ let keybinds = self.keybinds.clone();
async move {
match PluginLoader::reload_plugin(
first_plugin_id,
@@ -364,6 +370,7 @@ impl WasmBridge {
default_layout.clone(),
layout_dir.clone(),
default_mode,
+ &keybinds,
) {
Ok(_) => {
handle_plugin_successful_loading(&senders, first_plugin_id);
@@ -390,6 +397,7 @@ impl WasmBridge {
default_layout.clone(),
layout_dir.clone(),
default_mode,
+ &keybinds,
) {
Ok(_) => handle_plugin_successful_loading(&senders, *plugin_id),
Err(e) => handle_plugin_loading_failure(
@@ -443,6 +451,7 @@ impl WasmBridge {
self.default_layout.clone(),
self.layout_dir.clone(),
self.default_mode,
+ self.keybinds.get(&client_id).cloned(),
) {
Ok(_) => {
let _ = self
@@ -795,6 +804,51 @@ impl WasmBridge {
.unwrap()
.run_plugin_of_plugin_id(plugin_id)
}
+
+ pub fn reconfigure(
+ &mut self,
+ client_id: ClientId,
+ keybinds: Option<Keybinds>,
+ default_mode: Option<InputMode>,
+ ) -> Result<()> {
+ let plugins_to_reconfigure: Vec<Arc<Mutex<RunningPlugin>>> = self
+ .plugin_map
+ .lock()
+ .unwrap()
+ .running_plugins()
+ .iter()
+ .cloned()
+ .filter_map(|(_plugin_id, c_id, running_plugin)| {
+ if c_id == client_id {
+ Some(running_plugin.clone())
+ } else {
+ None
+ }
+ })
+ .collect();
+ if let Some(default_mode) = default_mode.as_ref() {
+ self.default_mode = *default_mode;
+ }
+ if let Some(keybinds) = keybinds.as_ref() {
+ self.keybinds.insert(client_id, keybinds.clone());
+ }
+ for running_plugin in plugins_to_reconfigure {
+ task::spawn({
+ let running_plugin = running_plugin.clone();
+ let keybinds = keybinds.clone();
+ async move {
+ let mut running_plugin = running_plugin.lock().unwrap();
+ if let Some(keybinds) = keybinds {
+ running_plugin.update_keybinds(keybinds);
+ }
+ if let Some(default_mode) = default_mode {
+ running_plugin.update_default_mode(default_mode);
+ }
+ }
+ });
+ }
+ Ok(())
+ }
fn apply_cached_events_and_resizes_for_plugin(
&mut self,
plugin_id: PluginId,
@@ -1288,6 +1342,18 @@ pub fn apply_event_to_plugin(
let err_context = || format!("Failed to apply event to plugin {plugin_id}");
match check_event_permission(running_plugin.store.data(), event) {
(PermissionStatus::Granted, _) => {
+ let mut event = event.clone();
+ if let Event::ModeUpdate(mode_info) = &mut event {
+ // we do this because there can be some cases where this event arrives here with
+ // the wrong keybindings or default mode (for example: when triggered from the CLI,
+ // where we do not know the target client_id and thus don't know if their keybindings are the
+ // default or if they have changed at runtime), the keybindings in running_plugin
+ // should always be up-to-date. Ideally, we would have changed the keybindings in
+ // ModeInfo to an Option, but alas - this is already part of our contract and that
+ // would be a breaking change.
+ mode_info.keybinds = running_plugin.store.data().keybinds.to_keybinds_vec();
+ mode_info.base_mode = Some(running_plugin.store.data().default_mode);
+ }
let protobuf_event: ProtobufEvent = event
.clone()
.try_into()
diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs
index a517df06..21b8b01d 100644
--- a/zellij-server/src/plugins/zellij_exports.rs
+++ b/zellij-server/src/plugins/zellij_exports.rs
@@ -61,6 +61,7 @@ macro_rules! apply_action {
$env.default_shell.clone(),
$env.default_layout.clone(),
None,
+ $env.keybinds.clone(),
$env.default_mode.clone(),
) {
log::error!("{}: {:?}", $error_message(), e);
@@ -244,7 +245,7 @@ fn host_run_plugin_command(caller: Caller<'_, PluginEnv>) {
PluginCommand::WatchFilesystem => watch_filesystem(env),
PluginCommand::DumpSessionLayout => dump_session_layout(env),
PluginCommand::CloseSelf => close_self(env),
- PluginCommand::RebindKeys(new_keybinds) => rebind_keys(env, new_keybinds)?,
+ PluginCommand::Reconfigure(new_config) => reconfigure(env, new_config)?,
},
(PermissionStatus::Denied, permission) => {
log::error!(
@@ -781,11 +782,11 @@ fn close_self(env: &PluginEnv) {
.non_fatal();
}
-fn rebind_keys(env: &PluginEnv, new_keybinds: String) -> Result<()> {
- let err_context = || "Failed to rebind keys";
+fn reconfigure(env: &PluginEnv, new_config: String) -> Result<()> {
+ let err_context = || "Failed to reconfigure";
let client_id = env.client_id;
env.senders
- .send_to_server(ServerInstruction::RebindKeys(client_id, new_keybinds))
+ .send_to_server(ServerInstruction::Reconfigure(client_id, new_config))
.with_context(err_context)?;
Ok(())
}
@@ -1446,7 +1447,7 @@ fn check_command_permission(
| PluginCommand::CliPipeOutput(..) => PermissionType::ReadCliPipes,
PluginCommand::MessageToPlugin(..) => PermissionType::MessageAndLaunchOtherPlugins,
PluginCommand::DumpSessionLayout => PermissionType::ReadApplicationState,
- PluginCommand::RebindKeys(..) => PermissionType::RebindKeys,
+ PluginCommand::Reconfigure(..) => PermissionType::Reconfigure,
_ => return (PermissionStatus::Granted, None),
};
diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs
index 3092310a..ab5db049 100644
--- a/zellij-server/src/route.rs
+++ b/zellij-server/src/route.rs
@@ -19,6 +19,7 @@ use zellij_utils::{
actions::{Action, SearchDirection, SearchOption},
command::TerminalAction,
get_mode_info,
+ keybinds::Keybinds,
layout::Layout,
},
ipc::{
@@ -38,6 +39,7 @@ pub(crate) fn route_action(
default_shell: Option<TerminalAction>,
default_layout: Box<Layout>,
mut seen_cli_pipes: Option<&mut HashSet<String>>,
+ client_keybinds: Keybinds,
default_mode: InputMode,
) -> Result<bool> {
let mut should_break = false;
@@ -99,7 +101,13 @@ pub(crate) fn route_action(
.send_to_plugin(PluginInstruction::Update(vec![(
None,
Some(client_id),
- Event::ModeUpdate(get_mode_info(mode, attrs, capabilities, Some(default_mode))),
+ Event::ModeUpdate(get_mode_info(
+ mode,
+ attrs,
+ capabilities,
+ &client_keybinds,
+ Some(default_mode),
+ )),
)]))
.with_context(err_context)?;
senders
@@ -107,7 +115,13 @@ pub(crate) fn route_action(
.with_context(err_context)?;
senders
.send_to_screen(ScreenInstruction::ChangeMode(
- get_mode_info(mode, attrs, capabilities, Some(default_mode)),
+ get_mode_info(
+ mode,
+ attrs,
+ capabilities,
+ &client_keybinds,
+ Some(default_mode),
+ ),
client_id,
))
.with_context(err_context)?;
@@ -349,6 +363,7 @@ pub(crate) fn route_action(
input_mode,
attrs,
capabilities,
+ &client_keybinds,
Some(default_mode),
)),
)]))
@@ -363,6 +378,7 @@ pub(crate) fn route_action(
input_mode,
attrs,
capabilities,
+ &client_keybinds,
Some(default_mode),
)))
.with_context(err_context)?;
@@ -1025,7 +1041,20 @@ pub(crate) fn route_thread_main(
rlocked_sessions.default_shell.clone(),
rlocked_sessions.layout.clone(),
Some(&mut seen_cli_pipes),
- rlocked_sessions.default_mode,
+ rlocked_sessions
+ .client_keybinds
+ .get(&client_id)
+ .unwrap_or(
+ &rlocked_sessions
+ .client_attributes
+ .keybinds,
+ )
+ .clone(),
+ rlocked_sessions
+ .default_mode
+ .get(&client_id)
+ .unwrap_or(&InputMode::Normal)
+ .clone(),
)? {
should_break = true;
}
@@ -1050,7 +1079,16 @@ pub(crate) fn route_thread_main(
rlocked_sessions.default_shell.clone(),
rlocked_sessions.layout.clone(),
Some(&mut seen_cli_pipes),
- rlocked_sessions.default_mode,
+ rlocked_sessions
+ .client_keybinds
+ .get(&client_id)
+ .unwrap_or(&rlocked_sessions.client_attributes.keybinds)
+ .clone(),
+ rlocked_sessions
+ .default_mode
+ .get(&client_id)
+ .unwrap_or(&InputMode::Normal)
+ .clone(),
)? {
should_break = true;
}
diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs
index e61b5d19..189c2571 100644
--- a/zellij-server/src/screen.rs
+++ b/zellij-server/src/screen.rs
@@ -361,7 +361,11 @@ pub enum ScreenInstruction {
DumpLayoutToHd,
RenameSession(String, ClientId), // String -> new name
ListClientsMetadata(Option<PathBuf>, ClientId), // Option<PathBuf> - default shell
- RebindKeys(Keybinds, ClientId),
+ Reconfigure {
+ client_id: ClientId,
+ keybinds: Option<Keybinds>,
+ default_mode: Option<InputMode>,
+ },
}
impl From<&ScreenInstruction> for ScreenContext {
@@ -546,7 +550,7 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::DumpLayoutToHd => ScreenContext::DumpLayoutToHd,
ScreenInstruction::RenameSession(..) => ScreenContext::RenameSession,
ScreenInstruction::ListClientsMetadata(..) => ScreenContext::ListClientsMetadata,
- ScreenInstruction::RebindKeys(..) => ScreenContext::RebindKeys,
+ ScreenInstruction::Reconfigure { .. } => ScreenContext::Reconfigure,
}
}
}
@@ -1214,7 +1218,7 @@ impl Screen {
let tab_name = tab_name.unwrap_or_else(|| String::new());
let position = self.tabs.len();
- let tab = Tab::new(
+ let mut tab = Tab::new(
tab_index,
position,
tab_name,
@@ -1245,6 +1249,9 @@ impl Screen {
self.styled_underlines,
self.explicitly_disable_kitty_keyboard_protocol,
);
+ for (client_id, mode_info) in &self.mode_info {
+ tab.change_mode_info(mode_info.clone(), *client_id);
+ }
self.tabs.insert(tab_index, tab);
Ok(())
}
@@ -2151,17 +2158,30 @@ impl Screen {
}
Ok(())
}
- pub fn rebind_keys(&mut self, new_keybinds: Keybinds, client_id: ClientId) -> Result<()> {
+ pub fn reconfigure_mode_info(
+ &mut self,
+ new_keybinds: Option<Keybinds>,
+ new_default_mode: Option<InputMode>,
+ client_id: ClientId,
+ ) -> Result<()> {
if self.connected_clients_contains(&client_id) {
+ let should_update_mode_info = new_keybinds.is_some() || new_default_mode.is_some();
let mode_info = self
.mode_info
.entry(client_id)
.or_insert_with(|| self.default_mode_info.clone());
- mode_info.update_keybinds(new_keybinds);
- for tab in self.tabs.values_mut() {
- tab.change_mode_info(mode_info.clone(), client_id);
- tab.mark_active_pane_for_rerender(client_id);
- tab.update_input_modes()?;
+ if let Some(new_keybinds) = new_keybinds {
+ mode_info.update_keybinds(new_keybinds);
+ }
+ if let Some(new_default_mode) = new_default_mode {
+ mode_info.update_default_mode(new_default_mode);
+ }
+ if should_update_mode_info {
+ for tab in self.tabs.values_mut() {
+ tab.change_mode_info(mode_info.clone(), client_id);
+ tab.mark_active_pane_for_rerender(client_id);
+ tab.update_input_modes()?;
+ }
}
} else {
log::error!("Could not find client_id {client_id} to rebind keys");
@@ -2363,6 +2383,7 @@ pub(crate) fn screen_thread_main(
// ¯\_(ツ)_/¯
arrow_fonts: !arrow_fonts,
},
+ &client_attributes.keybinds,
config_options.default_mode,
),
draw_pane_frames,
@@ -4028,8 +4049,14 @@ pub(crate) fn screen_thread_main(
}
screen.unblock_input()?;
},
- ScreenInstruction::RebindKeys(new_keybinds, client_id) => {
- screen.rebind_keys(new_keybinds, client_id).non_fatal();
+ ScreenInstruction::Reconfigure {
+ client_id,
+ keybinds,
+ default_mode,
+ } => {
+ screen
+ .reconfigure_mode_info(keybinds, default_mode, client_id)
+ .non_fatal();
},
}
}
diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs
index 5f7f0a66..09d2d794 100644
--- a/zellij-server/src/unit/screen_tests.rs
+++ b/zellij-server/src/unit/screen_tests.rs
@@ -114,6 +114,7 @@ fn send_cli_action_to_server(
let get_current_dir = || PathBuf::from(".");
let actions = Action::actions_from_cli(cli_action, Box::new(get_current_dir), None).unwrap();
let senders = session_metadata.senders.clone();
+ let client_keybinds = session_metadata.client_keybinds.clone();
let default_mode = session_metadata.default_mode.clone();
let capabilities = PluginCapabilities::default();
let client_attributes = ClientAttributes::default();
@@ -130,7 +131,14 @@ fn send_cli_action_to_server(
default_shell.clone(),
default_layout.clone(),
None,
- default_mode,
+ client_keybinds
+ .get(&client_id)
+ .unwrap_or(&session_metadata.client_attributes.keybinds)
+ .clone(),
+ default_mode
+ .get(&client_id)
+ .unwrap_or(&InputMode::Normal)
+ .clone(),
)
.unwrap();
}
@@ -578,7 +586,7 @@ impl MockScreen {
layout,
client_input_modes: HashMap::new(),
client_keybinds: HashMap::new(),
- default_mode: InputMode::Normal,
+ default_mode: HashMap::new(),
};
let os_input = FakeInputOutput::default();