#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
#![warn(clippy::all, clippy::pedantic)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::too_many_arguments)]
pub mod input_capture;
bmux_plugin_schema_macros::schema! {
source: "bpdl/pane-runtime-plugin.bpdl",
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct PaneRuntimePluginConfig {
pub shell: String,
pub pane_term: String,
#[serde(default = "default_true")]
pub bracketed_paste: bool,
#[serde(default)]
pub shell_integration_root: Option<std::path::PathBuf>,
}
const fn default_true() -> bool {
true
}
#[cfg(test)]
mod tests {
use super::{pane_runtime_commands, pane_runtime_state};
#[test]
fn program_execution_contract_keeps_literal_binary_input_and_identity() {
use super::pane_runtime_execution_v4::{TerminalExecutionRef, client::SendRequest};
let execution = TerminalExecutionRef {
session_id: uuid::Uuid::new_v4(),
pane_id: uuid::Uuid::new_v4(),
execution_token: uuid::Uuid::new_v4(),
};
let request = SendRequest {
execution: execution.clone(),
data: vec![0, 255, 10],
};
let encoded = bmux_codec::to_vec(&request).unwrap();
let decoded: SendRequest = bmux_codec::from_bytes(&encoded).unwrap();
assert_eq!(decoded.execution, execution);
assert_eq!(decoded.data, request.data);
}
#[test]
fn fresh_shell_directory_is_an_explicit_versioned_contract() {
use super::pane_runtime_spawn_commands_v1::client::NewSessionRequest;
use super::pane_runtime_spawn_v1::InitialShellOptions;
for cwd in [None, Some("/work/project".to_string())] {
let request = NewSessionRequest {
name: Some("work".to_string()),
options: InitialShellOptions { cwd: cwd.clone() },
};
let encoded = bmux_codec::to_vec(&request).expect("encode spawn request");
let decoded: NewSessionRequest =
bmux_codec::from_bytes(&encoded).expect("decode spawn request");
assert_eq!(decoded.options.cwd, cwd);
assert_eq!(decoded.name, request.name);
}
}
#[test]
fn source_capture_requires_explicit_pane_identity() {
use super::pane_runtime_spawn_v1::client::CaptureSourceRequest;
#[derive(serde::Serialize)]
struct MissingPane {
session_id: uuid::Uuid,
}
let request = CaptureSourceRequest {
session_id: uuid::Uuid::new_v4(),
pane_id: uuid::Uuid::new_v4(),
};
let bytes = bmux_codec::to_vec(&request).unwrap();
let decoded: CaptureSourceRequest = bmux_codec::from_bytes(&bytes).unwrap();
assert_eq!(decoded.session_id, request.session_id);
assert_eq!(decoded.pane_id, request.pane_id);
let missing = bmux_codec::to_vec(&MissingPane {
session_id: request.session_id,
})
.unwrap();
assert!(bmux_codec::from_bytes::<CaptureSourceRequest>(&missing).is_err());
}
#[test]
fn directory_observation_preserves_execution_identity_and_provenance() {
use super::pane_runtime_spawn_v1::{
DirectoryObservation, ObservationMethod, SourceExecution,
};
let observation = DirectoryObservation {
source: SourceExecution {
session_id: uuid::Uuid::new_v4(),
pane_id: uuid::Uuid::new_v4(),
execution_token: uuid::Uuid::new_v4(),
},
path: "/work/project".to_string(),
method: ObservationMethod::Shell,
};
let encoded = bmux_codec::to_vec(&observation).expect("encode observation");
let decoded: DirectoryObservation =
bmux_codec::from_bytes(&encoded).expect("decode observation");
assert_eq!(decoded, observation);
}
#[test]
fn versioned_capture_preserves_legacy_pin_wire_shape() {
use super::attach_runtime_state::{HistoryCaptureV1, PaneScrollbackPin};
#[derive(serde::Serialize)]
struct LegacyPin {
pane_id: uuid::Uuid,
pin_id: u64,
total_scrolled_rows: u64,
max_scrollback_offset: u32,
stream_end: u64,
}
let pin = PaneScrollbackPin {
pane_id: uuid::Uuid::new_v4(),
pin_id: 7,
total_scrolled_rows: 12,
max_scrollback_offset: 3,
stream_end: 99,
};
let legacy = LegacyPin {
pane_id: pin.pane_id,
pin_id: pin.pin_id,
total_scrolled_rows: pin.total_scrolled_rows,
max_scrollback_offset: pin.max_scrollback_offset,
stream_end: pin.stream_end,
};
assert_eq!(
bmux_codec::to_vec(&pin).unwrap(),
bmux_codec::to_vec(&legacy).unwrap()
);
let capture = HistoryCaptureV1 {
width: 80,
height: 24,
history_truncated: true,
history_line_count: 3,
capture_id: uuid::Uuid::new_v4(),
pin,
};
let bytes = bmux_codec::to_vec(&capture).unwrap();
let decoded: HistoryCaptureV1 = bmux_codec::from_bytes(&bytes).unwrap();
assert_eq!(decoded, capture);
}
use pane_runtime_commands::client::SetPanePaddingRequest;
#[test]
fn pane_padding_generated_models_preserve_complete_contract_values() {
let spec = pane_runtime_state::PanePaddingSpec {
left: 1,
right: 2,
top: 3,
bottom: 4,
max_content_width: Some(120),
max_content_height: Some(40),
horizontal_alignment: "center".to_string(),
vertical_alignment: "bottom".to_string(),
};
let state = pane_runtime_state::PanePaddingState {
session_id: uuid::Uuid::new_v4(),
pane_id: uuid::Uuid::new_v4(),
declarative: spec.clone(),
matched_rule_index: Some(2),
runtime_override: Some(spec.clone()),
effective: spec.clone(),
source: "runtime_override".to_string(),
outer_rect: Some(pane_runtime_state::PanePaddingRect {
x: 0,
y: 0,
w: 160,
h: 50,
}),
base_content_rect: None,
effective_content_rect: Some(pane_runtime_state::PanePaddingRect {
x: 20,
y: 5,
w: 120,
h: 40,
}),
persist_runtime_overrides: true,
};
let encoded = bmux_codec::to_vec(&state).expect("encode generated padding state");
let decoded: pane_runtime_state::PanePaddingState =
bmux_codec::from_bytes(&encoded).expect("decode generated padding state");
assert_eq!(decoded, state);
let request = SetPanePaddingRequest {
session_id: state.session_id,
pane_id: Some(state.pane_id),
padding: spec,
};
let encoded = bmux_codec::to_vec(&request).expect("encode generated set request");
let decoded: SetPanePaddingRequest =
bmux_codec::from_bytes(&encoded).expect("decode generated set request");
assert_eq!(decoded.session_id, request.session_id);
assert_eq!(decoded.pane_id, request.pane_id);
assert_eq!(decoded.padding, request.padding);
}
}