Reduce session logging and background churn
This commit is contained in:
@@ -216,13 +216,6 @@ impl BedrockClient {
|
||||
tools.len()
|
||||
);
|
||||
|
||||
log::info!(
|
||||
"[bedrock] Sending request payload to Bedrock:\nSystem Prompt: {:?}\nMessages: {:#?}\nTools: {:#?}",
|
||||
system_prompt,
|
||||
messages,
|
||||
tools
|
||||
);
|
||||
|
||||
let converted = build_converse_request(
|
||||
messages.clone(),
|
||||
system_prompt.clone(),
|
||||
|
||||
@@ -269,11 +269,17 @@ impl CurrentPrompt {
|
||||
|
||||
if let Some(session) = session {
|
||||
let buffer_text = editor.as_ref(ctx).buffer_text(ctx);
|
||||
let mut should_notify = false;
|
||||
for (kind, state) in me.states.iter_mut() {
|
||||
state.should_render =
|
||||
kind.should_render(&buffer_text, session.aliases());
|
||||
let should_render = kind.should_render(&buffer_text, session.aliases());
|
||||
if state.should_render != should_render {
|
||||
state.should_render = should_render;
|
||||
should_notify = true;
|
||||
}
|
||||
}
|
||||
if should_notify {
|
||||
ctx.notify();
|
||||
}
|
||||
ctx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -321,7 +327,10 @@ impl CurrentPrompt {
|
||||
}
|
||||
|
||||
fn update_chip_value(&mut self, chip_kind: &ContextChipKind, value: Option<ChipValue>) {
|
||||
log::debug!("Updating prompt value of {chip_kind:?} to {value:?}");
|
||||
log::trace!(
|
||||
"Updating prompt value of {chip_kind:?}; has_value={}",
|
||||
value.is_some()
|
||||
);
|
||||
if let Some(state) = self.states.get_mut(chip_kind) {
|
||||
if state.last_computed_value != value {
|
||||
state.last_computed_value = value;
|
||||
@@ -332,14 +341,19 @@ impl CurrentPrompt {
|
||||
}
|
||||
|
||||
fn update_on_click_value(&mut self, chip_kind: &ContextChipKind, value: Option<Vec<String>>) {
|
||||
log::debug!("Updating prompt on_click value of {chip_kind:?} to {value:?}");
|
||||
log::trace!(
|
||||
"Updating prompt on_click value of {chip_kind:?}; item_count={}",
|
||||
value.as_ref().map_or(0, Vec::len)
|
||||
);
|
||||
let filter_values = match chip_kind {
|
||||
ContextChipKind::ShellGitBranch => self.filter_git_branch_on_click_values(value),
|
||||
_ => value,
|
||||
};
|
||||
if let Some(state) = self.states.get_mut(chip_kind) {
|
||||
state.last_on_click_values = filter_values;
|
||||
let _ = self.update_tx.try_send(());
|
||||
if state.last_on_click_values != filter_values {
|
||||
state.last_on_click_values = filter_values;
|
||||
let _ = self.update_tx.try_send(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ impl PromptSnapshot {
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
log::debug!("Current prompt snapshot: {chips:?}");
|
||||
Self {
|
||||
chips,
|
||||
same_line_prompt_enabled: current_prompt.same_line_prompt_enabled(),
|
||||
|
||||
@@ -492,7 +492,10 @@ impl EditorView {
|
||||
self.stop_transcribing_voice_input(ctx);
|
||||
match result {
|
||||
Ok(transcribe_response) => {
|
||||
log::debug!("Transcribed voice input: {transcribe_response:?}");
|
||||
log::debug!(
|
||||
"Transcribed voice input; characters={}",
|
||||
transcribe_response.chars().count()
|
||||
);
|
||||
self.user_insert(&transcribe_response, ctx);
|
||||
}
|
||||
Err(e) => match e {
|
||||
|
||||
+2
-2
@@ -2396,7 +2396,7 @@ pub(crate) fn app_callbacks(
|
||||
// Persist the final app state before tearing down the writer.
|
||||
// This ensures the latest session (tabs, CWD, conversations) is saved
|
||||
// even if the termination bypassed individual window-close events.
|
||||
ctx.dispatch_global_action("workspace:save_app", &());
|
||||
workspace::save_app_urgently(ctx);
|
||||
|
||||
NotebookManager::handle(ctx).update(ctx, |manager, ctx| {
|
||||
// Notebooks are only saved periodically, so ensure that any pending changes have
|
||||
@@ -2634,7 +2634,7 @@ pub(crate) fn app_callbacks(
|
||||
stack.handle_window_closed(window_data, ctx);
|
||||
});
|
||||
}
|
||||
ctx.dispatch_global_action("workspace:save_app", &());
|
||||
workspace::save_app_urgently(ctx);
|
||||
})),
|
||||
on_window_moved: Some(Box::new(move |ctx| {
|
||||
ctx.dispatch_global_action("workspace:save_app", &());
|
||||
|
||||
@@ -499,7 +499,7 @@ impl PaneContent for TerminalPane {
|
||||
let ambient_model = ambient_model.as_ref(app);
|
||||
let task_id = ambient_model.task_id();
|
||||
|
||||
log::info!("[session-save] pane=viewer/ambient task_id={task_id:?}");
|
||||
log::trace!("[session-snapshot] pane=viewer/ambient task_id={task_id:?}");
|
||||
return LeafContents::AmbientAgent(AmbientAgentPaneSnapshot {
|
||||
uuid: self.uuid.clone(),
|
||||
task_id,
|
||||
@@ -507,7 +507,7 @@ impl PaneContent for TerminalPane {
|
||||
}
|
||||
|
||||
let cwd = view.pwd_if_local(app);
|
||||
log::info!("[session-save] pane=viewer cwd={cwd:?} is_active={is_active}");
|
||||
log::trace!("[session-snapshot] pane=viewer cwd={cwd:?} is_active={is_active}");
|
||||
LeafContents::Terminal(TerminalPaneSnapshot {
|
||||
uuid: self.uuid.clone(),
|
||||
cwd,
|
||||
@@ -533,14 +533,14 @@ impl PaneContent for TerminalPane {
|
||||
// can be restored via the ambient agent task if one exists.
|
||||
let task_id = view.model.lock().ambient_agent_task_id();
|
||||
if task_id.is_some() {
|
||||
log::info!("[session-save] pane=transcript/ambient task_id={task_id:?}");
|
||||
log::trace!("[session-snapshot] pane=transcript/ambient task_id={task_id:?}");
|
||||
LeafContents::AmbientAgent(AmbientAgentPaneSnapshot {
|
||||
uuid: self.uuid.clone(),
|
||||
task_id,
|
||||
})
|
||||
} else {
|
||||
let cwd = view.pwd_if_local(app);
|
||||
log::info!("[session-save] pane=transcript cwd={cwd:?} is_active={is_active}");
|
||||
log::trace!("[session-snapshot] pane=transcript cwd={cwd:?} is_active={is_active}");
|
||||
LeafContents::Terminal(TerminalPaneSnapshot {
|
||||
uuid: self.uuid.clone(),
|
||||
cwd,
|
||||
@@ -584,8 +584,8 @@ impl PaneContent for TerminalPane {
|
||||
});
|
||||
|
||||
let cwd = view.pwd_if_local(app);
|
||||
log::info!(
|
||||
"[session-save] pane=terminal cwd={cwd:?} is_active={is_active} \
|
||||
log::trace!(
|
||||
"[session-snapshot] pane=terminal cwd={cwd:?} is_active={is_active} \
|
||||
conversations={} active_conversation={active_conversation_id:?} \
|
||||
has_shell_launch_data={} has_input_config=true",
|
||||
conversation_ids_to_restore.len(),
|
||||
|
||||
+17
-1
@@ -1000,7 +1000,7 @@ pub enum OpenAIProviderKind {
|
||||
///
|
||||
/// Multiple providers can be configured simultaneously. Each provider has its own endpoint,
|
||||
/// credentials, and model list.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, schemars::JsonSchema)]
|
||||
#[derive(Clone, Serialize, Deserialize, PartialEq, schemars::JsonSchema)]
|
||||
#[schemars(description = "Configuration for a direct model provider endpoint.")]
|
||||
pub struct OpenAIProviderConfig {
|
||||
#[serde(default)]
|
||||
@@ -1027,6 +1027,22 @@ pub struct OpenAIProviderConfig {
|
||||
pub models: Vec<OpenAIModelConfig>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for OpenAIProviderConfig {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
formatter
|
||||
.debug_struct("OpenAIProviderConfig")
|
||||
.field("kind", &self.kind)
|
||||
.field("enabled", &self.enabled)
|
||||
.field("name", &self.name)
|
||||
.field("base_url", &self.base_url)
|
||||
.field("api_key_configured", &self.api_key.is_some())
|
||||
.field("project_id", &self.project_id)
|
||||
.field("location", &self.location)
|
||||
.field("models", &self.models)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl settings_value::SettingsValue for OpenAIProviderConfig {}
|
||||
|
||||
fn default_acp_agent_id() -> String {
|
||||
|
||||
@@ -391,6 +391,9 @@ fn native_provider_settings_roundtrip_with_vertex_configuration() {
|
||||
}))
|
||||
.expect("Native OpenAI provider settings should deserialize");
|
||||
assert_eq!(native_openai.kind, OpenAIProviderKind::OpenAI);
|
||||
let debug_output = format!("{native_openai:?}");
|
||||
assert!(!debug_output.contains("sk-test"));
|
||||
assert!(debug_output.contains("api_key_configured: true"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -260,18 +260,19 @@ fn wire_up_terminal_view_session_sharing(
|
||||
if *SessionSettings::as_ref(ctx).honor_ps1 {
|
||||
return
|
||||
}
|
||||
let Some(network) = session_sharer_clone.borrow().clone() else {
|
||||
return;
|
||||
};
|
||||
let prompt_snapshot = current_prompt.read(ctx, |current_prompt, ctx| {
|
||||
PromptSnapshot::from_current_prompt(current_prompt, ctx)
|
||||
});
|
||||
if let Some(network) = session_sharer_clone.borrow().as_ref() {
|
||||
let Ok(serialized_prompt) = serde_json::to_string(&prompt_snapshot) else {
|
||||
log::error!("Failed to serialize prompt snapshot to send active prompt update to shared session server");
|
||||
return
|
||||
};
|
||||
network.update(ctx, |network, _| {
|
||||
network.send_active_prompt_update_if_changed(session_sharing_protocol::common::ActivePrompt::WarpPrompt(serialized_prompt))
|
||||
});
|
||||
}
|
||||
let Ok(serialized_prompt) = serde_json::to_string(&prompt_snapshot) else {
|
||||
log::error!("Failed to serialize prompt snapshot to send active prompt update to shared session server");
|
||||
return
|
||||
};
|
||||
network.update(ctx, |network, _| {
|
||||
network.send_active_prompt_update_if_changed(session_sharing_protocol::common::ActivePrompt::WarpPrompt(serialized_prompt))
|
||||
});
|
||||
});
|
||||
|
||||
let session_sharer_clone = session_sharer.clone();
|
||||
|
||||
@@ -264,11 +264,11 @@ fn add_local_machine_env(env: &mut BTreeMap<OsString, EnvEntry>) {
|
||||
let Ok(value) = reg_value_to_string(&value, &name) else {
|
||||
safe_info!(
|
||||
safe: ("Unable to convert value for key {name:?}"),
|
||||
full: ("Unable to convert value for key {name:?}: {:?}", value.bytes)
|
||||
full: ("Unable to convert value for key {name:?}")
|
||||
);
|
||||
continue;
|
||||
};
|
||||
log::trace!("adding SYS env: {name:?} = {value:?}");
|
||||
log::trace!("adding SYS env: {name:?}");
|
||||
env.insert(
|
||||
map_key(name.clone().into()),
|
||||
EnvEntry {
|
||||
@@ -289,7 +289,7 @@ fn add_user_env(env: &mut BTreeMap<OsString, EnvEntry>) {
|
||||
let Ok(value) = reg_value_to_string(&value, &name) else {
|
||||
safe_info!(
|
||||
safe: ("Unable to convert value for key {name:?}"),
|
||||
full: ("Unable to convert value for key {name:?}: {:?}", value.bytes)
|
||||
full: ("Unable to convert value for key {name:?}")
|
||||
);
|
||||
continue;
|
||||
};
|
||||
@@ -309,7 +309,7 @@ fn add_user_env(env: &mut BTreeMap<OsString, EnvEntry>) {
|
||||
value
|
||||
};
|
||||
|
||||
log::trace!("adding USER env: {name:?} = {value:?}");
|
||||
log::trace!("adding USER env: {name:?}");
|
||||
env.insert(
|
||||
map_key(name.clone().into()),
|
||||
EnvEntry {
|
||||
|
||||
@@ -19,7 +19,10 @@ pub(super) fn parse_ansi_c_quoted_string(quoted_string: String) -> String {
|
||||
if quoted_string.trim().is_empty() {
|
||||
return quoted_string;
|
||||
}
|
||||
log::debug!("Attempting to parse the following ANSI C escaped shell output: {quoted_string}");
|
||||
log::debug!(
|
||||
"Attempting to parse ANSI-C escaped shell output; bytes={}",
|
||||
quoted_string.len()
|
||||
);
|
||||
|
||||
let Some(quoted_string_without_prefix) = quoted_string.strip_prefix("$\'") else {
|
||||
log::warn!("Tried to parse ANSI-C quoted string but $\' prefix was not present");
|
||||
|
||||
@@ -653,10 +653,7 @@ impl<'a, H: Handler + 'a, W: io::Write> Performer<'a, H, W> {
|
||||
fn handle_decoded_data(&mut self, decoded_data: Result<Vec<u8>, hex::FromHexError>) {
|
||||
match decoded_data {
|
||||
Ok(decoded_data) => {
|
||||
safe_debug!(
|
||||
safe: ("Decoded payload"),
|
||||
full: ("Decoded payload string: {:?}", std::str::from_utf8(&decoded_data))
|
||||
);
|
||||
log::debug!("Decoded shell hook payload; bytes={}", decoded_data.len());
|
||||
|
||||
let hook = serde_json::from_slice::<DProtoHook>(&decoded_data);
|
||||
if let Ok(hook) = &hook {
|
||||
@@ -691,10 +688,7 @@ impl<'a, H: Handler + 'a, W: io::Write> Performer<'a, H, W> {
|
||||
return;
|
||||
}
|
||||
};
|
||||
safe_debug!(
|
||||
safe: ("Decoded payload"),
|
||||
full: ("Decoded payload string: {:?}", serde_json::to_string(&hook))
|
||||
);
|
||||
log::debug!("Decoded key-value shell hook payload");
|
||||
self.handle_decoded_hook(Ok(hook));
|
||||
}
|
||||
Some(&WARP_KV_ENTRY_BYTE) => {
|
||||
@@ -888,7 +882,7 @@ where
|
||||
.map(|parts| parts.join(";").trim().to_owned());
|
||||
if let Ok(body) = body {
|
||||
if !body.is_empty() {
|
||||
log::info!("Received OSC 9 notification: {}", body);
|
||||
log::info!("Received OSC 9 notification; bytes={}", body.len());
|
||||
self.handler.pluggable_notification(None, body);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3058,7 +3058,6 @@ impl BlockList {
|
||||
|
||||
if let Some(prompt_snapshot) = &block.prompt_snapshot {
|
||||
if let Ok(prompt_snapshot) = serde_json::from_str(prompt_snapshot) {
|
||||
log::debug!("Restored prompt: {prompt_snapshot:?}");
|
||||
self.active_block_mut().set_prompt_snapshot(prompt_snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,7 +356,6 @@ impl Sessions {
|
||||
let session = Session::new(session_info.clone(), command_executor);
|
||||
|
||||
log::info!("Shell is bootstrapped with session_id {:?}", session.id());
|
||||
log::debug!("Session details: {session:?}");
|
||||
|
||||
let session = Arc::new(session);
|
||||
self.sessions.insert(session.id(), session.clone());
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
use ::settings::ToggleableSetting;
|
||||
use galaxy_core::execution_mode::AppExecutionMode;
|
||||
use galaxy_graphql::mutations::create_anonymous_user::AnonymousUserType;
|
||||
use galaxyui::r#async::{SpawnedFutureHandle, Timer};
|
||||
use galaxyui::windowing::WindowManager;
|
||||
use galaxyui::{AppContext, SingletonEntity, TypedActionView};
|
||||
use galaxyui::{AppContext, Entity, ModelContext, SingletonEntity, TypedActionView};
|
||||
|
||||
use crate::ai::agent::conversation::AIConversationId;
|
||||
use crate::ai::agent::AIAgentExchangeId;
|
||||
use crate::app_state::get_app_state;
|
||||
use crate::app_state::{get_app_state, AppState};
|
||||
use crate::network::NetworkStatus;
|
||||
use crate::persistence::ModelEvent;
|
||||
use crate::root_view::OpenPath;
|
||||
@@ -20,6 +22,102 @@ use crate::workspace::cross_window_tab_drag::CrossWindowTabDrag;
|
||||
use crate::workspace::{Workspace, WorkspaceAction};
|
||||
use crate::{auth, GlobalResourceHandlesProvider};
|
||||
|
||||
const SESSION_SAVE_DEBOUNCE: Duration = Duration::from_millis(250);
|
||||
|
||||
pub(crate) struct SessionSaveCoordinator {
|
||||
last_enqueued_state: Option<AppState>,
|
||||
pending_save: bool,
|
||||
timer_generation: u64,
|
||||
timer: Option<SpawnedFutureHandle>,
|
||||
}
|
||||
|
||||
impl SessionSaveCoordinator {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
last_enqueued_state: None,
|
||||
pending_save: false,
|
||||
timer_generation: 0,
|
||||
timer: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn request_save(&mut self, ctx: &mut ModelContext<Self>) {
|
||||
if !session_save_is_enabled(ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.timer.is_none() {
|
||||
self.save_if_changed(ctx);
|
||||
} else {
|
||||
self.pending_save = true;
|
||||
}
|
||||
self.restart_timer(ctx);
|
||||
}
|
||||
|
||||
fn save_urgently(&mut self, ctx: &mut ModelContext<Self>) {
|
||||
self.timer_generation = self.timer_generation.wrapping_add(1);
|
||||
if let Some(timer) = self.timer.take() {
|
||||
timer.abort();
|
||||
}
|
||||
self.pending_save = false;
|
||||
|
||||
if session_save_is_enabled(ctx) {
|
||||
self.save_if_changed(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
fn restart_timer(&mut self, ctx: &mut ModelContext<Self>) {
|
||||
if let Some(timer) = self.timer.take() {
|
||||
timer.abort();
|
||||
}
|
||||
self.timer_generation = self.timer_generation.wrapping_add(1);
|
||||
let generation = self.timer_generation;
|
||||
self.timer = Some(ctx.spawn(
|
||||
async move { Timer::after(SESSION_SAVE_DEBOUNCE).await },
|
||||
move |coordinator, _, ctx| {
|
||||
if coordinator.timer_generation != generation {
|
||||
return;
|
||||
}
|
||||
coordinator.timer = None;
|
||||
if std::mem::take(&mut coordinator.pending_save) {
|
||||
coordinator.save_if_changed(ctx);
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
fn save_if_changed(&mut self, ctx: &mut ModelContext<Self>) {
|
||||
let Some(model_event_sender) = GlobalResourceHandlesProvider::as_ref(ctx)
|
||||
.get()
|
||||
.model_event_sender
|
||||
.clone()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let app_state = get_app_state(ctx);
|
||||
if self.last_enqueued_state.as_ref() == Some(&app_state) {
|
||||
return;
|
||||
}
|
||||
if let Err(err) = model_event_sender.send(ModelEvent::Snapshot(app_state.clone())) {
|
||||
log::error!("Error trying to send model event {err:?}");
|
||||
return;
|
||||
}
|
||||
self.last_enqueued_state = Some(app_state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for SessionSaveCoordinator {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
impl SingletonEntity for SessionSaveCoordinator {}
|
||||
|
||||
fn session_save_is_enabled(ctx: &AppContext) -> bool {
|
||||
AppExecutionMode::as_ref(ctx).can_save_session()
|
||||
&& *GeneralSettings::as_ref(ctx).restore_session
|
||||
&& !CrossWindowTabDrag::as_ref(ctx).is_active()
|
||||
}
|
||||
|
||||
/// Specifies where a forked conversation should be opened.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub enum ForkedConversationDestination {
|
||||
@@ -130,41 +228,15 @@ fn toggle_focus_reporting(_: &(), ctx: &mut AppContext) {
|
||||
}
|
||||
|
||||
fn save_app(_: &(), ctx: &mut AppContext) {
|
||||
if !AppExecutionMode::as_ref(ctx).can_save_session() {
|
||||
return;
|
||||
}
|
||||
SessionSaveCoordinator::handle(ctx).update(ctx, |coordinator, ctx| {
|
||||
coordinator.request_save(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
if !*GeneralSettings::as_ref(ctx).restore_session {
|
||||
return;
|
||||
}
|
||||
|
||||
// While a cross-window tab drag is active, the dragged tab's pane group
|
||||
// is in flight between source and preview windows and `get_app_state`
|
||||
// would produce a snapshot with zero windows. Persisting that snapshot
|
||||
// wipes the on-disk session via `save_app_state`'s delete-then-insert
|
||||
// transaction. `save_app` fires from window move / focus / resize /
|
||||
// close callbacks (see `app_callbacks` in `lib.rs`), all of which run
|
||||
// during a drag, so we have to short-circuit at this boundary. The
|
||||
// first save after the drag finalizes will rewrite the snapshot.
|
||||
if CrossWindowTabDrag::as_ref(ctx).is_active() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(model_event_sender) = GlobalResourceHandlesProvider::as_ref(ctx)
|
||||
.get()
|
||||
.model_event_sender
|
||||
.clone()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Only compute the app state if we're definitely going to use it.
|
||||
let app_state = get_app_state(ctx);
|
||||
let event = ModelEvent::Snapshot(app_state);
|
||||
|
||||
if let Err(err) = model_event_sender.send(event) {
|
||||
log::error!("Error trying to send model event {err:?}");
|
||||
}
|
||||
pub(crate) fn save_app_urgently(ctx: &mut AppContext) {
|
||||
SessionSaveCoordinator::handle(ctx).update(ctx, |coordinator, ctx| {
|
||||
coordinator.save_urgently(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
fn toggle_debug_network_status(_: &(), ctx: &mut AppContext) {
|
||||
|
||||
@@ -30,6 +30,7 @@ pub use action::{
|
||||
};
|
||||
pub use active_session::ActiveSession;
|
||||
use galaxy_core::context_flag::ContextFlag;
|
||||
pub(crate) use global_actions::save_app_urgently;
|
||||
pub use global_actions::{
|
||||
ForkAIConversationParams, ForkFromExchange, ForkedConversationDestination,
|
||||
};
|
||||
@@ -78,6 +79,7 @@ use crate::workspace::view::{
|
||||
pub fn init(app: &mut AppContext) {
|
||||
app.add_singleton_model(|_| WorkspaceRegistry::new());
|
||||
app.add_singleton_model(|_| cross_window_tab_drag::CrossWindowTabDrag::new());
|
||||
app.add_singleton_model(|_| global_actions::SessionSaveCoordinator::new());
|
||||
use galaxyui::keymap::macros::*;
|
||||
app.register_binding_validator::<Workspace>(is_binding_pty_compliant);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user