Files
galaxy/app/src/workspace/sync_inputs.rs
T

190 lines
6.5 KiB
Rust

use std::collections::{HashMap, HashSet};
use warpui::{keymap::EditableBinding, AppContext, Entity, EntityId, SingletonEntity, WindowId};
use crate::util::bindings::{BindingGroup, CustomAction};
use super::WorkspaceAction;
pub fn init(app: &mut AppContext) {
use warpui::keymap::macros::*;
app.register_editable_bindings(vec![
EditableBinding::new(
"workspace:disable_terminal_input_syncing",
"Stop Synchronizing Any Panes",
WorkspaceAction::DisableTerminalInputSync,
)
.with_context_predicate(id!("Workspace"))
.with_key_binding("alt-cmd-shift-I")
.with_group(BindingGroup::Settings.as_str())
.with_custom_action(CustomAction::DisableSyncTerminalInputs),
EditableBinding::new(
"workspace:toggle_sync_terminal_inputs_in_tab",
"Toggle Synchronizing All Panes in Current Tab",
WorkspaceAction::ToggleSyncTerminalInputsInTab,
)
.with_context_predicate(id!("Workspace"))
.with_group(BindingGroup::Settings.as_str())
.with_custom_action(CustomAction::ToggleSyncTerminalInputsInCurrentTab),
EditableBinding::new(
"workspace:toggle_sync_all_terminal_inputs_in_all_tabs",
"Toggle Synchronizing All Panes in All Tabs",
WorkspaceAction::ToggleSyncAllTerminalInputsInAllTabs,
)
.with_context_predicate(id!("Workspace"))
.with_group(BindingGroup::Settings.as_str())
.with_custom_action(CustomAction::ToggleSyncAllTerminalInputsInAllTabs),
]);
}
#[derive(Debug, PartialEq)]
enum SyncedPanes {
All,
AllPanesInPaneGroups { pane_group_ids: HashSet<EntityId> },
}
/// Stores state for syncing inputs across terminals.
/// Note: we sync input editors with themselves and
/// alt-screen/long-running commands with themselves
pub struct SyncedInputState {
sync_state_by_window: HashMap<WindowId, Option<SyncedPanes>>,
}
impl Entity for SyncedInputState {
type Event = ();
}
impl SingletonEntity for SyncedInputState {}
impl Default for SyncedInputState {
fn default() -> Self {
SyncedInputState::new()
}
}
impl SyncedInputState {
pub fn new() -> Self {
Self {
sync_state_by_window: HashMap::new(),
}
}
pub fn mock() -> Self {
Self::new()
}
pub fn toggle_sync_all_terminal_inputs_in_all_tabs(&mut self, window_id: WindowId) {
let new_sync_state = match self.sync_state_by_window.get(&window_id).unwrap_or(&None) {
Some(SyncedPanes::All) => None,
_ => Some(SyncedPanes::All),
};
self.sync_state_by_window.insert(window_id, new_sync_state);
}
pub fn toggle_sync_terminal_inputs_in_tab(
&mut self,
tab_id: EntityId,
all_tab_ids: impl Iterator<Item = EntityId>,
pane_group_count: usize,
window_id: WindowId,
) {
let new_state = match self.sync_state_by_window.get(&window_id).unwrap_or(&None) {
None => {
let mut synced_tabs = HashSet::new();
synced_tabs.insert(tab_id);
Some(SyncedPanes::AllPanesInPaneGroups {
pane_group_ids: synced_tabs,
})
}
Some(SyncedPanes::All) => {
let mut synced_tabs = HashSet::from_iter(all_tab_ids);
synced_tabs.remove(&tab_id);
Self::normalized_synced_panes(synced_tabs, pane_group_count)
}
Some(SyncedPanes::AllPanesInPaneGroups {
pane_group_ids: tab_ids,
}) => {
let mut synced_tabs = tab_ids.clone();
if synced_tabs.contains(&tab_id) {
// Tab is already synced so toggle should un-sync it.
synced_tabs.remove(&tab_id);
} else {
// Tab wasn't already synced so toggle should sync it.
synced_tabs.insert(tab_id);
}
Self::normalized_synced_panes(synced_tabs, pane_group_count)
}
};
self.sync_state_by_window.insert(window_id, new_state);
}
/// Given a set of `synced_pane_group_ids` and the total count of pane groups in a window, return the normalized SyncedPane variant to reduce ambiguity. For example, if `synced_pane_group_ids` is an empty HashSet, the normalized representation should be None.
fn normalized_synced_panes(
synced_pane_group_ids: HashSet<EntityId>,
pane_group_count: usize,
) -> Option<SyncedPanes> {
match synced_pane_group_ids.len() {
0 => None,
i if i == pane_group_count => Some(SyncedPanes::All),
_ => Some(SyncedPanes::AllPanesInPaneGroups {
pane_group_ids: synced_pane_group_ids,
}),
}
}
pub fn disable_sync_terminal_inputs(&mut self, window_id: WindowId) {
self.sync_state_by_window.insert(window_id, None);
}
fn get_state(&self, window_id: WindowId) -> Option<&SyncedPanes> {
self.sync_state_by_window
.get(&window_id)
.and_then(|state| state.as_ref())
}
pub fn is_syncing_any_inputs(&self, window_id: WindowId) -> bool {
self.get_state(window_id).is_some()
}
pub fn is_syncing_all_inputs(&self, window_id: WindowId) -> bool {
matches!(self.get_state(window_id), Some(SyncedPanes::All))
}
/// Returns true if sync mode is all panes in a set of pane group ids and
/// the specified pane group id is in that set.
/// Returns false otherwise -- notably, even when all panes are synced.
/// Useful when we need to know the exact sync state, not just sync.
pub fn is_syncing_all_panes_in_pane_group(
&self,
window_id: WindowId,
pane_group_id: EntityId,
) -> bool {
match self.sync_state_by_window.get(&window_id).unwrap_or(&None) {
Some(SyncedPanes::AllPanesInPaneGroups {
pane_group_ids: tab_ids,
}) => tab_ids.contains(&pane_group_id),
_ => false,
}
}
/// Returns true if we're in any state that should sync this pane group.
pub fn should_sync_this_pane_group(
&self,
pane_group_id: EntityId,
window_id: WindowId,
) -> bool {
match self.sync_state_by_window.get(&window_id).unwrap_or(&None) {
Some(SyncedPanes::All) => true,
Some(SyncedPanes::AllPanesInPaneGroups {
pane_group_ids: tab_ids,
}) => tab_ids.contains(&pane_group_id),
None => false,
}
}
}