diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aa6589..3525450 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.8.0] - 2025-12-09 + +### Changed (BREAKING) + +- `CommandHandler::handle()` now takes `ctx: CommandContext` instead of `terminal_info: TerminalInfo` +- Access terminal info via `ctx.terminal_info` + +### Added + +- **Environment variable passing**: Pass env vars from client to daemon per-command + - New `EnvVarFilter` for exact-match filtering of env var names + - New `CommandContext` struct bundling terminal info + env vars + - `DaemonClient::with_env_filter()` builder method +- **Terminal theme detection**: Detect dark/light mode via `terminal-colorsaurus` + - New `Theme` enum (`Dark`, `Light`) + - New `TerminalInfo.theme: Option` field + - Uses 50ms timeout, returns `None` if detection fails + ## [0.7.0] - 2025-12-03 ### Changed (BREAKING) diff --git a/Cargo.toml b/Cargo.toml index d9afeee..f7e24e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "daemon-cli" -version = "0.7.0" +version = "0.8.0" edition = "2024" [dependencies] @@ -18,6 +18,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } parking_lot = "0.12" terminal_size = "0.4" supports-color = "3.0" +terminal-colorsaurus = "0.4" interprocess = { version = "2.2", features = ["tokio"] } [target.'cfg(unix)'.dependencies] diff --git a/examples/common/mod.rs b/examples/common/mod.rs index 818beea..c6ca3e6 100644 --- a/examples/common/mod.rs +++ b/examples/common/mod.rs @@ -26,7 +26,7 @@ impl CommandHandler for CommandProcessor { async fn handle( &self, command: &str, - _terminal_info: TerminalInfo, + _ctx: CommandContext, mut output: impl AsyncWrite + Send + Unpin, cancel_token: CancellationToken, ) -> Result { diff --git a/examples/concurrent.rs b/examples/concurrent.rs index eff5f31..13a4be4 100644 --- a/examples/concurrent.rs +++ b/examples/concurrent.rs @@ -60,7 +60,7 @@ impl CommandHandler for TaskQueueHandler { async fn handle( &self, command: &str, - _terminal_info: TerminalInfo, + _ctx: CommandContext, mut output: impl AsyncWrite + Send + Unpin, cancel_token: CancellationToken, ) -> Result { diff --git a/src/client.rs b/src/client.rs index 4d11543..c420807 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,8 +1,8 @@ -use crate::StartupReason; use crate::error_context::{ErrorContextBuffer, get_or_init_global_error_context}; use crate::process::{TerminateResult, kill_process, process_exists, terminate_process}; use crate::terminal::TerminalInfo; use crate::transport::{SocketClient, SocketMessage, daemon_socket_exists, socket_path}; +use crate::{CommandContext, EnvVarFilter, StartupReason}; use anyhow::{Result, bail}; use std::{fs, path::PathBuf, process::Stdio, time::Duration}; use tokio::{io::AsyncWriteExt, process::Command, time::sleep}; @@ -48,6 +48,8 @@ pub struct DaemonClient { error_context: ErrorContextBuffer, /// Enable automatic daemon restart on fatal connection errors (default: false) auto_restart_on_error: bool, + /// Filter for which environment variables to pass to daemon + env_var_filter: EnvVarFilter, } impl DaemonClient { @@ -204,6 +206,7 @@ impl DaemonClient { build_timestamp, error_context, auto_restart_on_error: false, + env_var_filter: EnvVarFilter::none(), }) } @@ -452,10 +455,12 @@ impl DaemonClient { ) .await?; - // Replace self with new client, preserving auto_restart setting + // Replace self with new client, preserving settings let auto_restart = self.auto_restart_on_error; + let env_filter = std::mem::take(&mut self.env_var_filter); *self = new_client; self.auto_restart_on_error = auto_restart; + self.env_var_filter = env_filter; Ok(()) } @@ -516,6 +521,7 @@ impl DaemonClient { build_timestamp, error_context, auto_restart_on_error: false, + env_var_filter: EnvVarFilter::none(), }) } @@ -547,6 +553,31 @@ impl DaemonClient { self } + /// Configure which environment variables to pass to the daemon. + /// + /// By default, no environment variables are passed (backward compatible). + /// Use [`EnvVarFilter::with_names`] to specify exact variable names to include. + /// + /// # Example + /// + /// ```rust,no_run + /// use daemon_cli::prelude::*; + /// + /// # tokio_test::block_on(async { + /// let mut client = DaemonClient::connect("/path/to/project") + /// .await? + /// .with_env_filter(EnvVarFilter::with_names(["MY_APP_DEBUG", "MY_APP_CONFIG"])); + /// + /// // Commands will now include these env vars if they are set + /// client.execute_command("process file.txt".to_string()).await?; + /// # Ok::<(), anyhow::Error>(()) + /// # }); + /// ``` + pub fn with_env_filter(mut self, filter: EnvVarFilter) -> Self { + self.env_var_filter = filter; + self + } + /// Check if an error indicates a fatal connection issue (daemon crash/hang). /// /// Returns true for errors that suggest the daemon has crashed or become @@ -618,12 +649,21 @@ impl DaemonClient { "Detected terminal info" ); - // Send command with terminal info + // Filter environment variables based on configured names + let env_vars = self.env_var_filter.filter_current_env(); + if !env_vars.is_empty() { + tracing::debug!( + env_var_count = env_vars.len(), + "Passing filtered environment variables" + ); + } + + // Build command context + let context = CommandContext::with_env(terminal_info, env_vars); + + // Send command with context self.socket_client - .send_message(&SocketMessage::Command { - command, - terminal_info, - }) + .send_message(&SocketMessage::Command { command, context }) .await .inspect_err(|_| { self.error_context.dump_to_stderr(); diff --git a/src/lib.rs b/src/lib.rs index 4942bd7..3cc4713 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +35,7 @@ //! async fn handle( //! &self, //! command: &str, -//! terminal_info: TerminalInfo, +//! ctx: CommandContext, //! mut output: impl AsyncWrite + Send + Unpin, //! cancel_token: CancellationToken, //! ) -> Result { @@ -62,7 +62,7 @@ //! # async fn handle( //! # &self, //! # command: &str, -//! # _terminal_info: TerminalInfo, +//! # _ctx: CommandContext, //! # mut output: impl AsyncWrite + Send + Unpin, //! # _cancel_token: CancellationToken, //! # ) -> Result { @@ -100,7 +100,8 @@ use anyhow::Result; use async_trait::async_trait; -use std::{env, fs, str::FromStr, time::UNIX_EPOCH}; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, env, fs, str::FromStr, time::UNIX_EPOCH}; use tokio::io::AsyncWrite; use tokio_util::sync::CancellationToken; @@ -114,7 +115,113 @@ mod transport; pub use client::DaemonClient; pub use error_context::ErrorContextBuffer; pub use server::{DaemonHandle, DaemonServer}; -pub use terminal::{ColorSupport, TerminalInfo}; +pub use terminal::{ColorSupport, TerminalInfo, Theme}; + +/// Configuration for filtering which environment variables to pass from client to daemon. +/// +/// By default, no environment variables are passed. Use [`EnvVarFilter::with_names`] to +/// specify exact variable names to include. +/// +/// # Example +/// +/// ```rust +/// use daemon_cli::EnvVarFilter; +/// +/// // Pass specific env vars +/// let filter = EnvVarFilter::with_names(["MY_APP_DEBUG", "MY_APP_CONFIG"]); +/// +/// // Or build incrementally +/// let filter = EnvVarFilter::none() +/// .include("MY_APP_DEBUG") +/// .include("MY_APP_CONFIG"); +/// ``` +#[derive(Debug, Clone, Default)] +pub struct EnvVarFilter { + names: Vec, +} + +impl EnvVarFilter { + /// Create a filter that passes no environment variables (default). + pub fn none() -> Self { + Self { names: vec![] } + } + + /// Create a filter that passes env vars with the specified exact names. + pub fn with_names(names: impl IntoIterator>) -> Self { + Self { + names: names.into_iter().map(Into::into).collect(), + } + } + + /// Include an env var name to pass. + pub fn include(mut self, name: impl Into) -> Self { + self.names.push(name.into()); + self + } + + /// Filter environment variables from the provided source. + /// + /// This is useful for testing or when you want to filter from + /// a custom set of variables rather than the current process env. + pub fn filter_from( + &self, + env: impl IntoIterator, + ) -> HashMap + where + K: AsRef, + V: Into, + { + if self.names.is_empty() { + return HashMap::new(); + } + env.into_iter() + .filter(|(k, _)| self.names.iter().any(|n| n == k.as_ref())) + .map(|(k, v)| (k.as_ref().to_string(), v.into())) + .collect() + } + + /// Filter environment variables from the current process. + /// + /// Returns a HashMap containing only the env vars whose names match + /// those configured in this filter. + pub fn filter_current_env(&self) -> HashMap { + self.filter_from(std::env::vars()) + } +} + +/// Context information passed with each command execution. +/// +/// This struct bundles metadata about the command execution environment, +/// including terminal information and environment variables. It is designed +/// for extensibility - new fields can be added in the future without breaking +/// the handler trait signature. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct CommandContext { + /// Information about the client's terminal environment + pub terminal_info: TerminalInfo, + /// Environment variables passed from client (filtered by exact name match). + /// Empty by default for backward compatibility. + #[serde(default)] + pub env_vars: HashMap, +} + +impl CommandContext { + /// Create a new CommandContext with terminal info only (no env vars). + pub fn new(terminal_info: TerminalInfo) -> Self { + Self { + terminal_info, + env_vars: HashMap::new(), + } + } + + /// Create a CommandContext with terminal info and environment variables. + pub fn with_env(terminal_info: TerminalInfo, env_vars: HashMap) -> Self { + Self { + terminal_info, + env_vars, + } + } +} /// Reason why daemon was started. /// @@ -174,8 +281,8 @@ mod tests; /// Use `use daemon_cli::prelude::*;` to import all commonly needed items. pub mod prelude { pub use crate::{ - ColorSupport, CommandHandler, DaemonClient, DaemonHandle, DaemonServer, ErrorContextBuffer, - StartupReason, TerminalInfo, + ColorSupport, CommandContext, CommandHandler, DaemonClient, DaemonHandle, DaemonServer, + EnvVarFilter, ErrorContextBuffer, StartupReason, TerminalInfo, Theme, }; pub use anyhow::Result; pub use async_trait::async_trait; @@ -271,7 +378,7 @@ fn auto_detect_daemon_name() -> String { /// async fn handle( /// &self, /// command: &str, -/// terminal_info: TerminalInfo, +/// ctx: CommandContext, /// mut output: impl AsyncWrite + Send + Unpin, /// cancel_token: CancellationToken, /// ) -> Result { @@ -310,10 +417,10 @@ pub trait CommandHandler: Send + Sync { /// This method may be called concurrently from multiple tasks. Ensure /// your implementation is thread-safe if accessing shared state. /// - /// The `terminal_info` parameter contains information about the client's - /// terminal environment (width, height, color support, theme). Individual - /// fields may be `None` if detection failed. Use this to format output - /// appropriately for the client's terminal. + /// The `ctx` parameter contains information about the command execution + /// environment including terminal info (width, height, color support) and + /// any environment variables passed from the client. Use this to format + /// output appropriately and access client-side configuration. /// /// Write output incrementally via `output`. Long-running operations should /// check `cancel_token.is_cancelled()` to handle graceful cancellation. @@ -323,7 +430,7 @@ pub trait CommandHandler: Send + Sync { async fn handle( &self, command: &str, - terminal_info: TerminalInfo, + ctx: CommandContext, output: impl AsyncWrite + Send + Unpin, cancel_token: CancellationToken, ) -> Result; diff --git a/src/server.rs b/src/server.rs index 29fb6f5..31ad218 100644 --- a/src/server.rs +++ b/src/server.rs @@ -47,7 +47,7 @@ static CLIENT_COUNTER: AtomicU64 = AtomicU64::new(1); /// async fn handle( /// &self, /// command: &str, -/// _terminal_info: TerminalInfo, +/// _ctx: CommandContext, /// mut output: impl AsyncWrite + Send + Unpin, /// _cancel_token: CancellationToken, /// ) -> Result { @@ -303,8 +303,8 @@ where } // Receive command - let (command, terminal_info) = match connection.receive_message::().await { - Ok(Some(SocketMessage::Command { command, terminal_info })) => (command, terminal_info), + let (command, context) = match connection.receive_message::().await { + Ok(Some(SocketMessage::Command { command, context })) => (command, context), _ => { tracing::warn!("No command received from client"); return; @@ -312,11 +312,12 @@ where }; tracing::debug!( - terminal_width = ?terminal_info.width, - terminal_height = ?terminal_info.height, - is_tty = terminal_info.is_tty, - color_support = ?terminal_info.color_support, - "Received command with terminal info" + terminal_width = ?context.terminal_info.width, + terminal_height = ?context.terminal_info.height, + is_tty = context.terminal_info.is_tty, + color_support = ?context.terminal_info.color_support, + env_var_count = context.env_vars.len(), + "Received command with context" ); // Create a pipe for streaming output @@ -329,7 +330,7 @@ where // Spawn handler task (will not inherit span by default) let mut handler_task = Some(spawn(async move { handler - .handle(&command, terminal_info, output_writer, cancel_token_clone) + .handle(&command, context, output_writer, cancel_token_clone) .await })); diff --git a/src/terminal.rs b/src/terminal.rs index 4391c03..142f968 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -12,6 +12,9 @@ pub struct TerminalInfo { pub is_tty: bool, /// Level of color support (always returns at least ColorSupport::None) pub color_support: ColorSupport, + /// Terminal color theme (None if detection fails/times out) + #[serde(default)] + pub theme: Option, } /// Level of color support in the terminal @@ -27,6 +30,15 @@ pub enum ColorSupport { Truecolor, } +/// Terminal color theme (dark or light mode) +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] +pub enum Theme { + /// Dark background with light text + Dark, + /// Light background with dark text + Light, +} + impl TerminalInfo { /// Detect terminal information from the current environment /// @@ -34,6 +46,7 @@ impl TerminalInfo { /// - Terminal size (width/height) - fast, non-blocking /// - TTY detection - fast, non-blocking /// - Color support - fast, non-blocking + /// - Theme detection - up to 50ms timeout /// /// Individual detections can fail, but the function always returns /// a TerminalInfo struct with whatever information could be gathered. @@ -46,12 +59,14 @@ impl TerminalInfo { .unwrap_or((None, None)); let color_support = detect_color_support(); + let theme = detect_theme(); TerminalInfo { width, height, is_tty, color_support, + theme, } } } @@ -74,6 +89,34 @@ fn detect_color_support() -> ColorSupport { } } +/// Check if we're running in a test environment where terminal queries may hang. +/// See: https://github.com/bash/terminal-colorsaurus/issues/38 +fn is_test_environment() -> bool { + // NEXTEST is set by cargo-nextest + // RUST_TEST_THREADS is set by cargo test + std::env::var("NEXTEST").is_ok() || std::env::var("RUST_TEST_THREADS").is_ok() +} + +/// Detect terminal theme (dark/light mode) +fn detect_theme() -> Option { + use std::time::Duration; + use terminal_colorsaurus::{QueryOptions, color_scheme}; + + // Skip terminal detection in test environments to avoid hangs + if is_test_environment() { + return None; + } + + let mut options = QueryOptions::default(); + options.timeout = Duration::from_millis(50); + + match color_scheme(options) { + Ok(terminal_colorsaurus::ColorScheme::Dark) => Some(Theme::Dark), + Ok(terminal_colorsaurus::ColorScheme::Light) => Some(Theme::Light), + Err(_) => None, + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/tests.rs b/src/tests.rs index 305610a..44248a1 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,5 +1,6 @@ use crate::transport::SocketMessage; use crate::*; +use std::collections::HashMap; use tokio::io::{AsyncWrite, AsyncWriteExt}; // Test handler for unit tests @@ -19,7 +20,7 @@ impl CommandHandler for TestHandler { async fn handle( &self, _command: &str, - _terminal_info: TerminalInfo, + _ctx: CommandContext, mut output: impl AsyncWrite + Send + Unpin, _cancel: CancellationToken, ) -> Result { @@ -57,23 +58,28 @@ fn test_socket_message_serialization() { height: Some(24), is_tty: true, color_support: ColorSupport::Truecolor, + theme: None, }; + let mut env_vars = HashMap::new(); + env_vars.insert("TEST_VAR".to_string(), "test_value".to_string()); + let context = CommandContext::with_env(terminal_info.clone(), env_vars); let command_msg = SocketMessage::Command { command: "test command".to_string(), - terminal_info: terminal_info.clone(), + context, }; let serialized = serde_json::to_string(&command_msg).unwrap(); let deserialized: SocketMessage = serde_json::from_str(&serialized).unwrap(); match deserialized { - SocketMessage::Command { - command, - terminal_info: ti, - } => { + SocketMessage::Command { command, context } => { assert_eq!(command, "test command"); - assert_eq!(ti.width, Some(80)); - assert_eq!(ti.height, Some(24)); - assert!(ti.is_tty); - assert_eq!(ti.color_support, ColorSupport::Truecolor); + assert_eq!(context.terminal_info.width, Some(80)); + assert_eq!(context.terminal_info.height, Some(24)); + assert!(context.terminal_info.is_tty); + assert_eq!(context.terminal_info.color_support, ColorSupport::Truecolor); + assert_eq!( + context.env_vars.get("TEST_VAR"), + Some(&"test_value".to_string()) + ); } _ => panic!("Wrong message type"), } @@ -122,11 +128,11 @@ async fn test_handler_basic_output() { height: Some(24), is_tty: true, color_support: ColorSupport::Basic16, + theme: None, }; + let ctx = CommandContext::new(terminal_info); - let result = handler - .handle("test", terminal_info, &mut output, cancel) - .await; + let result = handler.handle("test", ctx, &mut output, cancel).await; assert!(result.is_ok()); assert_eq!(result.unwrap(), 0); // Success exit code assert_eq!(String::from_utf8(output).unwrap(), "Hello, World!"); @@ -143,7 +149,7 @@ async fn test_handler_with_cancellation() { async fn handle( &self, _command: &str, - _terminal_info: TerminalInfo, + _ctx: CommandContext, mut output: impl AsyncWrite + Send + Unpin, cancel: CancellationToken, ) -> Result { @@ -167,14 +173,47 @@ async fn test_handler_with_cancellation() { height: None, is_tty: false, color_support: ColorSupport::None, + theme: None, }; + let ctx = CommandContext::new(terminal_info); // Cancel immediately cancel.cancel(); - let result = handler - .handle("test", terminal_info, &mut output, cancel) - .await; + let result = handler.handle("test", ctx, &mut output, cancel).await; assert!(result.is_err()); assert!(String::from_utf8(output).unwrap().contains("Cancelled")); } + +#[test] +fn test_env_var_filter_none() { + let filter = EnvVarFilter::none(); + assert!(filter.filter_current_env().is_empty()); +} + +#[test] +fn test_env_var_filter_with_names() { + let mock_env = [("TEST_VAR", "test_value"), ("OTHER_VAR", "other")]; + let filter = EnvVarFilter::with_names(["TEST_VAR"]); + let filtered = filter.filter_from(mock_env); + assert_eq!(filtered.get("TEST_VAR"), Some(&"test_value".to_string())); + assert_eq!(filtered.len(), 1); +} + +#[test] +fn test_env_var_filter_include() { + let mock_env = [("VAR1", "value1"), ("VAR2", "value2"), ("VAR3", "value3")]; + let filter = EnvVarFilter::none().include("VAR1").include("VAR2"); + let filtered = filter.filter_from(mock_env); + assert_eq!(filtered.len(), 2); + assert_eq!(filtered.get("VAR1"), Some(&"value1".to_string())); + assert_eq!(filtered.get("VAR2"), Some(&"value2".to_string())); +} + +#[test] +fn test_env_var_filter_missing_var() { + // Filter for a var that doesn't exist + let filter = EnvVarFilter::with_names(["NONEXISTENT_VAR_12345"]); + let filtered = filter.filter_current_env(); + assert!(filtered.is_empty()); +} diff --git a/src/transport.rs b/src/transport.rs index 43cd876..96b08ee 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -1,4 +1,4 @@ -use crate::terminal::TerminalInfo; +use crate::CommandContext; use anyhow::Result; use futures::{SinkExt, StreamExt}; #[cfg(unix)] @@ -235,7 +235,7 @@ pub enum SocketMessage { }, Command { command: String, - terminal_info: TerminalInfo, + context: CommandContext, }, OutputChunk(Vec), CommandComplete { diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index fb6f17a..a9023d5 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -85,7 +85,7 @@ impl CommandHandler for EchoHandler { async fn handle( &self, command: &str, - _terminal_info: TerminalInfo, + _ctx: CommandContext, mut output: impl AsyncWrite + Send + Unpin, _cancel: CancellationToken, ) -> Result { @@ -105,7 +105,7 @@ impl CommandHandler for ChunkedHandler { async fn handle( &self, _command: &str, - _terminal_info: TerminalInfo, + _ctx: CommandContext, mut output: impl AsyncWrite + Send + Unpin, _cancel: CancellationToken, ) -> Result { @@ -128,7 +128,7 @@ impl CommandHandler for CancellableHandler { async fn handle( &self, _command: &str, - _terminal_info: TerminalInfo, + _ctx: CommandContext, mut output: impl AsyncWrite + Send + Unpin, cancel: CancellationToken, ) -> Result { @@ -153,7 +153,7 @@ impl CommandHandler for ErrorHandler { async fn handle( &self, _command: &str, - _terminal_info: TerminalInfo, + _ctx: CommandContext, mut output: impl AsyncWrite + Send + Unpin, _cancel: CancellationToken, ) -> Result { @@ -356,7 +356,7 @@ impl CommandHandler for ConcurrentTrackingHandler { async fn handle( &self, _command: &str, - _terminal_info: TerminalInfo, + _ctx: CommandContext, mut output: impl AsyncWrite + Send + Unpin, _cancel: CancellationToken, ) -> Result { @@ -785,7 +785,7 @@ impl CommandHandler for ImmediateOutputHandler { async fn handle( &self, _command: &str, - _terminal_info: TerminalInfo, + _ctx: CommandContext, mut output: impl AsyncWrite + Send + Unpin, _cancel: CancellationToken, ) -> Result { @@ -841,7 +841,7 @@ impl CommandHandler for LargeOutputHandler { async fn handle( &self, _command: &str, - _terminal_info: TerminalInfo, + _ctx: CommandContext, mut output: impl AsyncWrite + Send + Unpin, _cancel: CancellationToken, ) -> Result { @@ -897,7 +897,7 @@ impl CommandHandler for PanicHandler { async fn handle( &self, _command: &str, - _terminal_info: TerminalInfo, + _ctx: CommandContext, mut output: impl AsyncWrite + Send + Unpin, _cancel: CancellationToken, ) -> Result { @@ -1087,7 +1087,7 @@ async fn test_connection_limit_immediate_rejection() -> Result<()> { async fn handle( &self, _command: &str, - _terminal_info: TerminalInfo, + _ctx: CommandContext, mut output: impl AsyncWrite + Send + Unpin, _cancel: CancellationToken, ) -> Result { diff --git a/tests/version_tests.rs b/tests/version_tests.rs index 81db60e..d81a3b6 100644 --- a/tests/version_tests.rs +++ b/tests/version_tests.rs @@ -19,7 +19,7 @@ impl CommandHandler for SimpleHandler { async fn handle( &self, command: &str, - _terminal_info: TerminalInfo, + _ctx: CommandContext, mut output: impl AsyncWrite + Send + Unpin, _cancel: CancellationToken, ) -> Result { @@ -218,11 +218,12 @@ async fn test_version_handshake_before_command() -> Result<()> { height: Some(24), is_tty: true, color_support: ColorSupport::Basic16, + theme: None, }; client .send_message(&SocketMessage::Command { command: "test command".to_string(), - terminal_info, + context: CommandContext::new(terminal_info), }) .await?; @@ -267,11 +268,12 @@ async fn test_command_without_handshake_fails() -> Result<()> { height: None, is_tty: false, color_support: ColorSupport::None, + theme: None, }; client .send_message(&SocketMessage::Command { command: "test".to_string(), - terminal_info, + context: CommandContext::new(terminal_info), }) .await?; @@ -447,11 +449,12 @@ async fn test_multiple_commands_same_connection() -> Result<()> { height: Some(24), is_tty: true, color_support: ColorSupport::Basic16, + theme: None, }; client .send_message(&SocketMessage::Command { command: "first command".to_string(), - terminal_info: terminal_info.clone(), + context: CommandContext::new(terminal_info.clone()), }) .await?; @@ -477,7 +480,7 @@ async fn test_multiple_commands_same_connection() -> Result<()> { client .send_message(&SocketMessage::Command { command: "second command".to_string(), - terminal_info: terminal_info.clone(), + context: CommandContext::new(terminal_info.clone()), }) .await?;