fix(plugins): support multiple users (#930)
* fix(plugins): support multiple clients * fix(style): make clippy happy
This commit is contained in:
parent
56e85f87d6
commit
92bddf1b79
11 changed files with 305 additions and 91 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
client_id,
|
||||
client_mode,
|
||||
boundaries,
|
||||
self.session_is_mirrored,
|
||||
);
|
||||
|
|
@ -3325,7 +3375,11 @@ impl Tab {
|
|||
if let Some(selected_text) = selected_text {
|
||||
self.write_selection_to_clipboard(&selected_text);
|
||||
self.senders
|
||||
.send_to_plugin(PluginInstruction::Update(None, Event::CopyToClipboard))
|
||||
.send_to_plugin(PluginInstruction::Update(
|
||||
None,
|
||||
None,
|
||||
Event::CopyToClipboard,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
|
@ -3343,7 +3397,11 @@ impl Tab {
|
|||
.send_to_server(ServerInstruction::Render(Some(output)))
|
||||
.unwrap();
|
||||
self.senders
|
||||
.send_to_plugin(PluginInstruction::Update(None, Event::CopyToClipboard))
|
||||
.send_to_plugin(PluginInstruction::Update(
|
||||
None,
|
||||
None,
|
||||
Event::CopyToClipboard,
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
fn is_inside_viewport(&self, pane_id: &PaneId) -> bool {
|
||||
|
|
@ -3387,6 +3445,7 @@ impl Tab {
|
|||
self.senders
|
||||
.send_to_plugin(PluginInstruction::Update(
|
||||
Some(*pid),
|
||||
None,
|
||||
Event::Visible(visible),
|
||||
))
|
||||
.unwrap();
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ pub struct PaneContentsAndUi<'a> {
|
|||
colors: Palette,
|
||||
focused_clients: Vec<ClientId>,
|
||||
multiple_users_exist_in_session: bool,
|
||||
mode: InputMode, // TODO: per client
|
||||
}
|
||||
|
||||
impl<'a> PaneContentsAndUi<'a> {
|
||||
|
|
@ -22,7 +21,6 @@ impl<'a> PaneContentsAndUi<'a> {
|
|||
output: &'a mut Output,
|
||||
colors: Palette,
|
||||
active_panes: &HashMap<ClientId, PaneId>,
|
||||
mode: InputMode,
|
||||
) -> Self {
|
||||
let focused_clients: Vec<ClientId> = active_panes
|
||||
.iter()
|
||||
|
|
@ -36,11 +34,10 @@ impl<'a> PaneContentsAndUi<'a> {
|
|||
colors,
|
||||
focused_clients,
|
||||
multiple_users_exist_in_session,
|
||||
mode,
|
||||
}
|
||||
}
|
||||
pub fn render_pane_contents_for_all_clients(&mut self) {
|
||||
if let Some(vte_output) = self.pane.render() {
|
||||
if let Some(vte_output) = self.pane.render(None) {
|
||||
// FIXME: Use Termion for cursor and style clearing?
|
||||
self.output.push_str_to_all_clients(&format!(
|
||||
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||
|
|
@ -50,6 +47,20 @@ impl<'a> PaneContentsAndUi<'a> {
|
|||
));
|
||||
}
|
||||
}
|
||||
pub fn render_pane_contents_for_client(&mut self, client_id: ClientId) {
|
||||
if let Some(vte_output) = self.pane.render(Some(client_id)) {
|
||||
// FIXME: Use Termion for cursor and style clearing?
|
||||
self.output.push_to_client(
|
||||
client_id,
|
||||
&format!(
|
||||
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||
self.pane.y() + 1,
|
||||
self.pane.x() + 1,
|
||||
vte_output
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
pub fn render_fake_cursor_if_needed(&mut self, client_id: ClientId) {
|
||||
let pane_focused_for_client_id = self.focused_clients.contains(&client_id);
|
||||
let pane_focused_for_different_client = self
|
||||
|
|
@ -79,7 +90,12 @@ impl<'a> PaneContentsAndUi<'a> {
|
|||
}
|
||||
}
|
||||
}
|
||||
pub fn render_pane_frame(&mut self, client_id: ClientId, session_is_mirrored: bool) {
|
||||
pub fn render_pane_frame(
|
||||
&mut self,
|
||||
client_id: ClientId,
|
||||
client_mode: InputMode,
|
||||
session_is_mirrored: bool,
|
||||
) {
|
||||
let pane_focused_for_client_id = self.focused_clients.contains(&client_id);
|
||||
let other_focused_clients: Vec<ClientId> = self
|
||||
.focused_clients
|
||||
|
|
@ -89,7 +105,7 @@ impl<'a> PaneContentsAndUi<'a> {
|
|||
.collect();
|
||||
let pane_focused_for_differet_client = !other_focused_clients.is_empty();
|
||||
|
||||
let frame_color = self.frame_color(client_id, self.mode, session_is_mirrored);
|
||||
let frame_color = self.frame_color(client_id, client_mode, session_is_mirrored);
|
||||
let focused_client = if pane_focused_for_client_id {
|
||||
Some(client_id)
|
||||
} else if pane_focused_for_differet_client {
|
||||
|
|
@ -132,10 +148,11 @@ impl<'a> PaneContentsAndUi<'a> {
|
|||
pub fn render_pane_boundaries(
|
||||
&self,
|
||||
client_id: ClientId,
|
||||
client_mode: InputMode,
|
||||
boundaries: &mut Boundaries,
|
||||
session_is_mirrored: bool,
|
||||
) {
|
||||
let color = self.frame_color(client_id, self.mode, session_is_mirrored);
|
||||
let color = self.frame_color(client_id, client_mode, session_is_mirrored);
|
||||
boundaries.add_rect(self.pane.as_ref(), color);
|
||||
}
|
||||
fn frame_color(
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ fn create_new_tab(size: Size) -> Tab {
|
|||
LayoutTemplate::default().try_into().unwrap(),
|
||||
vec![1],
|
||||
index,
|
||||
client_id,
|
||||
);
|
||||
tab
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ use crate::{
|
|||
pty::{ClientOrTabIndex, PtyInstruction},
|
||||
screen::ScreenInstruction,
|
||||
thread_bus::{Bus, ThreadSenders},
|
||||
ClientId,
|
||||
};
|
||||
|
||||
use zellij_utils::{
|
||||
|
|
@ -40,10 +41,12 @@ use zellij_utils::{
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum PluginInstruction {
|
||||
Load(Sender<u32>, RunPlugin, usize), // tx_pid, plugin metadata, tab_index
|
||||
Update(Option<u32>, Event), // Focused plugin / broadcast, event data
|
||||
Render(Sender<String>, u32, usize, usize), // String buffer, plugin id, rows, cols
|
||||
Unload(u32),
|
||||
Load(Sender<u32>, RunPlugin, usize, ClientId), // tx_pid, plugin metadata, tab_index, client_ids
|
||||
Update(Option<u32>, Option<ClientId>, Event), // Focused plugin / broadcast, client_id, event data
|
||||
Render(Sender<String>, u32, ClientId, usize, usize), // String buffer, plugin id, client_id, rows, cols
|
||||
Unload(u32), // plugin_id
|
||||
AddClient(ClientId),
|
||||
RemoveClient(ClientId),
|
||||
Exit,
|
||||
}
|
||||
|
||||
|
|
@ -53,8 +56,10 @@ impl From<&PluginInstruction> for PluginContext {
|
|||
PluginInstruction::Load(..) => PluginContext::Load,
|
||||
PluginInstruction::Update(..) => PluginContext::Update,
|
||||
PluginInstruction::Render(..) => PluginContext::Render,
|
||||
PluginInstruction::Unload(_) => PluginContext::Unload,
|
||||
PluginInstruction::Unload(..) => PluginContext::Unload,
|
||||
PluginInstruction::Exit => PluginContext::Exit,
|
||||
PluginInstruction::AddClient(_) => PluginContext::AddClient,
|
||||
PluginInstruction::RemoveClient(_) => PluginContext::RemoveClient,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -67,6 +72,7 @@ pub(crate) struct PluginEnv {
|
|||
pub wasi_env: WasiEnv,
|
||||
pub subscriptions: Arc<Mutex<HashSet<EventType>>>,
|
||||
pub tab_index: usize,
|
||||
pub client_id: ClientId,
|
||||
plugin_own_data_dir: PathBuf,
|
||||
}
|
||||
|
||||
|
|
@ -80,38 +86,25 @@ pub(crate) fn wasm_thread_main(
|
|||
info!("Wasm main thread starts");
|
||||
|
||||
let mut plugin_id = 0;
|
||||
let mut plugin_map = HashMap::new();
|
||||
let mut headless_plugins = HashMap::new();
|
||||
let mut plugin_map: HashMap<(u32, ClientId), (Instance, PluginEnv)> = HashMap::new(); // u32 => pid
|
||||
let mut connected_clients: Vec<ClientId> = vec![];
|
||||
let plugin_dir = data_dir.join("plugins/");
|
||||
let plugin_global_data_dir = plugin_dir.join("data");
|
||||
fs::create_dir_all(&plugin_global_data_dir).unwrap();
|
||||
|
||||
for plugin in plugins.iter() {
|
||||
if let PluginType::Headless = plugin.run {
|
||||
let (instance, plugin_env) = start_plugin(
|
||||
plugin_id,
|
||||
plugin,
|
||||
0,
|
||||
&bus,
|
||||
&store,
|
||||
&data_dir,
|
||||
&plugin_global_data_dir,
|
||||
);
|
||||
plugin_map.insert(plugin_id, (instance, plugin_env));
|
||||
plugin_id += 1;
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel");
|
||||
err_ctx.add_call(ContextType::Plugin((&event).into()));
|
||||
match event {
|
||||
PluginInstruction::Load(pid_tx, run, tab_index) => {
|
||||
PluginInstruction::Load(pid_tx, run, tab_index, client_id) => {
|
||||
let plugin = plugins
|
||||
.get(&run)
|
||||
.unwrap_or_else(|| panic!("Plugin {:?} could not be resolved", run));
|
||||
|
||||
let (instance, plugin_env) = start_plugin(
|
||||
plugin_id,
|
||||
client_id,
|
||||
&plugin,
|
||||
tab_index,
|
||||
&bus,
|
||||
|
|
@ -120,16 +113,34 @@ pub(crate) fn wasm_thread_main(
|
|||
&plugin_global_data_dir,
|
||||
);
|
||||
|
||||
plugin_map.insert(plugin_id, (instance, plugin_env));
|
||||
plugin_map.insert(
|
||||
(plugin_id, client_id),
|
||||
(instance.clone(), plugin_env.clone()),
|
||||
);
|
||||
|
||||
// clone plugins for the rest of the client ids if they exist
|
||||
for client_id in connected_clients.iter() {
|
||||
let mut new_plugin_env = plugin_env.clone();
|
||||
new_plugin_env.client_id = *client_id;
|
||||
let module = instance.module().clone();
|
||||
let wasi = new_plugin_env.wasi_env.import_object(&module).unwrap();
|
||||
let zellij = zellij_exports(&store, &new_plugin_env);
|
||||
let instance = Instance::new(&module, &zellij.chain_back(wasi)).unwrap();
|
||||
plugin_map.insert((plugin_id, *client_id), (instance, new_plugin_env));
|
||||
}
|
||||
pid_tx.send(plugin_id).unwrap();
|
||||
plugin_id += 1;
|
||||
}
|
||||
PluginInstruction::Update(pid, event) => {
|
||||
for (&i, (instance, plugin_env)) in &plugin_map {
|
||||
PluginInstruction::Update(pid, cid, event) => {
|
||||
for (&(plugin_id, client_id), (instance, plugin_env)) in &plugin_map {
|
||||
let subs = plugin_env.subscriptions.lock().unwrap();
|
||||
// FIXME: This is very janky... Maybe I should write my own macro for Event -> EventType?
|
||||
let event_type = EventType::from_str(&event.to_string()).unwrap();
|
||||
if (pid.is_none() || pid == Some(i)) && subs.contains(&event_type) {
|
||||
if subs.contains(&event_type)
|
||||
&& ((pid.is_none() && cid.is_none())
|
||||
|| (pid.is_none() && cid == Some(client_id))
|
||||
|| (cid.is_none() && pid == Some(plugin_id)))
|
||||
{
|
||||
let update = instance.exports.get_function("update").unwrap();
|
||||
wasi_write_object(&plugin_env.wasi_env, &event);
|
||||
update.call(&[]).unwrap();
|
||||
|
|
@ -137,11 +148,11 @@ pub(crate) fn wasm_thread_main(
|
|||
}
|
||||
drop(bus.senders.send_to_screen(ScreenInstruction::Render));
|
||||
}
|
||||
PluginInstruction::Render(buf_tx, pid, rows, cols) => {
|
||||
PluginInstruction::Render(buf_tx, pid, cid, rows, cols) => {
|
||||
if rows == 0 || cols == 0 {
|
||||
buf_tx.send(String::new()).unwrap();
|
||||
} else {
|
||||
let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
|
||||
let (instance, plugin_env) = plugin_map.get(&(pid, cid)).unwrap();
|
||||
let render = instance.exports.get_function("render").unwrap();
|
||||
|
||||
render
|
||||
|
|
@ -154,7 +165,54 @@ pub(crate) fn wasm_thread_main(
|
|||
PluginInstruction::Unload(pid) => {
|
||||
info!("Bye from plugin {}", &pid);
|
||||
// TODO: remove plugin's own data directory
|
||||
drop(plugin_map.remove(&pid));
|
||||
let ids_in_plugin_map: Vec<(u32, ClientId)> = plugin_map.keys().copied().collect();
|
||||
for (plugin_id, client_id) in ids_in_plugin_map {
|
||||
if pid == plugin_id {
|
||||
drop(plugin_map.remove(&(plugin_id, client_id)));
|
||||
}
|
||||
}
|
||||
}
|
||||
PluginInstruction::AddClient(client_id) => {
|
||||
connected_clients.push(client_id);
|
||||
let mut seen = HashSet::new();
|
||||
let mut new_plugins = HashMap::new();
|
||||
for (&(plugin_id, client_id), (instance, plugin_env)) in &plugin_map {
|
||||
if seen.contains(&plugin_id) {
|
||||
continue;
|
||||
} else {
|
||||
seen.insert(plugin_id);
|
||||
let mut new_plugin_env = plugin_env.clone();
|
||||
new_plugin_env.client_id = client_id;
|
||||
new_plugins.insert(plugin_id, (instance.module().clone(), new_plugin_env));
|
||||
}
|
||||
}
|
||||
for (plugin_id, (module, mut new_plugin_env)) in new_plugins.drain() {
|
||||
let wasi = new_plugin_env.wasi_env.import_object(&module).unwrap();
|
||||
let zellij = zellij_exports(&store, &new_plugin_env);
|
||||
let instance = Instance::new(&module, &zellij.chain_back(wasi)).unwrap();
|
||||
plugin_map.insert((plugin_id, client_id), (instance, new_plugin_env));
|
||||
}
|
||||
|
||||
// load headless plugins
|
||||
for plugin in plugins.iter() {
|
||||
if let PluginType::Headless = plugin.run {
|
||||
let (instance, plugin_env) = start_plugin(
|
||||
plugin_id,
|
||||
client_id,
|
||||
plugin,
|
||||
0,
|
||||
&bus,
|
||||
&store,
|
||||
&data_dir,
|
||||
&plugin_global_data_dir,
|
||||
);
|
||||
headless_plugins.insert(plugin_id, (instance, plugin_env));
|
||||
plugin_id += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
PluginInstruction::RemoveClient(client_id) => {
|
||||
connected_clients.retain(|c| c != &client_id);
|
||||
}
|
||||
PluginInstruction::Exit => break,
|
||||
}
|
||||
|
|
@ -163,8 +221,10 @@ pub(crate) fn wasm_thread_main(
|
|||
fs::remove_dir_all(&plugin_global_data_dir).unwrap();
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn start_plugin(
|
||||
plugin_id: u32,
|
||||
client_id: ClientId,
|
||||
plugin: &PluginConfig,
|
||||
tab_index: usize,
|
||||
bus: &Bus<PluginInstruction>,
|
||||
|
|
@ -224,6 +284,7 @@ fn start_plugin(
|
|||
|
||||
let plugin_env = PluginEnv {
|
||||
plugin_id,
|
||||
client_id,
|
||||
plugin,
|
||||
senders: bus.senders.clone(),
|
||||
wasi_env,
|
||||
|
|
@ -346,6 +407,7 @@ fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) {
|
|||
// But that's a lot of code, and this is a few lines:
|
||||
let send_plugin_instructions = plugin_env.senders.to_plugin.clone();
|
||||
let update_target = Some(plugin_env.plugin_id);
|
||||
let client_id = plugin_env.client_id;
|
||||
thread::spawn(move || {
|
||||
let start_time = Instant::now();
|
||||
thread::sleep(Duration::from_secs_f64(secs));
|
||||
|
|
@ -357,6 +419,7 @@ fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) {
|
|||
.unwrap()
|
||||
.send(PluginInstruction::Update(
|
||||
update_target,
|
||||
Some(client_id),
|
||||
Event::Timer(elapsed_time),
|
||||
))
|
||||
.unwrap();
|
||||
|
|
|
|||
|
|
@ -294,6 +294,8 @@ pub enum PluginContext {
|
|||
Render,
|
||||
Unload,
|
||||
Exit,
|
||||
AddClient,
|
||||
RemoveClient,
|
||||
}
|
||||
|
||||
/// Stack call representations corresponding to the different types of [`ClientInstruction`]s.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue