summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2021-12-08 18:41:41 +0100
committerGitHub <noreply@github.com>2021-12-08 18:41:41 +0100
commit92bddf1b798ec4ecfce8831552e1f218273bb50f (patch)
treeaa65624c877a1407ede75a414461559e0cbb530f
parent56e85f87d6c365816cca71c496aa7e49709e0b11 (diff)
fix(plugins): support multiple users (#930)
* fix(plugins): support multiple clients * fix(style): make clippy happy
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap2
-rw-r--r--zellij-server/src/lib.rs41
-rw-r--r--zellij-server/src/panes/plugin_pane.rs12
-rw-r--r--zellij-server/src/panes/terminal_pane.rs3
-rw-r--r--zellij-server/src/route.rs15
-rw-r--r--zellij-server/src/screen.rs49
-rw-r--r--zellij-server/src/tab.rs117
-rw-r--r--zellij-server/src/ui/pane_contents_and_ui.rs31
-rw-r--r--zellij-server/src/unit/tab_tests.rs1
-rw-r--r--zellij-server/src/wasm_vm.rs123
-rw-r--r--zellij-utils/src/errors.rs2
11 files changed, 305 insertions, 91 deletions
diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap
index 7dbfbbd1e..bfb3fcd9d 100644
--- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap
+++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap
@@ -26,4 +26,4 @@ expression: first_runner_snapshot
│ ││ │
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SCROLL  <o> SESSION  <q> QUIT 
- <←↓↑→> Move focus / <n> New / <x> Close / <r> Rename / <s> Sync / <Tab> Toggle / <ENTER> Select pane
+ Tip: Alt + <n> => new pane. Alt + <[] or hjkl> => navigate. Alt + <+-> => resize pane.
diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs
index 391a03f6f..6a94742ca 100644
--- a/zellij-server/src/lib.rs
+++ b/zellij-server/src/lib.rs
@@ -318,6 +318,14 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
} else {
spawn_tabs(None);
}
+ session_data
+ .read()
+ .unwrap()
+ .as_ref()
+ .unwrap()
+ .senders
+ .send_to_plugin(PluginInstruction::AddClient(client_id))
+ .unwrap();
}
ServerInstruction::AttachClient(attrs, options, client_id) => {
let rlock = session_data.read().unwrap();
@@ -339,6 +347,10 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.senders
.send_to_screen(ScreenInstruction::AddClient(client_id))
.unwrap();
+ session_data
+ .senders
+ .send_to_plugin(PluginInstruction::AddClient(client_id))
+ .unwrap();
let default_mode = options.default_mode.unwrap_or_default();
let mode_info =
get_mode_info(default_mode, attrs.palette, session_data.capabilities);
@@ -351,12 +363,11 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.senders
.send_to_plugin(PluginInstruction::Update(
None,
+ Some(client_id),
Event::ModeUpdate(mode_info),
))
.unwrap();
- for client_id in session_state.read().unwrap().clients.keys() {
- os_input.send_to_client(*client_id, ServerToClientMsg::SwitchToMode(mode));
- }
+ os_input.send_to_client(client_id, ServerToClientMsg::SwitchToMode(mode));
}
ServerInstruction::UnblockInputThread => {
for client_id in session_state.read().unwrap().clients.keys() {
@@ -385,6 +396,14 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.senders
.send_to_screen(ScreenInstruction::RemoveClient(client_id))
.unwrap();
+ session_data
+ .write()
+ .unwrap()
+ .as_ref()
+ .unwrap()
+ .senders
+ .send_to_plugin(PluginInstruction::RemoveClient(client_id))
+ .unwrap();
}
if session_state.read().unwrap().clients.is_empty() {
*session_data.write().unwrap() = None;
@@ -412,6 +431,14 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.senders
.send_to_screen(ScreenInstruction::RemoveClient(client_id))
.unwrap();
+ session_data
+ .write()
+ .unwrap()
+ .as_ref()
+ .unwrap()
+ .senders
+ .send_to_plugin(PluginInstruction::RemoveClient(client_id))
+ .unwrap();
}
}
ServerInstruction::KillSession => {
@@ -444,6 +471,14 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.senders
.send_to_screen(ScreenInstruction::RemoveClient(client_id))
.unwrap();
+ session_data
+ .write()
+ .unwrap()
+ .as_ref()
+ .unwrap()
+ .senders
+ .send_to_plugin(PluginInstruction::RemoveClient(client_id))
+ .unwrap();
}
}
ServerInstruction::Render(mut output) => {
diff --git a/zellij-server/src/panes/plugin_pane.rs b/zellij-server/src/panes/plugin_pane.rs
index e34145f45..a9d5d3e5c 100644
--- a/zellij-server/src/panes/plugin_pane.rs
+++ b/zellij-server/src/panes/plugin_pane.rs
@@ -131,7 +131,10 @@ impl Pane for PluginPane {
fn set_selectable(&mut self, selectable: bool) {
self.selectable = selectable;
}
- fn render(&mut self) -> Option<String> {
+ fn render(&mut self, client_id: Option<ClientId>) -> Option<String> {
+ // this is a bit of a hack but works in a pinch
+ client_id?;
+ let client_id = client_id.unwrap();
// if self.should_render {
if true {
// while checking should_render rather than rendering each pane every time
@@ -145,6 +148,7 @@ impl Pane for PluginPane {
.send(PluginInstruction::Render(
buf_tx,
self.pid,
+ client_id,
self.get_content_rows(),
self.get_content_columns(),
))
@@ -274,6 +278,7 @@ impl Pane for PluginPane {
self.send_plugin_instructions
.send(PluginInstruction::Update(
Some(self.pid),
+ None,
Event::Mouse(Mouse::ScrollUp(count)),
))
.unwrap();
@@ -282,6 +287,7 @@ impl Pane for PluginPane {
self.send_plugin_instructions
.send(PluginInstruction::Update(
Some(self.pid),
+ None,
Event::Mouse(Mouse::ScrollDown(count)),
))
.unwrap();
@@ -293,6 +299,7 @@ impl Pane for PluginPane {
self.send_plugin_instructions
.send(PluginInstruction::Update(
Some(self.pid),
+ None,
Event::Mouse(Mouse::LeftClick(start.line.0, start.column.0)),
))
.unwrap();
@@ -301,6 +308,7 @@ impl Pane for PluginPane {
self.send_plugin_instructions
.send(PluginInstruction::Update(
Some(self.pid),
+ None,
Event::Mouse(Mouse::Hold(position.line.0, position.column.0)),
))
.unwrap();
@@ -309,6 +317,7 @@ impl Pane for PluginPane {
self.send_plugin_instructions
.send(PluginInstruction::Update(
Some(self.pid),
+ None,
Event::Mouse(Mouse::Release(
end.map(|Position { line, column }| (line.0, column.0)),
)),
@@ -342,6 +351,7 @@ impl Pane for PluginPane {
self.send_plugin_instructions
.send(PluginInstruction::Update(
Some(self.pid),
+ None,
Event::Mouse(Mouse::RightClick(to.line.0, to.column.0)),
))
.unwrap();
diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs
index f2e340324..a6da76709 100644
--- a/zellij-server/src/panes/terminal_pane.rs
+++ b/zellij-server/src/panes/terminal_pane.rs
@@ -183,7 +183,8 @@ impl Pane for TerminalPane {
fn set_selectable(&mut self, selectable: bool) {
self.selectable = selectable;
}
- fn render(&mut self) -> Option<String> {
+ fn render(&mut self, _client_id: Option<ClientId>) -> Option<String> {
+ // we don't use client_id because terminal panes render the same for all users
if self.should_render() {
let mut vte_output = String::new();
let mut character_styles = CharacterStyles::new();
diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs
index 8bb82c124..e24d2d4f1 100644
--- a/zellij-server/src/route.rs
+++ b/zellij-server/src/route.rs
@@ -31,7 +31,11 @@ fn route_action(
let mut should_break = false;
session
.senders
- .send_to_plugin(PluginInstruction::Update(None, Event::InputReceived))
+ .send_to_plugin(PluginInstruction::Update(
+ None,
+ Some(client_id),
+ Event::InputReceived,
+ ))
.unwrap();
match action {
Action::ToggleTab => {
@@ -70,6 +74,7 @@ fn route_action(
.senders
.send_to_plugin(PluginInstruction::Update(
None,
+ Some(client_id),
Event::ModeUpdate(get_mode_info(mode, palette, session.capabilities)),
))
.unwrap();
@@ -379,12 +384,8 @@ pub(crate) fn route_thread_main(
ClientToServerMsg::Action(action) => {
if let Some(rlocked_sessions) = rlocked_sessions.as_ref() {
if let Action::SwitchToMode(input_mode) = action {
- for client_id in session_state.read().unwrap().clients.keys() {
- os_input.send_to_client(
- *client_id,
- ServerToClientMsg::SwitchToMode(input_mode),
- );
- }
+ os_input
+ .send_to_client(client_id, ServerToClientMsg::SwitchToMode(input_mode));
}
if route_action(action, rlocked_sessions, &*os_input, &to_server, client_id) {
break;
diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs
index 48b577925..1400235ea 100644
--- a/zellij-server/src/screen.rs
+++ b/zellij-server/src/screen.rs
@@ -183,7 +183,8 @@ pub(crate) struct Screen {
/// The indices of this [`Screen`]'s active [`Tab`]s.
active_tab_indices: BTreeMap<ClientId, usize>,
tab_history: BTreeMap<ClientId, Vec<usize>>,
- mode_info: ModeInfo,
+ mode_info: BTreeMap<ClientId, ModeInfo>,
+ default_mode_info: ModeInfo, // TODO: restructure ModeInfo to prevent this duplication
colors: Palette,
draw_pane_frames: bool,
}
@@ -206,7 +207,8 @@ impl Screen {
tabs: BTreeMap::new(),
overlay: OverlayWindow::default(),
tab_history: BTreeMap::new(),
- mode_info,
+ mode_info: BTreeMap::new(),
+ default_mode_info: mode_info,
draw_pane_frames,
}
}
@@ -241,12 +243,15 @@ impl Screen {
}
}
fn move_clients(&mut self, source_index: usize, destination_index: usize) {
- let connected_clients_in_source_tab = {
+ let (connected_clients_in_source_tab, client_mode_infos_in_source_tab) = {
let source_tab = self.tabs.get_mut(&source_index).unwrap();
source_tab.drain_connected_clients()
};
let destination_tab = self.tabs.get_mut(&destination_index).unwrap();
- destination_tab.add_multiple_clients(&connected_clients_in_source_tab);
+ destination_tab.add_multiple_clients(
+ connected_clients_in_source_tab,
+ client_mode_infos_in_source_tab,
+ );
}
/// A helper function to switch to a new tab at specified position.
fn switch_active_tab(&mut self, new_tab_pos: usize, client_id: ClientId) {
@@ -423,6 +428,11 @@ impl Screen {
pub fn new_tab(&mut self, layout: Layout, new_pids: Vec<RawFd>, client_id: ClientId) {
let tab_index = self.get_new_tab_index();
let position = self.tabs.len();
+ let client_mode_info = self
+ .mode_info
+ .get(&client_id)
+ .unwrap_or(&self.default_mode_info)
+ .clone();
let mut tab = Tab::new(
tab_index,
position,
@@ -431,16 +441,20 @@ impl Screen {
self.bus.os_input.as_ref().unwrap().clone(),
self.bus.senders.clone(),
self.max_panes,
- self.mode_info.clone(),
+ client_mode_info,
self.colors,
self.draw_pane_frames,
client_id,
);
- tab.apply_layout(layout, new_pids, tab_index);
+ tab.apply_layout(layout, new_pids, tab_index, client_id);
if let Some(active_tab) = self.get_active_tab_mut(client_id) {
active_tab.visible(false);
- let connected_clients = active_tab.drain_connected_clients();
- tab.add_multiple_clients(&connected_clients);
+ let (connected_clients_in_source_tab, client_mode_infos_in_source_tab) =
+ active_tab.drain_connected_clients();
+ tab.add_multiple_clients(
+ connected_clients_in_source_tab,
+ client_mode_infos_in_source_tab,
+ );
}
for (client_id, tab_history) in &mut self.tab_history {
let old_active_index = self.active_tab_indices.remove(client_id).unwrap();
@@ -448,6 +462,7 @@ impl Screen {
tab_history.retain(|&e| e != tab_index);
tab_history.push(old_active_index);
}
+ tab.update_input_modes();
tab.visible(true);
self.tabs.insert(tab_index, tab);
if !self.active_tab_indices.contains_key(&client_id) {
@@ -503,7 +518,11 @@ impl Screen {
}
self.bus
.senders
- .send_to_plugin(PluginInstruction::Update(None, Event::TabUpdate(tab_data)))
+ .send_to_plugin(PluginInstruction::Update(
+ None,
+ None,
+ Event::TabUpdate(tab_data),
+ ))
.unwrap();
}
}
@@ -526,7 +545,12 @@ impl Screen {
self.update_tabs();
}
pub fn change_mode(&mut self, mode_info: ModeInfo, client_id: ClientId) {
- if self.mode_info.mode == InputMode::Scroll
+ let previous_mode = self
+ .mode_info
+ .get(&client_id)
+ .unwrap_or(&self.default_mode_info)
+ .mode;
+ if previous_mode == InputMode::Scroll
&& (mode_info.mode == InputMode::Normal || mode_info.mode == InputMode::Locked)
{
self.get_active_tab_mut(client_id)
@@ -534,9 +558,9 @@ impl Screen {
.clear_active_terminal_scroll(client_id);
}
self.colors = mode_info.palette;
- self.mode_info = mode_info;
+ self.mode_info.insert(client_id, mode_info.clone());
for tab in self.tabs.values_mut() {
- tab.mode_info = self.mode_info.clone();
+ tab.change_mode_info(mode_info.clone(), client_id);
tab.mark_active_pane_for_rerender(client_id);
}
}
@@ -1100,6 +1124,7 @@ pub(crate) fn screen_thread_main(
}
ScreenInstruction::AddClient(client_id) => {
screen.add_client(client_id);
+ screen.update_tabs();
screen.render();
}
diff --git a/zellij-server/src/tab.rs b/zellij-server/src/tab.rs
index dec0c0977..f684b9962 100644
--- a/zellij-server/src/tab.rs
+++ b/zellij-server/src/tab.rs
@@ -137,7 +137,8 @@ pub(crate) struct Tab {
pub senders: ThreadSenders,
synchronize_is_active: bool,
should_clear_display_before_rendering: bool,
- pub mode_info: ModeInfo,
+ mode_info: HashMap<ClientId, ModeInfo>,
+ default_mode_info: ModeInfo,
pub colors: Palette,
connected_clients: HashSet<ClientId>,
draw_pane_frames: bool,
@@ -179,7 +180,7 @@ pub trait Pane {
fn set_should_render_boundaries(&mut self, _should_render: bool) {}
fn selectable(&self) -> bool;
fn set_selectable(&mut self, selectable: bool);
- fn render(&mut self) -> Option<String>;
+ fn render(&mut self, client_id: Option<ClientId>) -> Option<String>;
fn render_frame(&mut self, client_id: ClientId, frame_params: FrameParams) -> Option<String>;
fn render_fake_cursor(
&mut self,
@@ -342,7 +343,8 @@ impl Tab {
os_api,
senders,
should_clear_display_before_rendering: false,
- mode_info,
+ mode_info: HashMap::new(),
+ default_mode_info: mode_info,
colors,
draw_pane_frames,
// at the moment this is hard-coded while the feature is being developed
@@ -353,7 +355,13 @@ impl Tab {
}
}
- pub fn apply_layout(&mut self, layout: Layout, new_pids: Vec<RawFd>, tab_index: usize) {
+ pub fn apply_layout(
+ &mut self,
+ layout: Layout,
+ new_pids: Vec<RawFd>,
+ tab_index: usize,
+ client_id: ClientId,
+ ) {
// TODO: this should be an attribute on Screen instead of full_screen_ws
let free_space = PaneGeom::default();
self.panes_to_hide.clear();
@@ -384,7 +392,7 @@ impl Tab {
let (pid_tx, pid_rx) = channel();
let pane_title = run.location.to_string();
self.senders
- .send_to_plugin(PluginInstruction::Load(pid_tx, run, tab_index))
+ .send_to_plugin(PluginInstruction::Load(pid_tx, run, tab_index, client_id))
.unwrap();
let pid = pid_rx.recv().unwrap();
let mut new_plugin = PluginPane::new(
@@ -395,13 +403,6 @@ impl Tab {
);
new_plugin.set_borderless(layout.borderless);
self.panes.insert(PaneId::Plugin(pid), Box::new(new_plugin));
- // Send an initial mode update to the newly loaded plugin only!
- self.senders
- .send_to_plugin(PluginInstruction::Update(
- Some(pid),
- Event::ModeUpdate(self.mode_info.clone()),
- ))
- .unwrap();
} else {
// there are still panes left to fill, use the pids we received in this method
let pid = new_pids.next().unwrap(); // if this crashes it means we got less pids than there are panes in this layout
@@ -465,12 +466,30 @@ impl Tab {
}
}
}
+ pub fn update_input_modes(&mut self) {
+ // this updates all plugins with the client's input mode
+ for client_id in &self.connected_clients {
+ let mode_info = self
+ .mode_info
+ .get(client_id)
+ .unwrap_or(&self.default_mode_info);
+ self.senders
+ .send_to_plugin(PluginInstruction::Update(
+ None,
+ Some(*client_id),
+ Event::ModeUpdate(mode_info.clone()),
+ ))
+ .unwrap();
+ }
+ }
pub fn add_client(&mut self, client_id: ClientId) {
match self.connected_clients.iter().next() {
Some(first_client_id) => {
let first_active_pane_id = *self.active_panes.get(first_client_id).unwrap();
self.connected_clients.insert(client_id);
self.active_panes.insert(client_id, first_active_pane_id);
+ self.mode_info
+ .insert(client_id, self.default_mode_info.clone());
}
None => {
let mut pane_ids: Vec<PaneId> = self.panes.keys().copied().collect();
@@ -482,15 +501,32 @@ impl Tab {
let first_pane_id = pane_ids.get(0).unwrap();
self.connected_clients.insert(client_id);
self.active_panes.insert(client_id, *first_pane_id);
+ self.mode_info
+ .insert(client_id, self.default_mode_info.clone());
}
}
// TODO: we might be able to avoid this, we do this so that newly connected clients will
// necessarily get a full render
self.set_force_render();
}
- pub fn add_multiple_clients(&mut self, client_ids: &[ClientId]) {
+ pub fn change_mode_info(&mut self, mode_info: ModeInfo, client_id: ClientId) {
+ self.mode_info.insert(client_id, mode_info);
+ }
+ pub fn add_multiple_clients(
+ &mut self,
+ client_ids: Vec<ClientId>,
+ client_mode_infos: Vec<(ClientId, ModeInfo)>,
+ ) {
for client_id in client_ids {
- self.add_client(*client_id);
+ self.add_client(client_id);
+ }
+ for (client_id, client_mode_info) in client_mode_infos {
+ log::info!(
+ "add_multiple_clients: client_id: {:?}, mode: {:?}",
+ client_id,
+ client_mode_info.mode
+ );
+ self.mode_info.insert(client_id, client_mode_info);
}
}
pub fn remove_client(&mut self, client_id: ClientId) {
@@ -498,8 +534,12 @@ impl Tab {
self.active_panes.remove(&client_id);
self.set_force_render();
}
- pub fn drain_connected_clients(&mut self) -> Vec<ClientId> {
- self.connected_clients.drain().collect()
+ pub fn drain_connected_clients(&mut self) -> (Vec<ClientId>, Vec<(ClientId, ModeInfo)>) {
+ let client_mode_info = self.mode_info.drain();
+ (
+ self.connected_clients.drain().collect(),
+ client_mode_info.collect(),
+ )
}
pub fn new_pane(&mut self, pid: PaneId, client_id: Option<ClientId>) {
self.close_down_to_max_terminals();
@@ -750,7 +790,7 @@ impl Tab {
PaneId::Plugin(pid) => {
for key in parse_keys(&input_bytes) {
self.senders
- .send_to_plugin(PluginInstruction::Update(Some(pid), Event::Key(key)))
+ .send_to_plugin(PluginInstruction::Update(Some(pid), None, Event::Key(key)))
.unwrap()
}
}
@@ -926,25 +966,35 @@ impl Tab {
let mut client_id_to_boundaries: HashMap<ClientId, Boundaries> = HashMap::new();
self.hide_cursor_and_clear_display_as_needed(output);
// render panes and their frames
- for pane in self.panes.values_mut() {
+ for (kind, pane) in self.panes.iter_mut() {
if !self.panes_to_hide.contains(&pane.pid()) {
- let mut pane_contents_and_ui = PaneContentsAndUi::new(
- pane,
- output,
- self.colors,
- &self.active_panes,
- self.mode_info.mode,
- );
- pane_contents_and_ui.render_pane_contents_for_all_clients();
+ let mut pane_contents_and_ui =
+ PaneContentsAndUi::new(pane, output, self.colors, &self.active_panes);
+ if let PaneId::Terminal(..) = kind {
+ pane_contents_and_ui.render_pane_contents_for_all_clients();
+ }
for &client_id in &self.connected_clients {
+ let client_mode = self
+ .mode_info
+ .get(&client_id)
+ .unwrap_or(&self.default_mode_info)
+ .mode;
+ if let PaneId::Plugin(..) = kind {
+ pane_contents_and_ui.render_pane_contents_for_client(client_id);
+ }
if self.draw_pane_frames {
- pane_contents_and_ui.render_pane_frame(client_id, self.session_is_mirrored);
+ pane_contents_and_ui.render_pane_frame(
+ client_id,
+ client_mode,
+ self.session_is_mirrored,
+ );
} else {
let boundaries = client_id_to_boundaries
.entry(client_id)
.or_insert_with(|| Boundaries::new(self.viewport));
pane_contents_and_ui.render_pane_boundaries(