use std::str::FromStr; use crate::{ client_server_contract::client_server_contract::{ client_to_server_msg, server_to_client_msg, ActionMsg, AttachClientMsg, AttachWatcherClientMsg, BackgroundColorMsg, CliPipeOutputMsg, ClientExitedMsg, ClientToServerMsg as ProtoClientToServerMsg, ColorRegistersMsg, ConfigFileUpdatedMsg, ConnStatusMsg, ConnectedMsg, DesktopNotificationResponseMsg, DetachSessionMsg, EmitNestedSessionFrameMsg, ExitMsg, ExitReason as ProtoExitReason, FailedToStartWebServerMsg, FirstClientConnectedMsg, ForegroundColorMsg, ForwardQueryToHostMsg, ForwardedReplyFromHostMsg, HostTerminalThemeChangedMsg, HostTerminalThemeIndication as ProtoHostTerminalThemeIndication, InputMode as ProtoInputMode, KeyMsg, KillSessionMsg, KittyGraphicsSupportMsg, LayoutMetadata as ProtoLayoutMetadata, LogErrorMsg, LogMsg, NestedSessionFrameFromHostMsg, PaneMetadata as ProtoPaneMetadata, PaneRenderUpdateMsg, QueryTerminalSizeMsg, RenamedSessionMsg, RenderMsg, ResizeCause as ProtoResizeCause, ServerToClientMsg as ProtoServerToClientMsg, SetSoftKeyboardMsg, SoftKeyboardVisibilityChangedMsg, StartWebServerMsg, SubscribeToPaneRendersMsg, SubscribedPaneClosedMsg, SwitchSessionMsg, TabMetadata as ProtoTabMetadata, TerminalPixelDimensionsMsg, TerminalResizeMsg, UnblockCliPipeInputMsg, UnblockInputThreadMsg, WebServerStartedMsg, }, data::{HostTerminalThemeMode, InputMode, PaneId}, errors::prelude::*, ipc::{ ClientToServerMsg, ColorRegister, ExitReason, PaneReference, PixelDimensions, ResizeCause, ServerToClientMsg, }, }; use std::collections::BTreeMap; use std::path::PathBuf; // Convert Rust ClientToServerMsg to protobuf impl From for ProtoClientToServerMsg { fn from(msg: ClientToServerMsg) -> Self { let message = match msg { ClientToServerMsg::DetachSession { client_ids } => { client_to_server_msg::Message::DetachSession(DetachSessionMsg { client_ids: client_ids.into_iter().map(|id| id as u32).collect(), }) }, ClientToServerMsg::TerminalPixelDimensions { pixel_dimensions } => { client_to_server_msg::Message::TerminalPixelDimensions(TerminalPixelDimensionsMsg { pixel_dimensions: Some(pixel_dimensions.into()), }) }, ClientToServerMsg::BackgroundColor { color } => { client_to_server_msg::Message::BackgroundColor(BackgroundColorMsg { color }) }, ClientToServerMsg::ForegroundColor { color } => { client_to_server_msg::Message::ForegroundColor(ForegroundColorMsg { color }) }, ClientToServerMsg::ColorRegisters { color_registers } => { client_to_server_msg::Message::ColorRegisters(ColorRegistersMsg { color_registers: color_registers.into_iter().map(|cr| cr.into()).collect(), }) }, ClientToServerMsg::TerminalResize { new_size, cause } => { let cause_proto: ProtoResizeCause = cause.into(); client_to_server_msg::Message::TerminalResize(TerminalResizeMsg { new_size: Some(new_size.into()), cause: cause_proto as i32, }) }, ClientToServerMsg::FirstClientConnected { cli_assets, is_web_client, } => client_to_server_msg::Message::FirstClientConnected(FirstClientConnectedMsg { cli_assets: Some(cli_assets.into()), is_web_client, }), ClientToServerMsg::AttachClient { cli_assets, tab_position_to_focus, pane_to_focus, is_web_client, } => client_to_server_msg::Message::AttachClient(AttachClientMsg { cli_assets: Some(cli_assets.into()), tab_position_to_focus: tab_position_to_focus.map(|pos| pos as u32), pane_to_focus: pane_to_focus.map(|p| p.into()), is_web_client, }), ClientToServerMsg::AttachWatcherClient { terminal_size, is_web_client, } => client_to_server_msg::Message::AttachWatcherClient(AttachWatcherClientMsg { terminal_size: Some(terminal_size.into()), is_web_client, }), ClientToServerMsg::Action { action, terminal_id, client_id, is_cli_client, } => client_to_server_msg::Message::Action(ActionMsg { action: Some(action.into()), terminal_id, client_id: client_id.map(|id| id as u32), is_cli_client, }), ClientToServerMsg::Key { key, raw_bytes, is_kitty_keyboard_protocol, } => client_to_server_msg::Message::Key(KeyMsg { key: Some(key.into()), raw_bytes: raw_bytes.into_iter().map(|b| b as u32).collect(), is_kitty_keyboard_protocol, }), ClientToServerMsg::ClientExited => { client_to_server_msg::Message::ClientExited(ClientExitedMsg {}) }, ClientToServerMsg::KillSession => { client_to_server_msg::Message::KillSession(KillSessionMsg {}) }, ClientToServerMsg::ConnStatus => { client_to_server_msg::Message::ConnStatus(ConnStatusMsg {}) }, ClientToServerMsg::WebServerStarted { base_url } => { client_to_server_msg::Message::WebServerStarted(WebServerStartedMsg { base_url }) }, ClientToServerMsg::FailedToStartWebServer { error } => { client_to_server_msg::Message::FailedToStartWebServer(FailedToStartWebServerMsg { error, }) }, ClientToServerMsg::SubscribeToPaneRenders { pane_ids, scrollback, ansi, } => client_to_server_msg::Message::SubscribeToPaneRenders(SubscribeToPaneRendersMsg { pane_ids: pane_ids.into_iter().map(|id| id.into()).collect(), scrollback: scrollback.map(|s| s as u32), ansi, }), ClientToServerMsg::DesktopNotificationResponse { raw_bytes } => { client_to_server_msg::Message::DesktopNotificationResponse( DesktopNotificationResponseMsg { raw_bytes }, ) }, ClientToServerMsg::ForwardedReplyFromHost { token, reply_bytes } => { client_to_server_msg::Message::ForwardedReplyFromHost(ForwardedReplyFromHostMsg { token, reply_bytes, }) }, ClientToServerMsg::HostTerminalThemeChanged { mode } => { let proto_mode: ProtoHostTerminalThemeIndication = mode.into(); client_to_server_msg::Message::HostTerminalThemeChanged( HostTerminalThemeChangedMsg { mode: proto_mode as i32, }, ) }, ClientToServerMsg::SoftKeyboardVisibilityChanged { visible } => { client_to_server_msg::Message::SoftKeyboardVisibilityChanged( SoftKeyboardVisibilityChangedMsg { visible }, ) }, ClientToServerMsg::NestedSessionFrameFromHost { payload_bytes } => { client_to_server_msg::Message::NestedSessionFrameFromHost( NestedSessionFrameFromHostMsg { payload_bytes }, ) }, ClientToServerMsg::KittyGraphicsSupport { supported } => { client_to_server_msg::Message::KittyGraphicsSupport(KittyGraphicsSupportMsg { supported, }) }, }; ProtoClientToServerMsg { message: Some(message), } } } // Convert protobuf ClientToServerMsg to Rust impl TryFrom for ClientToServerMsg { type Error = anyhow::Error; fn try_from(msg: ProtoClientToServerMsg) -> Result { match msg.message { Some(client_to_server_msg::Message::DetachSession(detach)) => { Ok(ClientToServerMsg::DetachSession { client_ids: detach.client_ids.into_iter().map(|id| id as u16).collect(), }) }, Some(client_to_server_msg::Message::TerminalPixelDimensions(pixel_dims)) => { Ok(ClientToServerMsg::TerminalPixelDimensions { pixel_dimensions: pixel_dims .pixel_dimensions .ok_or_else(|| anyhow!("Missing pixel_dimensions"))? .try_into()?, }) }, Some(client_to_server_msg::Message::BackgroundColor(bg_color)) => { Ok(ClientToServerMsg::BackgroundColor { color: bg_color.color, }) }, Some(client_to_server_msg::Message::ForegroundColor(fg_color)) => { Ok(ClientToServerMsg::ForegroundColor { color: fg_color.color, }) }, Some(client_to_server_msg::Message::ColorRegisters(color_regs)) => { Ok(ClientToServerMsg::ColorRegisters { color_registers: color_regs .color_registers .into_iter() .map(|cr| cr.try_into()) .collect::>>()?, }) }, Some(client_to_server_msg::Message::TerminalResize(resize)) => { let cause = ProtoResizeCause::from_i32(resize.cause) .unwrap_or(ProtoResizeCause::Viewport) .into(); Ok(ClientToServerMsg::TerminalResize { new_size: resize .new_size .ok_or_else(|| anyhow!("Missing new_size"))? .try_into()?, cause, }) }, Some(client_to_server_msg::Message::FirstClientConnected(first_client)) => { Ok(ClientToServerMsg::FirstClientConnected { cli_assets: first_client .cli_assets .ok_or_else(|| anyhow!("Missing cli_assets"))? .try_into()?, is_web_client: first_client.is_web_client, }) }, Some(client_to_server_msg::Message::AttachClient(attach)) => { Ok(ClientToServerMsg::AttachClient { cli_assets: attach .cli_assets .ok_or_else(|| anyhow!("Missing cli_assets"))? .try_into()?, tab_position_to_focus: attach.tab_position_to_focus.map(|pos| pos as usize), pane_to_focus: attach.pane_to_focus.map(|p| p.try_into()).transpose()?, is_web_client: attach.is_web_client, }) }, Some(client_to_server_msg::Message::AttachWatcherClient(attach_watcher)) => { Ok(ClientToServerMsg::AttachWatcherClient { terminal_size: attach_watcher .terminal_size .ok_or_else(|| anyhow::anyhow!("Missing terminal_size"))? .try_into()?, is_web_client: attach_watcher.is_web_client, }) }, Some(client_to_server_msg::Message::Action(action)) => Ok(ClientToServerMsg::Action { action: action .action .ok_or_else(|| anyhow!("Missing action"))? .try_into()?, terminal_id: action.terminal_id, client_id: action.client_id.map(|id| id as u16), is_cli_client: action.is_cli_client, }), Some(client_to_server_msg::Message::Key(key)) => Ok(ClientToServerMsg::Key { key: key.key.ok_or_else(|| anyhow!("Missing key"))?.try_into()?, raw_bytes: key.raw_bytes.into_iter().map(|b| b as u8).collect(), is_kitty_keyboard_protocol: key.is_kitty_keyboard_protocol, }), Some(client_to_server_msg::Message::ClientExited(_)) => { Ok(ClientToServerMsg::ClientExited) }, Some(client_to_server_msg::Message::KillSession(_)) => { Ok(ClientToServerMsg::KillSession) }, Some(client_to_server_msg::Message::ConnStatus(_)) => Ok(ClientToServerMsg::ConnStatus), Some(client_to_server_msg::Message::WebServerStarted(web_server)) => { Ok(ClientToServerMsg::WebServerStarted { base_url: web_server.base_url, }) }, Some(client_to_server_msg::Message::FailedToStartWebServer(failed)) => { Ok(ClientToServerMsg::FailedToStartWebServer { error: failed.error, }) }, Some(client_to_server_msg::Message::SubscribeToPaneRenders(msg)) => { let pane_ids: Result> = msg.pane_ids.into_iter().map(|p| p.try_into()).collect(); Ok(ClientToServerMsg::SubscribeToPaneRenders { pane_ids: pane_ids?, scrollback: msg.scrollback.map(|s| s as usize), ansi: msg.ansi, }) }, Some(client_to_server_msg::Message::DesktopNotificationResponse(msg)) => { Ok(ClientToServerMsg::DesktopNotificationResponse { raw_bytes: msg.raw_bytes, }) }, Some(client_to_server_msg::Message::ForwardedReplyFromHost(msg)) => { Ok(ClientToServerMsg::ForwardedReplyFromHost { token: msg.token, reply_bytes: msg.reply_bytes, }) }, Some(client_to_server_msg::Message::HostTerminalThemeChanged(msg)) => { let proto_mode = ProtoHostTerminalThemeIndication::from_i32(msg.mode) .ok_or_else(|| anyhow!("Unknown HostTerminalThemeIndication: {}", msg.mode))?; Ok(ClientToServerMsg::HostTerminalThemeChanged { mode: proto_mode.into(), }) }, Some(client_to_server_msg::Message::SoftKeyboardVisibilityChanged(msg)) => { Ok(ClientToServerMsg::SoftKeyboardVisibilityChanged { visible: msg.visible, }) }, Some(client_to_server_msg::Message::NestedSessionFrameFromHost(msg)) => { Ok(ClientToServerMsg::NestedSessionFrameFromHost { payload_bytes: msg.payload_bytes, }) }, Some(client_to_server_msg::Message::KittyGraphicsSupport(msg)) => { Ok(ClientToServerMsg::KittyGraphicsSupport { supported: msg.supported, }) }, None => Err(anyhow!("Empty ClientToServerMsg message")), } } } // Convert Rust ServerToClientMsg to protobuf impl From for ProtoServerToClientMsg { fn from(msg: ServerToClientMsg) -> Self { let message = match msg { ServerToClientMsg::Render { content } => { server_to_client_msg::Message::Render(RenderMsg { content }) }, ServerToClientMsg::UnblockInputThread => { server_to_client_msg::Message::UnblockInputThread(UnblockInputThreadMsg {}) }, ServerToClientMsg::Exit { exit_reason } => { let (proto_exit_reason, payload) = match exit_reason { ExitReason::Error(ref msg) => (ProtoExitReason::Error, Some(msg.clone())), ExitReason::CustomExitStatus(status) => { (ProtoExitReason::CustomExitStatus, Some(status.to_string())) }, other => (ProtoExitReason::from(other), None), }; server_to_client_msg::Message::Exit(ExitMsg { exit_reason: proto_exit_reason as i32, payload, }) }, ServerToClientMsg::Connected => { server_to_client_msg::Message::Connected(ConnectedMsg {}) }, ServerToClientMsg::Log { lines } => { server_to_client_msg::Message::Log(LogMsg { lines }) }, ServerToClientMsg::LogError { lines } => { server_to_client_msg::Message::LogError(LogErrorMsg { lines }) }, ServerToClientMsg::SwitchSession { connect_to_session } => { server_to_client_msg::Message::SwitchSession(SwitchSessionMsg { connect_to_session: Some(connect_to_session.into()), }) }, ServerToClientMsg::UnblockCliPipeInput { pipe_name } => { server_to_client_msg::Message::UnblockCliPipeInput(UnblockCliPipeInputMsg { pipe_name, }) }, ServerToClientMsg::CliPipeOutput { pipe_name, output } => { server_to_client_msg::Message::CliPipeOutput(CliPipeOutputMsg { pipe_name, output }) }, ServerToClientMsg::QueryTerminalSize => { server_to_client_msg::Message::QueryTerminalSize(QueryTerminalSizeMsg {}) }, ServerToClientMsg::StartWebServer => { server_to_client_msg::Message::StartWebServer(StartWebServerMsg {}) }, ServerToClientMsg::RenamedSession { name } => { server_to_client_msg::Message::RenamedSession(RenamedSessionMsg { name }) }, ServerToClientMsg::ConfigFileUpdated => { server_to_client_msg::Message::ConfigFileUpdated(ConfigFileUpdatedMsg {}) }, ServerToClientMsg::PaneRenderUpdate { pane_id, viewport, scrollback, is_initial, } => server_to_client_msg::Message::PaneRenderUpdate(PaneRenderUpdateMsg { pane_id: Some(pane_id.into()), viewport, scrollback: scrollback.clone().unwrap_or_default(), has_scrollback: scrollback.is_some(), is_initial, }), ServerToClientMsg::SubscribedPaneClosed { pane_id } => { server_to_client_msg::Message::SubscribedPaneClosed(SubscribedPaneClosedMsg { pane_id: Some(pane_id.into()), }) }, ServerToClientMsg::ForwardQueryToHost { token, query_bytes } => { server_to_client_msg::Message::ForwardQueryToHost(ForwardQueryToHostMsg { token, query_bytes, }) }, ServerToClientMsg::SetSoftKeyboard { on } => { server_to_client_msg::Message::SetSoftKeyboard(SetSoftKeyboardMsg { on }) }, ServerToClientMsg::EmitNestedSessionFrame { payload_bytes } => { server_to_client_msg::Message::EmitNestedSessionFrame(EmitNestedSessionFrameMsg { payload_bytes, }) }, }; ProtoServerToClientMsg { message: Some(message), } } } // Convert protobuf ServerToClientMsg to Rust impl TryFrom for ServerToClientMsg { type Error = anyhow::Error; fn try_from(msg: ProtoServerToClientMsg) -> Result { match msg.message { Some(server_to_client_msg::Message::Render(render)) => Ok(ServerToClientMsg::Render { content: render.content, }), Some(server_to_client_msg::Message::UnblockInputThread(_)) => { Ok(ServerToClientMsg::UnblockInputThread) }, Some(server_to_client_msg::Message::Exit(exit)) => { let proto_exit_reason = ProtoExitReason::from_i32(exit.exit_reason) .ok_or_else(|| anyhow!("Invalid exit_reason"))?; let exit_reason = match proto_exit_reason { ProtoExitReason::Error => { let error_msg = exit.payload.unwrap_or_else(|| "Protobuf error".to_string()); ExitReason::Error(error_msg) }, ProtoExitReason::CustomExitStatus => { let status_str = exit.payload.unwrap_or_else(|| "0".to_string()); let status = status_str .parse::() .map_err(|_| anyhow!("Invalid custom exit status: {}", status_str))?; ExitReason::CustomExitStatus(status) }, other => other.try_into()?, }; Ok(ServerToClientMsg::Exit { exit_reason }) }, Some(server_to_client_msg::Message::Connected(_)) => Ok(ServerToClientMsg::Connected), Some(server_to_client_msg::Message::Log(log)) => { Ok(ServerToClientMsg::Log { lines: log.lines }) }, Some(server_to_client_msg::Message::LogError(log_error)) => { Ok(ServerToClientMsg::LogError { lines: log_error.lines, }) }, Some(server_to_client_msg::Message::SwitchSession(switch)) => { Ok(ServerToClientMsg::SwitchSession { connect_to_session: switch .connect_to_session .ok_or_else(|| anyhow!("Missing connect_to_session"))? .try_into()?, }) }, Some(server_to_client_msg::Message::UnblockCliPipeInput(unblock)) => { Ok(ServerToClientMsg::UnblockCliPipeInput { pipe_name: unblock.pipe_name, }) }, Some(server_to_client_msg::Message::CliPipeOutput(pipe_output)) => { Ok(ServerToClientMsg::CliPipeOutput { pipe_name: pipe_output.pipe_name, output: pipe_output.output, }) }, Some(server_to_client_msg::Message::QueryTerminalSize(_)) => { Ok(ServerToClientMsg::QueryTerminalSize) }, Some(server_to_client_msg::Message::StartWebServer(_)) => { Ok(ServerToClientMsg::StartWebServer) }, Some(server_to_client_msg::Message::RenamedSession(renamed)) => { Ok(ServerToClientMsg::RenamedSession { name: renamed.name }) }, Some(server_to_client_msg::Message::ConfigFileUpdated(_)) => { Ok(ServerToClientMsg::ConfigFileUpdated) }, Some(server_to_client_msg::Message::PaneRenderUpdate(msg)) => { let pane_id: PaneId = msg .pane_id .ok_or_else(|| anyhow!("Missing pane_id"))? .try_into()?; let scrollback = if msg.has_scrollback { Some(msg.scrollback) } else { None }; Ok(ServerToClientMsg::PaneRenderUpdate { pane_id, viewport: msg.viewport, scrollback, is_initial: msg.is_initial, }) }, Some(server_to_client_msg::Message::SubscribedPaneClosed(msg)) => { let pane_id: PaneId = msg .pane_id .ok_or_else(|| anyhow!("Missing pane_id"))? .try_into()?; Ok(ServerToClientMsg::SubscribedPaneClosed { pane_id }) }, Some(server_to_client_msg::Message::ForwardQueryToHost(msg)) => { Ok(ServerToClientMsg::ForwardQueryToHost { token: msg.token, query_bytes: msg.query_bytes, }) }, Some(server_to_client_msg::Message::SetSoftKeyboard(msg)) => { Ok(ServerToClientMsg::SetSoftKeyboard { on: msg.on }) }, Some(server_to_client_msg::Message::EmitNestedSessionFrame(msg)) => { Ok(ServerToClientMsg::EmitNestedSessionFrame { payload_bytes: msg.payload_bytes, }) }, None => Err(anyhow!("Empty ServerToClientMsg message")), } } } // Basic type conversions impl From for crate::client_server_contract::client_server_contract::Size { fn from(size: crate::pane_size::Size) -> Self { Self { cols: size.cols as u32, rows: size.rows as u32, } } } impl TryFrom for crate::pane_size::Size { type Error = anyhow::Error; fn try_from(size: crate::client_server_contract::client_server_contract::Size) -> Result { Ok(Self { rows: size.rows as usize, cols: size.cols as usize, }) } } impl From for crate::client_server_contract::client_server_contract::PixelDimensions { fn from(pixel_dims: PixelDimensions) -> Self { Self { text_area_size: pixel_dims.text_area_size.map(|size| { crate::client_server_contract::client_server_contract::SizeInPixels { width: size.width as u32, height: size.height as u32, } }), character_cell_size: pixel_dims.character_cell_size.map(|size| { crate::client_server_contract::client_server_contract::SizeInPixels { width: size.width as u32, height: size.height as u32, } }), } } } impl TryFrom for PixelDimensions { type Error = anyhow::Error; fn try_from( pixel_dims: crate::client_server_contract::client_server_contract::PixelDimensions, ) -> Result { Ok(Self { text_area_size: pixel_dims .text_area_size .map(|size| crate::pane_size::SizeInPixels { width: size.width as usize, height: size.height as usize, }), character_cell_size: pixel_dims.character_cell_size.map(|size| { crate::pane_size::SizeInPixels { width: size.width as usize, height: size.height as usize, } }), }) } } impl From for crate::client_server_contract::client_server_contract::PaneReference { fn from(pane_ref: PaneReference) -> Self { Self { pane_id: pane_ref.pane_id, is_plugin: pane_ref.is_plugin, } } } impl TryFrom for PaneReference { type Error = anyhow::Error; fn try_from( pane_ref: crate::client_server_contract::client_server_contract::PaneReference, ) -> Result { Ok(Self { pane_id: pane_ref.pane_id, is_plugin: pane_ref.is_plugin, }) } } impl From for crate::client_server_contract::client_server_contract::ColorRegister { fn from(color_reg: ColorRegister) -> Self { Self { index: color_reg.index as u32, color: color_reg.color, } } } impl TryFrom for ColorRegister { type Error = anyhow::Error; fn try_from( color_reg: crate::client_server_contract::client_server_contract::ColorRegister, ) -> Result { Ok(Self { index: color_reg.index as usize, color: color_reg.color, }) } } impl From for crate::client_server_contract::client_server_contract::CliAssets { fn from(cli_assets: crate::input::cli_assets::CliAssets) -> Self { Self { config_file_path: cli_assets .config_file_path .map(|p| p.to_string_lossy().to_string()), config_dir: cli_assets .config_dir .map(|p| p.to_string_lossy().to_string()), should_ignore_config: cli_assets.should_ignore_config, configuration_options: cli_assets.configuration_options.map(|o| o.into()), layout: cli_assets.layout.map(|l| l.into()), terminal_window_size: Some(cli_assets.terminal_window_size.into()), data_dir: cli_assets.data_dir.map(|p| p.to_string_lossy().to_string()), is_debug: cli_assets.is_debug, max_panes: cli_assets.max_panes.map(|m| m as u32), force_run_layout_commands: cli_assets.force_run_layout_commands, cwd: cli_assets.cwd.map(|p| p.to_string_lossy().to_string()), } } } impl TryFrom for crate::input::cli_assets::CliAssets { type Error = anyhow::Error; fn try_from( cli_assets: crate::client_server_contract::client_server_contract::CliAssets, ) -> Result { Ok(Self { config_file_path: cli_assets.config_file_path.map(PathBuf::from), config_dir: cli_assets.config_dir.map(PathBuf::from), should_ignore_config: cli_assets.should_ignore_config, configuration_options: cli_assets .configuration_options .map(|o| o.try_into()) .transpose()?, layout: cli_assets.layout.map(|l| l.try_into()).transpose()?, terminal_window_size: cli_assets .terminal_window_size .ok_or_else(|| anyhow!("CliAssets missing terminal_window_size"))? .try_into()?, data_dir: cli_assets.data_dir.map(PathBuf::from), is_debug: cli_assets.is_debug, max_panes: cli_assets.max_panes.map(|m| m as usize), force_run_layout_commands: cli_assets.force_run_layout_commands, cwd: cli_assets.cwd.map(PathBuf::from), }) } } impl From for crate::client_server_contract::client_server_contract::Options { fn from(options: crate::input::options::Options) -> Self { use crate::client_server_contract::client_server_contract::{ Clipboard as ProtoClipboard, OnForceClose as ProtoOnForceClose, WebSharing as ProtoWebSharing, }; Self { simplified_ui: options.simplified_ui, theme: options.theme, theme_dark: options.theme_dark, theme_light: options.theme_light, default_mode: options.default_mode.map(|m| input_mode_to_proto_i32(m)), default_shell: options .default_shell .map(|p| p.to_string_lossy().to_string()), default_cwd: options.default_cwd.map(|p| p.to_string_lossy().to_string()), default_layout: options .default_layout .map(|p| p.to_string_lossy().to_string()), layout_dir: options.layout_dir.map(|p| p.to_string_lossy().to_string()), theme_dir: options.theme_dir.map(|p| p.to_string_lossy().to_string()), mouse_mode: options.mouse_mode, pane_frames: options.pane_frames, mirror_session: options.mirror_session, on_force_close: options.on_force_close.map(|o| match o { crate::input::options::OnForceClose::Quit => ProtoOnForceClose::Quit as i32, crate::input::options::OnForceClose::Detach => ProtoOnForceClose::Detach as i32, }), scroll_buffer_size: options.scroll_buffer_size.map(|s| s as u32), copy_command: options.copy_command, copy_clipboard: options.copy_clipboard.map(|c| match c { crate::input::options::Clipboard::System => ProtoClipboard::System as i32, crate::input::options::Clipboard::Primary => ProtoClipboard::Primary as i32, }), copy_on_select: options.copy_on_select, osc8_hyperlinks: options.osc8_hyperlinks, scrollback_editor: options .scrollback_editor .map(|p| p.to_string_lossy().to_string()), session_name: options.session_name, attach_to_session: options.attach_to_session, auto_layout: options.auto_layout, session_serialization: options.session_serialization, serialize_pane_viewport: options.serialize_pane_viewport, scrollback_lines_to_serialize: options.scrollback_lines_to_serialize.map(|s| s as u32), styled_underlines: options.styled_underlines, serialization_interval: options.serialization_interval, disable_session_metadata: options.disable_session_metadata, support_kitty_keyboard_protocol: options.support_kitty_keyboard_protocol, support_kitty_graphics_protocol: options.support_kitty_graphics_protocol, web_server: options.web_server, web_sharing: options.web_sharing.map(|w| match w { crate::data::WebSharing::On => ProtoWebSharing::On as i32, crate::data::WebSharing::Off => ProtoWebSharing::Off as i32, crate::data::WebSharing::Disabled => ProtoWebSharing::Disabled as i32, }), stacked_resize: options.stacked_resize, stacked_pane_list: options.stacked_pane_list, show_startup_tips: options.show_startup_tips, show_release_notes: options.show_release_notes, advanced_mouse_actions: options.advanced_mouse_actions, mouse_scroll_resize: options.mouse_scroll_resize, mouse_hover_effects: options.mouse_hover_effects, web_server_ip: options.web_server_ip.map(|ip| ip.to_string()), web_server_port: options.web_server_port.map(|p| p as u32), web_server_cert: options .web_server_cert .map(|p| p.to_string_lossy().to_string()), web_server_key: options .web_server_key .map(|p| p.to_string_lossy().to_string()), enforce_https_for_localhost: options.enforce_https_for_localhost, post_command_discovery_hook: options.post_command_discovery_hook, client_async_worker_tasks: options.client_async_worker_tasks.map(|v| v as u64), visual_bell: options.visual_bell, focus_follows_mouse: options.focus_follows_mouse, mouse_click_through: options.mouse_click_through, mobile_layout: options.mobile_layout.map(|m| { use crate::client_server_contract::client_server_contract::MobileLayout as ProtoMobileLayout; use crate::input::options::MobileLayoutConfiguration; match m { MobileLayoutConfiguration::Web => ProtoMobileLayout::Web as i32, MobileLayoutConfiguration::Always => ProtoMobileLayout::Always as i32, MobileLayoutConfiguration::Never => ProtoMobileLayout::Never as i32, } }), mobile_threshold_cols: options.mobile_threshold_cols.map(|v| v as u32), mobile_threshold_rows: options.mobile_threshold_rows.map(|v| v as u32), pane_frame_style: options.pane_frame_style.map(|s| match s { crate::input::options::PaneFrameStyle::Full => "full".to_owned(), crate::input::options::PaneFrameStyle::Titles => "titles".to_owned(), crate::input::options::PaneFrameStyle::None => "none".to_owned(), }), nested_session_handling: options.nested_session_handling.map(|n| { use crate::client_server_contract::client_server_contract::NestedSessionHandling as ProtoNestedSessionHandling; use crate::input::options::NestedSessionHandling; match n { NestedSessionHandling::Ask => ProtoNestedSessionHandling::Ask as i32, NestedSessionHandling::Fullscreen => { ProtoNestedSessionHandling::Fullscreen as i32 }, NestedSessionHandling::Descend => ProtoNestedSessionHandling::Descend as i32, NestedSessionHandling::Never => ProtoNestedSessionHandling::Never as i32, } }), } } } impl TryFrom for crate::input::options::Options { type Error = anyhow::Error; fn try_from( options: crate::client_server_contract::client_server_contract::Options, ) -> Result { use crate::client_server_contract::client_server_contract::{ Clipboard as ProtoClipboard, MobileLayout as ProtoMobileLayout, NestedSessionHandling as ProtoNestedSessionHandling, OnForceClose as ProtoOnForceClose, WebSharing as ProtoWebSharing, }; Ok(Self { simplified_ui: options.simplified_ui, theme: options.theme, theme_dark: options.theme_dark, theme_light: options.theme_light, default_mode: options .default_mode .map(|m| proto_i32_to_input_mode(m)) .transpose()?, default_shell: options.default_shell.map(std::path::PathBuf::from), default_cwd: options.default_cwd.map(std::path::PathBuf::from), default_layout: options.default_layout.map(std::path::PathBuf::from), layout_dir: options.layout_dir.map(std::path::PathBuf::from), theme_dir: options.theme_dir.map(std::path::PathBuf::from), mouse_mode: options.mouse_mode, pane_frames: options.pane_frames, pane_frame_style: options.pane_frame_style.as_deref().and_then(|s| match s { "full" => Some(crate::input::options::PaneFrameStyle::Full), "titles" => Some(crate::input::options::PaneFrameStyle::Titles), "none" => Some(crate::input::options::PaneFrameStyle::None), _ => None, }), mirror_session: options.mirror_session, on_force_close: options .on_force_close .map(|o| match ProtoOnForceClose::from_i32(o) { Some(ProtoOnForceClose::Quit) => Ok(crate::input::options::OnForceClose::Quit), Some(ProtoOnForceClose::Detach) => { Ok(crate::input::options::OnForceClose::Detach) }, _ => Err(anyhow!("Invalid OnForceClose value: {}", o)), }) .transpose()?, scroll_buffer_size: options.scroll_buffer_size.map(|s| s as usize), copy_command: options.copy_command, copy_clipboard: options .copy_clipboard .map(|c| match ProtoClipboard::from_i32(c) { Some(ProtoClipboard::System) => Ok(crate::input::options::Clipboard::System), Some(ProtoClipboard::Primary) => Ok(crate::input::options::Clipboard::Primary), _ => Err(anyhow!("Invalid Clipboard value: {}", c)), }) .transpose()?, copy_on_select: options.copy_on_select, osc8_hyperlinks: options.osc8_hyperlinks, scrollback_editor: options.scrollback_editor.map(std::path::PathBuf::from), session_name: options.session_name, attach_to_session: options.attach_to_session, auto_layout: options.auto_layout, session_serialization: options.session_serialization, serialize_pane_viewport: options.serialize_pane_viewport, scrollback_lines_to_serialize: options .scrollback_lines_to_serialize .map(|s| s as usize), styled_underlines: options.styled_underlines, serialization_interval: options.serialization_interval, disable_session_metadata: options.disable_session_metadata, support_kitty_keyboard_protocol: options.support_kitty_keyboard_protocol, support_kitty_graphics_protocol: options.support_kitty_graphics_protocol, web_server: options.web_server, web_sharing: options .web_sharing .map(|w| match ProtoWebSharing::from_i32(w) { Some(ProtoWebSharing::On) => Ok(crate::data::WebSharing::On), Some(ProtoWebSharing::Off) => Ok(crate::data::WebSharing::Off), Some(ProtoWebSharing::Disabled) => Ok(crate::data::WebSharing::Disabled), _ => Err(anyhow!("Invalid WebSharing value: {}", w)), }) .transpose()?, stacked_resize: options.stacked_resize, stacked_pane_list: options.stacked_pane_list, show_startup_tips: options.show_startup_tips, show_release_notes: options.show_release_notes, advanced_mouse_actions: options.advanced_mouse_actions, mouse_scroll_resize: options.mouse_scroll_resize, mouse_hover_effects: options.mouse_hover_effects, web_server_ip: options .web_server_ip .map(|ip| ip.parse()) .transpose() .map_err(|e| anyhow!("Invalid IP address: {}", e))?, web_server_port: options.web_server_port.map(|p| p as u16), web_server_cert: options.web_server_cert.map(std::path::PathBuf::from), web_server_key: options.web_server_key.map(std::path::PathBuf::from), enforce_https_for_localhost: options.enforce_https_for_localhost, post_command_discovery_hook: options.post_command_discovery_hook, client_async_worker_tasks: options.client_async_worker_tasks.map(|v| v as usize), visual_bell: options.visual_bell, focus_follows_mouse: options.focus_follows_mouse, mouse_click_through: options.mouse_click_through, mobile_layout: options .mobile_layout .map(|m| match ProtoMobileLayout::from_i32(m) { Some(ProtoMobileLayout::Web) => { Ok(crate::input::options::MobileLayoutConfiguration::Web) }, Some(ProtoMobileLayout::Always) => { Ok(crate::input::options::MobileLayoutConfiguration::Always) }, Some(ProtoMobileLayout::Never) => { Ok(crate::input::options::MobileLayoutConfiguration::Never) }, _ => Err(anyhow!("Invalid MobileLayout value: {}", m)), }) .transpose()?, mobile_threshold_cols: options.mobile_threshold_cols.map(|v| v as u16), mobile_threshold_rows: options.mobile_threshold_rows.map(|v| v as u16), nested_session_handling: options .nested_session_handling .map(|n| match ProtoNestedSessionHandling::from_i32(n) { Some(ProtoNestedSessionHandling::Ask) => { Ok(crate::input::options::NestedSessionHandling::Ask) }, Some(ProtoNestedSessionHandling::Fullscreen) => { Ok(crate::input::options::NestedSessionHandling::Fullscreen) }, Some(ProtoNestedSessionHandling::Descend) => { Ok(crate::input::options::NestedSessionHandling::Descend) }, Some(ProtoNestedSessionHandling::Never) => { Ok(crate::input::options::NestedSessionHandling::Never) }, _ => Err(anyhow!("Invalid NestedSessionHandling value: {}", n)), }) .transpose()?, }) } } // Complete Action conversion implementation - all 91 variants impl From for crate::client_server_contract::client_server_contract::Action { fn from(action: crate::input::actions::Action) -> Self { use crate::client_server_contract::client_server_contract::{ action::ActionType, AreFloatingPanesVisibleAction, BreakPaneAction, BreakPaneLeftAction, BreakPaneRightAction, ChangeFloatingPaneCoordinatesAction, ClearScreenAction, ClearScreenByPaneIdAction, CliPipeAction, CloseFocusAction, CloseFocusByPaneIdAction, ClosePluginPaneAction, CloseTabAction, CloseTabByIdAction, CloseTerminalPaneAction, ConfirmAction, CopyAction, CurrentTabInfoAction, DenyAction, DetachAction, DumpLayoutAction, DumpScreenAction, EditFileAction, EditScrollbackAction, EditScrollbackByPaneIdAction, FocusGuestSessionAction, FocusHostSessionAction, FocusLastPaneAction, FocusNextPaneAction, FocusPaneByPaneIdAction, FocusPluginPaneWithIdAction, FocusPreviousPaneAction, FocusTerminalPaneWithIdAction, GoToNextTabAction, GoToPreviousTabAction, GoToTabAction, GoToTabByIdAction, GoToTabNameAction, HalfPageScrollDownAction, HalfPageScrollDownByPaneIdAction, HalfPageScrollUpAction, HalfPageScrollUpByPaneIdAction, HideFloatingPanesAction, KeybindPipeAction, LaunchOrFocusPluginAction, LaunchPluginAction, ListClientsAction, ListPanesAction, ListTabsAction, MouseEventAction, MoveFocusAction, MoveFocusOrTabAction, MovePaneAction, MovePaneBackwardsAction, MovePaneBackwardsByPaneIdAction, MovePaneByPaneIdAction, MoveTabAction, MoveTabByTabIdAction, NewBlockingPaneAction, NewFloatingPaneAction, NewFloatingPluginPaneAction, NewInPlacePaneAction, NewInPlacePluginPaneAction, NewPaneAction, NewStackedPaneAction, NewTabAction, NewTiledPaneAction, NewTiledPluginPaneAction, NextSwapLayoutAction, NextSwapLayoutByTabIdAction, NoOpAction, OverrideLayoutAction, PageScrollDownAction, PageScrollDownByPaneIdAction, PageScrollUpAction, PageScrollUpByPaneIdAction, PaneIdWithPlugin, PaneNameInputAction, PasteAction, PreviousSwapLayoutAction, PreviousSwapLayoutByTabIdAction, QueryTabNamesAction, QuitAction, RenamePaneByPaneIdAction, RenamePluginPaneAction, RenameSessionAction, RenameTabAction, RenameTabByIdAction, RenameTerminalPaneAction, ResizeAction, ResizeByPaneIdAction, RunAction, SaveSessionAction, ScrollDownAction, ScrollDownAtAction, ScrollDownByPaneIdAction, ScrollToBottomAction, ScrollToBottomByPaneIdAction, ScrollToTopAction, ScrollToTopByPaneIdAction, ScrollUpAction, ScrollUpAtAction, // Pane-targeting ScrollUpByPaneIdAction, SearchAction, SearchInputAction, SearchToggleOptionAction, SetDarkThemeAction, SetLightThemeAction, SetPaneBorderlessAction, SetPaneColorAction, SetPaneFrameStyleAction, ShowFloatingPanesAction, SkipConfirmAction, StackPanesAction, StartOrReloadPluginAction, SwitchFocusAction, SwitchModeForAllClientsAction, SwitchSessionAction, SwitchToModeAction, TabNameInputAction, ToggleActiveSyncTabAction, ToggleActiveSyncTabByTabIdAction, ToggleFloatingPanesAction, ToggleFloatingPanesByTabIdAction, ToggleFocusFullscreenAction, ToggleFocusNoUiFullscreenAction, ToggleFullscreenByPaneIdAction, ToggleGroupMarkingAction, ToggleHostFullscreenAction, ToggleMouseModeAction, ToggleNoUiFullscreenByPaneIdAction, TogglePaneBorderlessAction, TogglePaneEmbedOrFloatingAction, TogglePaneEmbedOrFloatingByPaneIdAction, TogglePaneFramesAction, TogglePaneInGroupAction, TogglePanePinnedAction, TogglePanePinnedByPaneIdAction, ToggleTabAction, ToggleThemeAction, UndoRenamePaneAction, UndoRenamePaneByPaneIdAction, UndoRenameTabAction, // Tab-targeting UndoRenameTabByTabIdAction, WriteAction, WriteCharsAction, WriteCharsToPaneIdAction, WriteToPaneIdAction, }; use std::collections::HashMap; let action_type = match action { crate::input::actions::Action::Quit => ActionType::Quit(QuitAction {}), crate::input::actions::Action::Write { key_with_modifier, bytes, is_kitty_keyboard_protocol, } => ActionType::Write(WriteAction { key_with_modifier: key_with_modifier.map(|k| k.into()), bytes: bytes.into_iter().map(|b| b as u32).collect(), is_kitty_keyboard_protocol, }), crate::input::actions::Action::WriteChars { chars } => { ActionType::WriteChars(WriteCharsAction { chars }) }, crate::input::actions::Action::WriteToPaneId { bytes, pane_id } => { ActionType::WriteToPaneId(WriteToPaneIdAction { pane_id: Some(pane_id.into()), bytes: bytes.into_iter().map(|b| b as u32).collect(), }) }, crate::input::actions::Action::WriteCharsToPaneId { chars, pane_id } => { ActionType::WriteCharsToPaneId(WriteCharsToPaneIdAction { pane_id: Some(pane_id.into()), chars, }) }, crate::input::actions::Action::Paste { chars, pane_id } => { ActionType::Paste(PasteAction { chars, pane_id: pane_id.map(|p| p.into()), }) }, crate::input::actions::Action::SwitchToMode { input_mode } => { ActionType::SwitchToMode(SwitchToModeAction { input_mode: input_mode_to_proto_i32(input_mode), }) }, crate::input::actions::Action::SwitchModeForAllClients { input_mode } => { ActionType::SwitchModeForAllClients(SwitchModeForAllClientsAction { input_mode: input_mode_to_proto_i32(input_mode), }) }, crate::input::actions::Action::Resize { resize, direction } => { ActionType::Resize(ResizeAction { resize: resize_to_proto_i32(resize), direction: direction.map(|d| direction_to_proto_i32(d)), }) }, crate::input::actions::Action::FocusNextPane => { ActionType::FocusNextPane(FocusNextPaneAction {}) }, crate::input::actions::Action::FocusPreviousPane => { ActionType::FocusPreviousPane(FocusPreviousPaneAction {}) }, crate::input::actions::Action::FocusLastPane => { ActionType::FocusLastPane(FocusLastPaneAction {}) }, crate::input::actions::Action::FocusHostSession => { ActionType::FocusHostSession(FocusHostSessionAction {}) }, crate::input::actions::Action::FocusGuestSession => { ActionType::FocusGuestSession(FocusGuestSessionAction {}) }, crate::input::actions::Action::ToggleHostFullscreen => { ActionType::ToggleHostFullscreen(ToggleHostFullscreenAction {}) }, crate::input::actions::Action::SwitchFocus => { ActionType::SwitchFocus(SwitchFocusAction {}) }, crate::input::actions::Action::MoveFocus { direction } => { ActionType::MoveFocus(MoveFocusAction { direction: direction_to_proto_i32(direction), }) }, crate::input::actions::Action::MoveFocusOrTab { direction } => { ActionType::MoveFocusOrTab(MoveFocusOrTabAction { direction: direction_to_proto_i32(direction), }) }, crate::input::actions::Action::MovePane { direction } => { ActionType::MovePane(MovePaneAction { direction: direction.map(|d| direction_to_proto_i32(d)), }) }, crate::input::actions::Action::MovePaneBackwards => { ActionType::MovePaneBackwards(MovePaneBackwardsAction {}) }, crate::input::actions::Action::ClearScreen => { ActionType::ClearScreen(ClearScreenAction {}) }, crate::input::actions::Action::DumpScreen { file_path, include_scrollback, pane_id, ansi, } => { let dump_to_stdout = file_path.is_none(); ActionType::DumpScreen(DumpScreenAction { file_path: file_path.unwrap_or_default(), include_scrollback, pane_id: pane_id.map(|p| p.into()), dump_to_stdout, ansi, }) }, crate::input::actions::Action::DumpLayout => { ActionType::DumpLayout(DumpLayoutAction {}) }, crate::input::actions::Action::EditScrollback { ansi } => { ActionType::EditScrollback(EditScrollbackAction { ansi }) }, crate::input::actions::Action::ScrollUp => ActionType::ScrollUp(ScrollUpAction {}), crate::input::actions::Action::ScrollUpAt { position } => { ActionType::ScrollUpAt(ScrollUpAtAction { position: Some(position.into()), }) }, crate::input::actions::Action::ScrollDown => { ActionType::ScrollDown(ScrollDownAction {}) }, crate::input::actions::Action::ScrollDownAt { position } => { ActionType::ScrollDownAt(ScrollDownAtAction { position: Some(position.into()), }) }, crate::input::actions::Action::ScrollToBottom => { ActionType::ScrollToBottom(ScrollToBottomAction {}) }, crate::input::actions::Action::ScrollToTop => { ActionType::ScrollToTop(ScrollToTopAction {}) }, crate::input::actions::Action::PageScrollUp => { ActionType::PageScrollUp(PageScrollUpAction {}) }, crate::input::actions::Action::PageScrollDown => { ActionType::PageScrollDown(PageScrollDownAction {}) }, crate::input::actions::Action::HalfPageScrollUp => { ActionType::HalfPageScrollUp(HalfPageScrollUpAction {}) }, crate::input::actions::Action::HalfPageScrollDown => { ActionType::HalfPageScrollDown(HalfPageScrollDownAction {}) }, crate::input::actions::Action::ToggleFocusFullscreen => { ActionType::ToggleFocusFullscreen(ToggleFocusFullscreenAction {}) }, crate::input::actions::Action::ToggleFocusNoUiFullscreen => { ActionType::ToggleFocusNoUiFullscreen(ToggleFocusNoUiFullscreenAction {}) }, crate::input::actions::Action::TogglePaneFrames => { ActionType::TogglePaneFrames(TogglePaneFramesAction {}) }, crate::input::actions::Action::SetPaneFrameStyle(style) => { let style = match style { crate::input::options::PaneFrameStyle::Full => "full", crate::input::options::PaneFrameStyle::Titles => "titles", crate::input::options::PaneFrameStyle::None => "none", }; ActionType::SetPaneFrameStyle(SetPaneFrameStyleAction { style: style.to_owned(), }) }, crate::input::actions::Action::ToggleActiveSyncTab => { ActionType::ToggleActiveSyncTab(ToggleActiveSyncTabAction {}) }, crate::input::actions::Action::NewPane { direction, pane_name, start_suppressed, } => ActionType::NewPane(NewPaneAction { direction: direction.map(|d| direction_to_proto_i32(d)), pane_name, start_suppressed, near_current_pane: false, }), crate::input::actions::Action::EditFile { payload, direction, floating, in_place, close_replaced_pane, start_suppressed, coordinates, near_current_pane, no_focus, tab_id, .. } => ActionType::EditFile(EditFileAction { payload: Some(payload.into()), direction: direction.map(|d| direction_to_proto_i32(d)), floating, in_place, close_replaced_pane, start_suppressed, coordinates: coordinates.map(|c| c.into()), near_current_pane, tab_id: tab_id.map(|t| t as u32), no_focus, }), crate::input::actions::Action::NewFloatingPane { command, pane_name, coordinates, near_current_pane, no_focus, tab_id, .. } => ActionType::NewFloatingPane(NewFloatingPaneAction { command: command.map(|c| c.into()), pane_name, coordinates: coordinates.map(|c| c.into()), near_current_pane, tab_id: tab_id.map(|t| t as u32), no_focus, }), crate::input::actions::Action::NewTiledPane { direction, command, pane_name, near_current_pane, no_focus, borderless, tab_id, .. } => ActionType::NewTiledPane(NewTiledPaneAction { direction: direction.map(|d| direction_to_proto_i32(d)), command: command.map(|c| c.into()), pane_name, near_current_pane, borderless, tab_id: tab_id.map(|t| t as u32), no_focus, }), crate::input::actions::Action::NewInPlacePane { command, pane_name, near_current_pane, no_focus, pane_id_to_replace, close_replaced_pane, tab_id, .. } => ActionType::NewInPlacePane(NewInPlacePaneAction { command: command.map(|c| c.into()), pane_name, near_current_pane, pane_id_to_replace: pane_id_to_replace.and_then(|p| p.try_into().ok()), close_replaced_pane, tab_id: tab_id.map(|t| t as u32), no_focus, }), crate::input::actions::Action::NewStackedPane { command, pane_name, near_current_pane, no_focus, tab_id, .. } => ActionType::NewStackedPane(NewStackedPaneAction { command: command.map(|c| c.into()), pane_name, near_current_pane, tab_id: tab_id.map(|t| t as u32), no_focus, }), crate::input::actions::Action::NewBlockingPane { placement, pane_name, command, unblock_condition, near_current_pane, no_focus, tab_id, .. } => ActionType::NewBlockingPane(NewBlockingPaneAction { placement: Some(placement.into()), pane_name, command: command.map(|c| c.into()), unblock_condition: unblock_condition.map(|c| unblock_condition_to_proto_i32(c)), near_current_pane, tab_id: tab_id.map(|t| t as u32), no_focus, }), crate::input::actions::Action::TogglePaneEmbedOrFloating => { ActionType::TogglePaneEmbedOrFloating(TogglePaneEmbedOrFloatingAction {}) }, crate::input::actions::Action::ToggleFloatingPanes => { ActionType::ToggleFloatingPanes(ToggleFloatingPanesAction {}) }, crate::input::actions::Action::ShowFloatingPanes { tab_id } => { ActionType::ShowFloatingPanes(ShowFloatingPanesAction { tab_id: tab_id.map(|id| id as u32), }) }, crate::input::actions::Action::HideFloatingPanes { tab_id } => { ActionType::HideFloatingPanes(HideFloatingPanesAction { tab_id: tab_id.map(|id| id as u32), }) }, crate::input::actions::Action::AreFloatingPanesVisible { tab_id } => { ActionType::AreFloatingPanesVisible(AreFloatingPanesVisibleAction { tab_id: tab_id.map(|id| id as u32), }) }, crate::input::actions::Action::CloseFocus => { ActionType::CloseFocus(CloseFocusAction {}) }, crate::input::actions::Action::PaneNameInput { input } => { ActionType::PaneNameInput(PaneNameInputAction { input: input.into_iter().map(|b| b as u32).collect(), }) }, crate::input::actions::Action::UndoRenamePane => { ActionType::UndoRenamePane(UndoRenamePaneAction {}) }, crate::input::actions::Action::NewTab { tiled_layout, floating_layouts, swap_tiled_layouts, swap_floating_layouts, tab_name, should_change_focus_to_new_tab, cwd, initial_panes, first_pane_unblock_condition, } => ActionType::NewTab(NewTabAction { tiled_layout: tiled_layout.map(|l| l.into()), floating_layouts: floating_layouts.into_iter().map(|l| l.into()).collect(), swap_tiled_layouts: swap_tiled_layouts .map(|layouts| layouts.into_iter().map(|l| l.into()).collect()) .unwrap_or_default(), swap_floating_layouts: swap_floating_layouts .map(|layouts| layouts.into_iter().map(|l| l.into()).collect()) .unwrap_or_default(), tab_name, should_change_focus_to_new_tab, cwd: cwd.map(|p| p.to_string_lossy().to_string()), initial_panes: initial_panes .map(|panes| panes.into_iter().map(|p| p.into()).collect()) .unwrap_or_default(), first_pane_unblock_condition: first_pane_unblock_condition .map(|c| unblock_condition_to_proto_i32(c)), }), crate::input::actions::Action::NoOp => ActionType::NoOp(NoOpAction {}), crate::input::actions::Action::GoToNextTab => { ActionType::GoToNextTab(GoToNextTabAction {}) }, crate::input::actions::Action::GoToPreviousTab => { ActionType::GoToPreviousTab(GoToPreviousTabAction {}) }, crate::input::actions::Action::CloseTab => ActionType::CloseTab(CloseTabAction {}), crate::input::actions::Action::GoToTab { index } => { ActionType::GoToTab(GoToTabAction { index }) }, crate::input::actions::Action::GoToTabName { name, create } => { ActionType::GoToTabName(GoToTabNameAction { name, create }) }, crate::input::actions::Action::ToggleTab => ActionType::ToggleTab(ToggleTabAction {}), crate::input::actions::Action::TabNameInput { input } => { ActionType::TabNameInput(TabNameInputAction { input: input.into_iter().map(|b| b as u32).collect(), }) }, crate::input::actions::Action::UndoRenameTab => { ActionType::UndoRenameTab(UndoRenameTabAction {}) }, crate::input::actions::Action::MoveTab { direction } => { ActionType::MoveTab(MoveTabAction { direction: direction_to_proto_i32(direction), }) }, crate::input::actions::Action::Run { command, near_current_pane, no_focus, } => ActionType::Run(RunAction { command: Some(command.into()), near_current_pane, no_focus, }), crate::input::actions::Action::Detach => ActionType::Detach(DetachAction {}), crate::input::actions::Action::SetDarkTheme => { ActionType::SetDarkTheme(SetDarkThemeAction {}) }, crate::input::actions::Action::SetLightTheme => { ActionType::SetLightTheme(SetLightThemeAction {}) }, crate::input::actions::Action::ToggleTheme => { ActionType::ToggleTheme(ToggleThemeAction {}) }, crate::input::actions::Action::SwitchSession { name, tab_position, pane_id, layout, cwd, } => ActionType::SwitchSession(SwitchSessionAction { name: name.clone(), tab_position: tab_position.map(|p| p as u32), pane_id: pane_id.map(|(id, is_plugin)| PaneIdWithPlugin { pane_id: id, is_plugin: is_plugin, }), layout: layout.as_ref().map(|l| l.clone().into()), cwd: cwd.as_ref().map(|p| p.to_string_lossy().to_string()), }), crate::input::actions::Action::LaunchOrFocusPlugin { plugin, should_float, move_to_focused_tab, should_open_in_place, close_replaced_pane, skip_cache, tab_id, // close_if_focused is deliberately dropped here: it has no // protobuf representation, and is only meaningful for a keybinding .. } => ActionType::LaunchOrFocusPlugin(LaunchOrFocusPluginAction { plugin: Some(plugin.into()), should_float, move_to_focused_tab, should_open_in_place, close_replaced_pane, skip_cache, tab_id: tab_id.map(|t| t as u32), }), crate::input::actions::Action::LaunchPlugin { plugin, should_float, should_open_in_place, close_replaced_pane, skip_cache, cwd, no_focus, tab_id, .. } => ActionType::LaunchPlugin(LaunchPluginAction { plugin: Some(plugin.into()), should_float, should_open_in_place, close_replaced_pane, skip_cache, cwd: cwd.map(|p| p.to_string_lossy().to_string()), tab_id: tab_id.map(|t| t as u32), no_focus, }), crate::input::actions::Action::MouseEvent { event } => { ActionType::MouseEvent(MouseEventAction { event: Some(event.into()), }) }, crate::input::actions::Action::Copy => ActionType::Copy(CopyAction {}), crate::input::actions::Action::Confirm => ActionType::Confirm(ConfirmAction {}), crate::input::actions::Action::Deny => ActionType::Deny(DenyAction {}), crate::input::actions::Action::SkipConfirm { action } => { ActionType::SkipConfirm(Box::new(SkipConfirmAction { action: Some(Box::new((*action).into())), })) }, crate::input::actions::Action::SearchInput { input } => { ActionType::SearchInput(SearchInputAction { input: input.into_iter().map(|b| b as u32).collect(), }) }, crate::input::actions::Action::Search { direction } => { ActionType::Search(SearchAction { direction: search_direction_to_proto_i32(direction), }) }, crate::input::actions::Action::SearchToggleOption { option } => { ActionType::SearchToggleOption(SearchToggleOptionAction { option: search_option_to_proto_i32(option), }) }, crate::input::actions::Action::ToggleMouseMode => { ActionType::ToggleMouseMode(ToggleMouseModeAction {}) }, crate::input::actions::Action::ToggleMobileMode => ActionType::ToggleMobileMode( crate::client_server_contract::client_server_contract::ToggleMobileModeAction {}, ), crate::input::actions::Action::PreviousSwapLayout => { ActionType::PreviousSwapLayout(PreviousSwapLayoutAction {}) }, crate::input::actions::Action::NextSwapLayout => { ActionType::NextSwapLayout(NextSwapLayoutAction {}) }, crate::input::actions::Action::OverrideLayout { tabs, retain_existing_terminal_panes, retain_existing_plugin_panes, apply_only_to_active_tab, } => ActionType::OverrideLayout(OverrideLayoutAction { tabs: tabs.into_iter().map(|t| t.into()).collect(), retain_existing_terminal_panes, retain_existing_plugin_panes, apply_only_to_active_tab, }), crate::input::actions::Action::QueryTabNames => { ActionType::QueryTabNames(QueryTabNamesAction {}) }, crate::input::actions::Action::NewTiledPluginPane { plugin, pane_name, skip_cache, cwd, no_focus, tab_id, .. } => ActionType::NewTiledPluginPane(NewTiledPluginPaneAction { plugin: Some(plugin.into()), pane_name, skip_cache, cwd: cwd.map(|p| p.to_string_lossy().to_string()), tab_id: tab_id.map(|t| t as u32), no_focus, }), crate::input::actions::Action::NewFloatingPluginPane { plugin, pane_name, skip_cache, cwd, coordinates, no_focus, tab_id, .. } => ActionType::NewFloatingPluginPane(NewFloatingPluginPaneAction { plugin: Some(plugin.into()), pane_name, skip_cache, cwd: cwd.map(|p| p.to_string_lossy().to_string()), coordinates: coordinates.map(|c| c.into()), tab_id: tab_id.map(|t| t as u32), no_focus, }), crate::input::actions::Action::NewInPlacePluginPane { plugin, pane_name, skip_cache, close_replaced_pane, no_focus, tab_id, .. } => ActionType::NewInPlacePluginPane(NewInPlacePluginPaneAction { plugin: Some(plugin.into()), pane_name, skip_cache, close_replaced_pane, tab_id: tab_id.map(|t| t as u32), no_focus, }), crate::input::actions::Action::StartOrReloadPlugin { plugin } => { ActionType::StartOrReloadPlugin(StartOrReloadPluginAction { plugin: Some(plugin.into()), }) }, crate::input::actions::Action::CloseTerminalPane { pane_id } => { ActionType::CloseTerminalPane(CloseTerminalPaneAction { pane_id }) }, crate::input::actions::Action::ClosePluginPane { pane_id } => { ActionType::ClosePluginPane(ClosePluginPaneAction { pane_id }) }, crate::input::actions::Action::FocusTerminalPaneWithId { pane_id, should_float_if_hidden, should_be_in_place_if_hidden, } => ActionType::FocusTerminalPaneWithId(FocusTerminalPaneWithIdAction { pane_id, should_float_if_hidden, should_be_in_place_if_hidden, }), crate::input::actions::Action::FocusPluginPaneWithId { pane_id, should_float_if_hidden, should_be_in_place_if_hidden, } => ActionType::FocusPluginPaneWithId(FocusPluginPaneWithIdAction { pane_id, should_float_if_hidden, should_be_in_place_if_hidden, }), crate::input::actions::Action::RenameTerminalPane { pane_id, name } => { ActionType::RenameTerminalPane(RenameTerminalPaneAction { pane_id, name: name.into_iter().map(|b| b as u32).collect(), }) }, crate::input::actions::Action::RenamePluginPane { pane_id, name } => { ActionType::RenamePluginPane(RenamePluginPaneAction { pane_id, name: name.into_iter().map(|b| b as u32).collect(), }) }, crate::input::actions::Action::RenameTab { tab_index, name } => { ActionType::RenameTab(RenameTabAction { tab_index, name: name.into_iter().map(|b| b as u32).collect(), }) }, crate::input::actions::Action::GoToTabById { id } => { ActionType::GoToTabById(GoToTabByIdAction { id }) }, crate::input::actions::Action::CloseTabById { id } => { ActionType::CloseTabById(CloseTabByIdAction { id }) }, crate::input::actions::Action::RenameTabById { id, name } => { ActionType::RenameTabById(RenameTabByIdAction { id, name }) }, crate::input::actions::Action::BreakPane => ActionType::BreakPane(BreakPaneAction {}), crate::input::actions::Action::BreakPaneRight => { ActionType::BreakPaneRight(BreakPaneRightAction {}) }, crate::input::actions::Action::BreakPaneLeft => { ActionType::BreakPaneLeft(BreakPaneLeftAction {}) }, crate::input::actions::Action::RenameSession { name } => { ActionType::RenameSession(RenameSessionAction { name }) }, crate::input::actions::Action::CliPipe { pipe_id, name, payload, args, plugin, configuration, launch_new, skip_cache, floating, in_place, cwd, pane_title, } => ActionType::CliPipe(CliPipeAction { pipe_id, name, payload, args: args .map(|a| a.into_iter().collect::>()) .unwrap_or_default(), plugin, configuration: configuration .map(|c| c.into_iter().collect::>()) .unwrap_or_default(), launch_new, skip_cache, floating, in_place, cwd: cwd.map(|p| p.to_string_lossy().to_string()), pane_title, }), crate::input::actions::Action::KeybindPipe { name, payload, args, plugin, plugin_id, configuration, launch_new, skip_cache, floating, in_place, cwd, pane_title, } => ActionType::KeybindPipe(KeybindPipeAction { name, payload, args: args .map(|a| a.into_iter().collect::>()) .unwrap_or_default(), plugin, plugin_id, configuration: configuration .map(|c| c.into_iter().collect::>()) .unwrap_or_default(), launch_new, skip_cache, floating, in_place, cwd: cwd.map(|p| p.to_string_lossy().to_string()), pane_title, }), crate::input::actions::Action::ListClients => { ActionType::ListClients(ListClientsAction {}) }, crate::input::actions::Action::ListPanes { show_tab, show_command, show_state, show_geometry, show_all, output_json, } => ActionType::ListPanes(ListPanesAction { show_tab, show_command, show_state, show_geometry, show_all, output_json, }), crate::input::actions::Action::TogglePanePinned => { ActionType::TogglePanePinned(TogglePanePinnedAction {}) }, crate::input::actions::Action::StackPanes { pane_ids } => { ActionType::StackPanes(StackPanesAction { pane_ids: pane_ids.into_iter().map(|id| id.into()).collect(), }) }, crate::input::actions::Action::ChangeFloatingPaneCoordinates { pane_id, coordinates, } => ActionType::ChangeFloatingPaneCoordinates(ChangeFloatingPaneCoordinatesAction { pane_id: Some(pane_id.into()), coordinates: Some(coordinates.into()), }), crate::input::actions::Action::TogglePaneBorderless { pane_id } => { ActionType::TogglePaneBorderless(TogglePaneBorderlessAction { pane_id: Some(pane_id.into()), }) }, crate::input::actions::Action::SetPaneBorderless { pane_id, borderless, } => ActionType::SetPaneBorderless(SetPaneBorderlessAction { pane_id: Some(pane_id.into()), borderless, }), crate::input::actions::Action::TogglePaneInGroup => { ActionType::TogglePaneInGroup(TogglePaneInGroupAction {}) }, crate::input::actions::Action::ToggleGroupMarking => { ActionType::ToggleGroupMarking(ToggleGroupMarkingAction {}) }, crate::input::actions::Action::SaveSession => { ActionType::SaveSession(SaveSessionAction {}) }, crate::input::actions::Action::ListTabs { show_state, show_dimensions, show_panes, show_layout, show_all, output_json, } => ActionType::ListTabs(ListTabsAction { show_state, show_dimensions, show_panes, show_layout, show_all, output_json, }), crate::input::actions::Action::CurrentTabInfo { output_json } => { ActionType::CurrentTabInfo(CurrentTabInfoAction { output_json }) }, crate::input::actions::Action::SetPaneColor { pane_id, fg, bg } => { ActionType::SetPaneColor(SetPaneColorAction { pane_id: Some(pane_id.into()), fg, bg, }) }, // Pane-targeting CLI-only variants crate::input::actions::Action::ScrollUpByPaneId { pane_id } => { ActionType::ScrollUpByPaneId(ScrollUpByPaneIdAction { pane_id: Some(pane_id.into()), }) }, crate::input::actions::Action::ScrollDownByPaneId { pane_id } => { ActionType::ScrollDownByPaneId(ScrollDownByPaneIdAction { pane_id: Some(pane_id.into()), }) }, crate::input::actions::Action::ScrollToTopByPaneId { pane_id } => { ActionType::ScrollToTopByPaneId(ScrollToTopByPaneIdAction { pane_id: Some(pane_id.into()), }) }, crate::input::actions::Action::ScrollToBottomByPaneId { pane_id } => { ActionType::ScrollToBottomByPaneId(ScrollToBottomByPaneIdAction { pane_id: Some(pane_id.into()), }) }, crate::input::actions::Action::PageScrollUpByPaneId { pane_id } => { ActionType::PageScrollUpByPaneId(PageScrollUpByPaneIdAction { pane_id: Some(pane_id.into()), }) }, crate::input::actions::Action::PageScrollDownByPaneId { pane_id } => { ActionType::PageScrollDownByPaneId(PageScrollDownByPaneIdAction { pane_id: Some(pane_id.into()), }) }, crate::input::actions::Action::HalfPageScrollUpByPaneId { pane_id } => { ActionType::HalfPageScrollUpByPaneId(HalfPageScrollUpByPaneIdAction { pane_id: Some(pane_id.into()), }) }, crate::input::actions::Action::HalfPageScrollDownByPaneId { pane_id } => { ActionType::HalfPageScrollDownByPaneId(HalfPageScrollDownByPaneIdAction { pane_id: Some(pane_id.into()), }) }, crate::input::actions::Action::ResizeByPaneId { pane_id, resize, direction, } => ActionType::ResizeByPaneId(ResizeByPaneIdAction { pane_id: Some(pane_id.into()), resize_action: Some(ResizeAction { resize: resize_to_proto_i32(resize), direction: direction.map(|d| direction_to_proto_i32(d)), }), }), crate::input::actions::Action::MovePaneByPaneId { pane_id, direction } => { ActionType::MovePaneByPaneId(MovePaneByPaneIdAction { pane_id: Some(pane_id.into()), direction: direction.map(|d| direction_to_proto_i32(d)), }) }, crate::input::actions::Action::MovePaneBackwardsByPaneId { pane_id } => { ActionType::MovePaneBackwardsByPaneId(MovePaneBackwardsByPaneIdAction { pane_id: Some(pane_id.into()), }) }, crate::input::actions::Action::ClearScreenByPaneId { pane_id } => { ActionType::ClearScreenByPaneId(ClearScreenByPaneIdAction { pane_id: Some(pane_id.into()), }) }, crate::input::actions::Action::EditScrollbackByPaneId { pane_id, ansi } => { ActionType::EditScrollbackByPaneId(EditScrollbackByPaneIdAction { pane_id: Some(pane_id.into()), ansi, }) }, crate::input::actions::Action::ToggleFocusFullscreenByPaneId { pane_id } => { ActionType::ToggleFullscreenByPaneId(ToggleFullscreenByPaneIdAction { pane_id: Some(pane_id.into()), }) }, crate::input::actions::Action::ToggleFocusNoUiFullscreenByPaneId { pane_id } => { ActionType::ToggleNoUiFullscreenByPaneId(ToggleNoUiFullscreenByPaneIdAction { pane_id: Some(pane_id.into()), }) }, crate::input::actions::Action::TogglePaneEmbedOrFloatingByPaneId { pane_id } => { ActionType::TogglePaneEmbedOrFloatingByPaneId( TogglePaneEmbedOrFloatingByPaneIdAction { pane_id: Some(pane_id.into()), }, ) }, crate::input::actions::Action::CloseFocusByPaneId { pane_id } => { ActionType::CloseFocusByPaneId(CloseFocusByPaneIdAction { pane_id: Some(pane_id.into()), }) }, crate::input::actions::Action::RenamePaneByPaneId { pane_id, name } => { ActionType::RenamePaneByPaneId(RenamePaneByPaneIdAction { pane_id: pane_id.map(|id| id.into()), name, }) }, crate::input::actions::Action::UndoRenamePaneByPaneId { pane_id } => { ActionType::UndoRenamePaneByPaneId(UndoRenamePaneByPaneIdAction { pane_id: Some(pane_id.into()), }) }, crate::input::actions::Action::TogglePanePinnedByPaneId { pane_id } => { ActionType::TogglePanePinnedByPaneId(TogglePanePinnedByPaneIdAction { pane_id: Some(pane_id.into()), }) }, crate::input::actions::Action::FocusPaneByPaneId { pane_id } => { ActionType::FocusPaneByPaneId(FocusPaneByPaneIdAction { pane_id: Some(pane_id.into()), }) }, // Tab-targeting CLI-only variants crate::input::actions::Action::UndoRenameTabByTabId { id } => { ActionType::UndoRenameTabByTabId(UndoRenameTabByTabIdAction { id }) }, crate::input::actions::Action::ToggleActiveSyncTabByTabId { id } => { ActionType::ToggleActiveSyncTabByTabId(ToggleActiveSyncTabByTabIdAction { id }) }, crate::input::actions::Action::ToggleFloatingPanesByTabId { id } => { ActionType::ToggleFloatingPanesByTabId(ToggleFloatingPanesByTabIdAction { id }) }, crate::input::actions::Action::PreviousSwapLayoutByTabId { id } => { ActionType::PreviousSwapLayoutByTabId(PreviousSwapLayoutByTabIdAction { id }) }, crate::input::actions::Action::NextSwapLayoutByTabId { id } => { ActionType::NextSwapLayoutByTabId(NextSwapLayoutByTabIdAction { id }) }, crate::input::actions::Action::MoveTabByTabId { id, direction } => { ActionType::MoveTabByTabId(MoveTabByTabIdAction { id, direction: direction_to_proto_i32(direction), }) }, }; Self { action_type: Some(action_type), } } } impl TryFrom for crate::input::actions::Action { type Error = anyhow::Error; fn try_from( action: crate::client_server_contract::client_server_contract::Action, ) -> Result { use crate::client_server_contract::client_server_contract::action::ActionType; let action_type = action .action_type .ok_or_else(|| anyhow!("Action missing action_type"))?; match action_type { ActionType::Quit(_) => Ok(crate::input::actions::Action::Quit), ActionType::Write(write_action) => Ok(crate::input::actions::Action::Write { key_with_modifier: write_action .key_with_modifier .map(|k| k.try_into()) .transpose()?, bytes: write_action.bytes.into_iter().map(|b| b as u8).collect(), is_kitty_keyboard_protocol: write_action.is_kitty_keyboard_protocol, }), ActionType::WriteChars(write_chars_action) => { Ok(crate::input::actions::Action::WriteChars { chars: write_chars_action.chars, }) }, ActionType::WriteToPaneId(write_to_pane_id_action) => { Ok(crate::input::actions::Action::WriteToPaneId { bytes: write_to_pane_id_action .bytes .into_iter() .map(|b| b as u8) .collect(), pane_id: write_to_pane_id_action .pane_id .ok_or_else(|| anyhow!("WriteToPaneId missing pane_id"))? .try_into()?, }) }, ActionType::WriteCharsToPaneId(write_chars_to_pane_id_action) => { Ok(crate::input::actions::Action::WriteCharsToPaneId { chars: write_chars_to_pane_id_action.chars, pane_id: write_chars_to_pane_id_action .pane_id .ok_or_else(|| anyhow!("WriteCharsToPaneId missing pane_id"))? .try_into()?, }) }, ActionType::Paste(paste_action) => Ok(crate::input::actions::Action::Paste { chars: paste_action.chars, pane_id: paste_action.pane_id.map(|p| p.try_into()).transpose()?, }), ActionType::SwitchToMode(switch_mode_action) => { Ok(crate::input::actions::Action::SwitchToMode { input_mode: proto_i32_to_input_mode(switch_mode_action.input_mode)?, }) }, ActionType::SwitchModeForAllClients(switch_mode_action) => { Ok(crate::input::actions::Action::SwitchModeForAllClients { input_mode: proto_i32_to_input_mode(switch_mode_action.input_mode)?, }) }, ActionType::Resize(resize_action) => Ok(crate::input::actions::Action::Resize { resize: proto_i32_to_resize(resize_action.resize)?, direction: resize_action .direction .map(|d| proto_i32_to_direction(d)) .transpose()?, }), ActionType::FocusNextPane(_) => Ok(crate::input::actions::Action::FocusNextPane), ActionType::FocusPreviousPane(_) => { Ok(crate::input::actions::Action::FocusPreviousPane) }, ActionType::FocusLastPane(_) => Ok(crate::input::actions::Action::FocusLastPane), ActionType::SwitchFocus(_) => Ok(crate::input::actions::Action::SwitchFocus), ActionType::MoveFocus(move_focus_action) => { Ok(crate::input::actions::Action::MoveFocus { direction: proto_i32_to_direction(move_focus_action.direction)?, }) }, ActionType::MoveFocusOrTab(move_focus_action) => { Ok(crate::input::actions::Action::MoveFocusOrTab { direction: proto_i32_to_direction(move_focus_action.direction)?, }) }, ActionType::MovePane(move_pane_action) => Ok(crate::input::actions::Action::MovePane { direction: move_pane_action .direction .map(|d| proto_i32_to_direction(d)) .transpose()?, }), ActionType::MovePaneBackwards(_) => { Ok(crate::input::actions::Action::MovePaneBackwards) }, ActionType::ClearScreen(_) => Ok(crate::input::actions::Action::ClearScreen), ActionType::DumpScreen(dump_screen_action) => { let file_path = if dump_screen_action.dump_to_stdout { None } else { Some(dump_screen_action.file_path) }; Ok(crate::input::actions::Action::DumpScreen { file_path, include_scrollback: dump_screen_action.include_scrollback, pane_id: dump_screen_action.pane_id.and_then(|p| p.try_into().ok()), ansi: dump_screen_action.ansi, }) }, ActionType::DumpLayout(_) => Ok(crate::input::actions::Action::DumpLayout), ActionType::SaveSession(_) => Ok(crate::input::actions::Action::SaveSession), ActionType::EditScrollback(edit_scrollback_action) => { Ok(crate::input::actions::Action::EditScrollback { ansi: edit_scrollback_action.ansi, }) }, ActionType::ScrollUp(_) => Ok(crate::input::actions::Action::ScrollUp), ActionType::ScrollUpAt(scroll_action) => { Ok(crate::input::actions::Action::ScrollUpAt { position: scroll_action .position .ok_or_else(|| anyhow!("ScrollUpAt missing position"))? .try_into()?, }) }, ActionType::ScrollDown(_) => Ok(crate::input::actions::Action::ScrollDown), ActionType::ScrollDownAt(scroll_action) => { Ok(crate::input::actions::Action::ScrollDownAt { position: scroll_action .position .ok_or_else(|| anyhow!("ScrollDownAt missing position"))? .try_into()?, }) }, ActionType::ScrollToBottom(_) => Ok(crate::input::actions::Action::ScrollToBottom), ActionType::ScrollToTop(_) => Ok(crate::input::actions::Action::ScrollToTop), ActionType::PageScrollUp(_) => Ok(crate::input::actions::Action::PageScrollUp), ActionType::PageScrollDown(_) => Ok(crate::input::actions::Action::PageScrollDown), ActionType::HalfPageScrollUp(_) => Ok(crate::input::actions::Action::HalfPageScrollUp), ActionType::HalfPageScrollDown(_) => { Ok(crate::input::actions::Action::HalfPageScrollDown) }, ActionType::ToggleFocusFullscreen(_) => { Ok(crate::input::actions::Action::ToggleFocusFullscreen) }, ActionType::ToggleFocusNoUiFullscreen(_) => { Ok(crate::input::actions::Action::ToggleFocusNoUiFullscreen) }, ActionType::TogglePaneFrames(_) => Ok(crate::input::actions::Action::TogglePaneFrames), ActionType::SetPaneFrameStyle(set_pane_frame_style_action) => { let style = crate::input::options::PaneFrameStyle::from_str( &set_pane_frame_style_action.style, ) .map_err(|e| anyhow!("{}", e))?; Ok(crate::input::actions::Action::SetPaneFrameStyle(style)) }, ActionType::ToggleActiveSyncTab(_) => { Ok(crate::input::actions::Action::ToggleActiveSyncTab) }, ActionType::NewPane(new_pane_action) => Ok(crate::input::actions::Action::NewPane { direction: new_pane_action .direction .map(|d| proto_i32_to_direction(d)) .transpose()?, pane_name: new_pane_action.pane_name, start_suppressed: new_pane_action.start_suppressed, }), ActionType::EditFile(edit_file_action) => Ok(crate::input::actions::Action::EditFile { payload: edit_file_action .payload .ok_or_else(|| anyhow!("EditFile missing payload"))? .try_into()?, direction: edit_file_action .direction .map(|d| proto_i32_to_direction(d)) .transpose()?, floating: edit_file_action.floating, in_place: edit_file_action.in_place, close_replaced_pane: edit_file_action.close_replaced_pane, start_suppressed: edit_file_action.start_suppressed, coordinates: edit_file_action .coordinates .map(|c| c.try_into()) .transpose()?, near_current_pane: edit_file_action.near_current_pane, no_focus: edit_file_action.no_focus, tab_id: edit_file_action.tab_id.map(|t| t as usize), }), ActionType::NewFloatingPane(new_floating_action) => { Ok(crate::input::actions::Action::NewFloatingPane { command: new_floating_action .command .map(|c| c.try_into()) .transpose()?, pane_name: new_floating_action.pane_name, coordinates: new_floating_action .coordinates .map(|c| c.try_into()) .transpose()?, near_current_pane: new_floating_action.near_current_pane, no_focus: new_floating_action.no_focus, tab_id: new_floating_action.tab_id.map(|t| t as usize), }) }, ActionType::NewTiledPane(new_tiled_action) => { Ok(crate::input::actions::Action::NewTiledPane { direction: new_tiled_action .direction .map(|d| proto_i32_to_direction(d)) .transpose()?, command: new_tiled_action.command.map(|c| c.try_into()).transpose()?, pane_name: new_tiled_action.pane_name, near_current_pane: new_tiled_action.near_current_pane, no_focus: new_tiled_action.no_focus, borderless: new_tiled_action.borderless, tab_id: new_tiled_action.tab_id.map(|t| t as usize), }) }, ActionType::NewInPlacePane(new_in_place_action) => { Ok(crate::input::actions::Action::NewInPlacePane { command: new_in_place_action .command .map(|c| c.try_into()) .transpose()?, pane_name: new_in_place_action.pane_name, near_current_pane: new_in_place_action.near_current_pane, no_focus: new_in_place_action.no_focus, pane_id_to_replace: new_in_place_action .pane_id_to_replace .and_then(|p| p.try_into().ok()), close_replaced_pane: new_in_place_action.close_replaced_pane, tab_id: new_in_place_action.tab_id.map(|t| t as usize), }) }, ActionType::NewStackedPane(new_stacked_action) => { Ok(crate::input::actions::Action::NewStackedPane { command: new_stacked_action .command .map(|c| c.try_into()) .transpose()?, pane_name: new_stacked_action.pane_name, near_current_pane: new_stacked_action.near_current_pane, no_focus: new_stacked_action.no_focus, tab_id: new_stacked_action.tab_id.map(|t| t as usize), }) }, ActionType::NewBlockingPane(new_blocking_action) => { Ok(crate::input::actions::Action::NewBlockingPane { placement: new_blocking_action .placement .ok_or_else(|| anyhow!("NewBlockingPane missing placement"))? .try_into()?, pane_name: new_blocking_action.pane_name, command: new_blocking_action .command .map(|c| c.try_into()) .transpose()?, unblock_condition: new_blocking_action .unblock_condition .map(|c| proto_i32_to_unblock_condition(c)) .transpose()?, near_current_pane: new_blocking_action.near_current_pane, no_focus: new_blocking_action.no_focus, tab_id: new_blocking_action.tab_id.map(|t| t as usize), }) }, ActionType::TogglePaneEmbedOrFloating(_) => { Ok(crate::input::actions::Action::TogglePaneEmbedOrFloating) }, ActionType::ToggleFloatingPanes(_) => { Ok(crate::input::actions::Action::ToggleFloatingPanes) }, ActionType::ShowFloatingPanes(a) => { Ok(crate::input::actions::Action::ShowFloatingPanes { tab_id: a.tab_id.map(|id| id as usize), }) }, ActionType::HideFloatingPanes(a) => { Ok(crate::input::actions::Action::HideFloatingPanes { tab_id: a.tab_id.map(|id| id as usize), }) }, ActionType::AreFloatingPanesVisible(a) => { Ok(crate::input::actions::Action::AreFloatingPanesVisible { tab_id: a.tab_id.map(|id| id as usize), }) }, ActionType::CloseFocus(_) => Ok(crate::input::actions::Action::CloseFocus), ActionType::PaneNameInput(pane_name_action) => { Ok(crate::input::actions::Action::PaneNameInput { input: pane_name_action .input .into_iter() .map(|b| b as u8) .collect(), }) }, ActionType::UndoRenamePane(_) => Ok(crate::input::actions::Action::UndoRenamePane), ActionType::NewTab(new_tab_action) => Ok(crate::input::actions::Action::NewTab { tiled_layout: new_tab_action .tiled_layout .map(|l| l.try_into()) .transpose()?, floating_layouts: new_tab_action .floating_layouts .into_iter() .map(|l| l.try_into()) .collect::>>()?, swap_tiled_layouts: if new_tab_action.swap_tiled_layouts.is_empty() { None } else { Some( new_tab_action .swap_tiled_layouts .into_iter() .map(|l| l.try_into()) .collect::>>()?, ) }, swap_floating_layouts: if new_tab_action.swap_floating_layouts.is_empty() { None } else { Some( new_tab_action .swap_floating_layouts .into_iter() .map(|l| l.try_into()) .collect::>>()?, ) }, tab_name: new_tab_action.tab_name, should_change_focus_to_new_tab: new_tab_action.should_change_focus_to_new_tab, cwd: new_tab_action.cwd.map(PathBuf::from), initial_panes: if new_tab_action.initial_panes.is_empty() { None } else { Some( new_tab_action .initial_panes .into_iter() .map(|p| p.try_into()) .collect::>>()?, ) }, first_pane_unblock_condition: new_tab_action .first_pane_unblock_condition .map(|c| proto_i32_to_unblock_condition(c)) .transpose()?, }), ActionType::NoOp(_) => Ok(crate::input::actions::Action::NoOp), ActionType::GoToNextTab(_) => Ok(crate::input::actions::Action::GoToNextTab), ActionType::GoToPreviousTab(_) => Ok(crate::input::actions::Action::GoToPreviousTab), ActionType::CloseTab(_) => Ok(crate::input::actions::Action::CloseTab), ActionType::GoToTab(go_to_tab_action) => Ok(crate::input::actions::Action::GoToTab { index: go_to_tab_action.index, }), ActionType::GoToTabName(go_to_tab_name_action) => { Ok(crate::input::actions::Action::GoToTabName { name: go_to_tab_name_action.name, create: go_to_tab_name_action.create, }) }, ActionType::ToggleTab(_) => Ok(crate::input::actions::Action::ToggleTab), ActionType::TabNameInput(tab_name_action) => { Ok(crate::input::actions::Action::TabNameInput { input: tab_name_action.input.into_iter().map(|b| b as u8).collect(), }) }, ActionType::UndoRenameTab(_) => Ok(crate::input::actions::Action::UndoRenameTab), ActionType::MoveTab(move_tab_action) => Ok(crate::input::actions::Action::MoveTab { direction: proto_i32_to_direction(move_tab_action.direction)?, }), ActionType::Run(run_action) => Ok(crate::input::actions::Action::Run { command: run_action .command .ok_or_else(|| anyhow!("Run missing command"))? .try_into()?, near_current_pane: run_action.near_current_pane, no_focus: run_action.no_focus, }), ActionType::Detach(_) => Ok(crate::input::actions::Action::Detach), ActionType::SetDarkTheme(_) => Ok(crate::input::actions::Action::SetDarkTheme), ActionType::SetLightTheme(_) => Ok(crate::input::actions::Action::SetLightTheme), ActionType::ToggleTheme(_) => Ok(crate::input::actions::Action::ToggleTheme), ActionType::SwitchSession(switch_session_action) => { Ok(crate::input::actions::Action::SwitchSession { name: switch_session_action.name.clone(), tab_position: switch_session_action.tab_position.map(|p| p as usize), pane_id: switch_session_action .pane_id .as_ref() .map(|p| (p.pane_id, p.is_plugin)), layout: switch_session_action .layout .map(|l| l.try_into()) .transpose()?, cwd: switch_session_action.cwd.map(PathBuf::from), }) }, ActionType::LaunchOrFocusPlugin(launch_plugin_action) => { Ok(crate::input::actions::Action::LaunchOrFocusPlugin { plugin: launch_plugin_action .plugin .ok_or_else(|| anyhow!("LaunchOrFocusPlugin missing plugin"))? .try_into()?, should_float: launch_plugin_action.should_float, move_to_focused_tab: launch_plugin_action.move_to_focused_tab, should_open_in_place: launch_plugin_action.should_open_in_place, close_replaced_pane: launch_plugin_action.close_replaced_pane, skip_cache: launch_plugin_action.skip_cache, tab_id: launch_plugin_action.tab_id.map(|t| t as usize), // no protobuf representation; a config-only option close_if_focused: false, }) }, ActionType::LaunchPlugin(launch_plugin_action) => { Ok(crate::input::actions::Action::LaunchPlugin { plugin: launch_plugin_action .plugin .ok_or_else(|| anyhow!("LaunchPlugin missing plugin"))? .try_into()?, should_float: launch_plugin_action.should_float, should_open_in_place: launch_plugin_action.should_open_in_place, close_replaced_pane: launch_plugin_action.close_replaced_pane, skip_cache: launch_plugin_action.skip_cache, cwd: launch_plugin_action.cwd.map(PathBuf::from), no_focus: launch_plugin_action.no_focus, tab_id: launch_plugin_action.tab_id.map(|t| t as usize), }) }, ActionType::MouseEvent(mouse_event_action) => { Ok(crate::input::actions::Action::MouseEvent { event: mouse_event_action .event .ok_or_else(|| anyhow!("MouseEvent missing event"))? .try_into()?, }) }, ActionType::Copy(_) => Ok(crate::input::actions::Action::Copy), ActionType::Confirm(_) => Ok(crate::input::actions::Action::Confirm), ActionType::Deny(_) => Ok(crate::input::actions::Action::Deny), ActionType::SkipConfirm(skip_confirm_action) => { Ok(crate::input::actions::Action::SkipConfirm { action: Box::new( skip_confirm_action .action .ok_or_else(|| anyhow!("SkipConfirm missing action"))? .as_ref() .clone() .try_into()?, ), }) }, ActionType::SearchInput(search_input_action) => { Ok(crate::input::actions::Action::SearchInput { input: search_input_action .input .into_iter() .map(|b| b as u8) .collect(), }) }, ActionType::Search(search_action) => Ok(crate::input::actions::Action::Search { direction: proto_i32_to_search_direction(search_action.direction)?, }), ActionType::SearchToggleOption(search_toggle_action) => { Ok(crate::input::actions::Action::SearchToggleOption { option: proto_i32_to_search_option(search_toggle_action.option)?, }) }, ActionType::ToggleMouseMode(_) => Ok(crate::input::actions::Action::ToggleMouseMode), ActionType::ToggleMobileMode(_) => Ok(crate::input::actions::Action::ToggleMobileMode), ActionType::PreviousSwapLayout(_) => { Ok(crate::input::actions::Action::PreviousSwapLayout) }, ActionType::NextSwapLayout(_) => Ok(crate::input::actions::Action::NextSwapLayout), ActionType::OverrideLayout(override_layout_action) => { Ok(crate::input::actions::Action::OverrideLayout { tabs: override_layout_action .tabs .into_iter() .map(|t| t.try_into()) .collect::>>()?, retain_existing_terminal_panes: override_layout_action .retain_existing_terminal_panes, retain_existing_plugin_panes: override_layout_action .retain_existing_plugin_panes, apply_only_to_active_tab: override_layout_action.apply_only_to_active_tab, }) }, ActionType::QueryTabNames(_) => Ok(crate::input::actions::Action::QueryTabNames), ActionType::NewTiledPluginPane(new_tiled_plugin_action) => { Ok(crate::input::actions::Action::NewTiledPluginPane { plugin: new_tiled_plugin_action .plugin .ok_or_else(|| anyhow!("NewTiledPluginPane missing plugin"))? .try_into()?, pane_name: new_tiled_plugin_action.pane_name, skip_cache: new_tiled_plugin_action.skip_cache, cwd: new_tiled_plugin_action.cwd.map(PathBuf::from), no_focus: new_tiled_plugin_action.no_focus, tab_id: new_tiled_plugin_action.tab_id.map(|t| t as usize), }) }, ActionType::NewFloatingPluginPane(new_floating_plugin_action) => { Ok(crate::input::actions::Action::NewFloatingPluginPane { plugin: new_floating_plugin_action .plugin .ok_or_else(|| anyhow!("NewFloatingPluginPane missing plugin"))? .try_into()?, pane_name: new_floating_plugin_action.pane_name, skip_cache: new_floating_plugin_action.skip_cache, cwd: new_floating_plugin_action.cwd.map(PathBuf::from), coordinates: new_floating_plugin_action .coordinates .map(|c| c.try_into()) .transpose()?, no_focus: new_floating_plugin_action.no_focus, tab_id: new_floating_plugin_action.tab_id.map(|t| t as usize), }) }, ActionType::NewInPlacePluginPane(new_in_place_plugin_action) => { Ok(crate::input::actions::Action::NewInPlacePluginPane { plugin: new_in_place_plugin_action .plugin .ok_or_else(|| anyhow!("NewInPlacePluginPane missing plugin"))? .try_into()?, pane_name: new_in_place_plugin_action.pane_name, skip_cache: new_in_place_plugin_action.skip_cache, close_replaced_pane: new_in_place_plugin_action.close_replaced_pane, no_focus: new_in_place_plugin_action.no_focus, tab_id: new_in_place_plugin_action.tab_id.map(|t| t as usize), }) }, ActionType::StartOrReloadPlugin(start_plugin_action) => { Ok(crate::input::actions::Action::StartOrReloadPlugin { plugin: start_plugin_action .plugin .ok_or_else(|| anyhow!("StartOrReloadPlugin missing plugin"))? .try_into()?, }) }, ActionType::CloseTerminalPane(close_pane_action) => { Ok(crate::input::actions::Action::CloseTerminalPane { pane_id: close_pane_action.pane_id, }) }, ActionType::ClosePluginPane(close_pane_action) => { Ok(crate::input::actions::Action::ClosePluginPane { pane_id: close_pane_action.pane_id, }) }, ActionType::FocusTerminalPaneWithId(focus_pane_action) => { Ok(crate::input::actions::Action::FocusTerminalPaneWithId { pane_id: focus_pane_action.pane_id, should_float_if_hidden: focus_pane_action.should_float_if_hidden, should_be_in_place_if_hidden: focus_pane_action.should_be_in_place_if_hidden, }) }, ActionType::FocusPluginPaneWithId(focus_pane_action) => { Ok(crate::input::actions::Action::FocusPluginPaneWithId { pane_id: focus_pane_action.pane_id, should_float_if_hidden: focus_pane_action.should_float_if_hidden, should_be_in_place_if_hidden: focus_pane_action.should_be_in_place_if_hidden, }) }, ActionType::RenameTerminalPane(rename_pane_action) => { Ok(crate::input::actions::Action::RenameTerminalPane { pane_id: rename_pane_action.pane_id, name: rename_pane_action .name .into_iter() .map(|b| b as u8) .collect(), }) }, ActionType::RenamePluginPane(rename_pane_action) => { Ok(crate::input::actions::Action::RenamePluginPane { pane_id: rename_pane_action.pane_id, name: rename_pane_action .name .into_iter() .map(|b| b as u8) .collect(), }) }, ActionType::RenameTab(rename_tab_action) => { Ok(crate::input::actions::Action::RenameTab { tab_index: rename_tab_action.tab_index, name: rename_tab_action .name .into_iter() .map(|b| b as u8) .collect(), }) }, ActionType::GoToTabById(go_to_tab_by_id_action) => { Ok(crate::input::actions::Action::GoToTabById { id: go_to_tab_by_id_action.id, }) }, ActionType::CloseTabById(close_tab_by_id_action) => { Ok(crate::input::actions::Action::CloseTabById { id: close_tab_by_id_action.id, }) }, ActionType::RenameTabById(rename_tab_by_id_action) => { Ok(crate::input::actions::Action::RenameTabById { id: rename_tab_by_id_action.id, name: rename_tab_by_id_action.name, }) }, ActionType::BreakPane(_) => Ok(crate::input::actions::Action::BreakPane), ActionType::BreakPaneRight(_) => Ok(crate::input::actions::Action::BreakPaneRight), ActionType::BreakPaneLeft(_) => Ok(crate::input::actions::Action::BreakPaneLeft), ActionType::FocusHostSession(_) => Ok(crate::input::actions::Action::FocusHostSession), ActionType::FocusGuestSession(_) => { Ok(crate::input::actions::Action::FocusGuestSession) }, ActionType::ToggleHostFullscreen(_) => { Ok(crate::input::actions::Action::ToggleHostFullscreen) }, ActionType::RenameSession(rename_session_action) => { Ok(crate::input::actions::Action::RenameSession { name: rename_session_action.name, }) }, ActionType::CliPipe(cli_pipe_action) => Ok(crate::input::actions::Action::CliPipe { pipe_id: cli_pipe_action.pipe_id, name: cli_pipe_action.name, payload: cli_pipe_action.payload, args: if cli_pipe_action.args.is_empty() { None } else { Some(cli_pipe_action.args.into_iter().collect()) }, plugin: cli_pipe_action.plugin, configuration: if cli_pipe_action.configuration.is_empty() { None } else { Some(cli_pipe_action.configuration.into_iter().collect()) }, launch_new: cli_pipe_action.launch_new, skip_cache: cli_pipe_action.skip_cache, floating: cli_pipe_action.floating, in_place: cli_pipe_action.in_place, cwd: cli_pipe_action.cwd.map(PathBuf::from), pane_title: cli_pipe_action.pane_title, }), ActionType::KeybindPipe(keybind_pipe_action) => { Ok(crate::input::actions::Action::KeybindPipe { name: keybind_pipe_action.name, payload: keybind_pipe_action.payload, args: if keybind_pipe_action.args.is_empty() { None } else { Some(keybind_pipe_action.args.into_iter().collect()) }, plugin: keybind_pipe_action.plugin, plugin_id: keybind_pipe_action.plugin_id, configuration: if keybind_pipe_action.configuration.is_empty() { None } else { Some(keybind_pipe_action.configuration.into_iter().collect()) }, launch_new: keybind_pipe_action.launch_new, skip_cache: keybind_pipe_action.skip_cache, floating: keybind_pipe_action.floating, in_place: keybind_pipe_action.in_place, cwd: keybind_pipe_action.cwd.map(PathBuf::from), pane_title: keybind_pipe_action.pane_title, }) }, ActionType::ListClients(_) => Ok(crate::input::actions::Action::ListClients), ActionType::ListPanes(list_panes_action) => { Ok(crate::input::actions::Action::ListPanes { show_tab: list_panes_action.show_tab, show_command: list_panes_action.show_command, show_state: list_panes_action.show_state, show_geometry: list_panes_action.show_geometry, show_all: list_panes_action.show_all, output_json: list_panes_action.output_json, }) }, ActionType::ListTabs(list_tabs_action) => Ok(crate::input::actions::Action::ListTabs { show_state: list_tabs_action.show_state, show_dimensions: list_tabs_action.show_dimensions, show_panes: list_tabs_action.show_panes, show_layout: list_tabs_action.show_layout, show_all: list_tabs_action.show_all, output_json: list_tabs_action.output_json, }), ActionType::CurrentTabInfo(current_tab_info_action) => { Ok(crate::input::actions::Action::CurrentTabInfo { output_json: current_tab_info_action.output_json, }) }, ActionType::TogglePanePinned(_) => Ok(crate::input::actions::Action::TogglePanePinned), ActionType::StackPanes(stack_panes_action) => { Ok(crate::input::actions::Action::StackPanes { pane_ids: stack_panes_action .pane_ids .into_iter() .map(|id| id.try_into()) .collect::>>()?, }) }, ActionType::ChangeFloatingPaneCoordinates(change_coords_action) => Ok( crate::input::actions::Action::ChangeFloatingPaneCoordinates { pane_id: change_coords_action .pane_id .ok_or_else(|| anyhow!("ChangeFloatingPaneCoordinates missing pane_id"))? .try_into()?, coordinates: change_coords_action .coordinates .ok_or_else(|| { anyhow!("ChangeFloatingPaneCoordinates missing coordinates") })? .try_into()?, }, ), ActionType::TogglePaneBorderless(toggle_borderless_action) => { Ok(crate::input::actions::Action::TogglePaneBorderless { pane_id: toggle_borderless_action .pane_id .ok_or_else(|| anyhow!("TogglePaneBorderless missing pane_id"))? .try_into()?, }) }, ActionType::SetPaneBorderless(set_borderless_action) => { Ok(crate::input::actions::Action::SetPaneBorderless { pane_id: set_borderless_action .pane_id .ok_or_else(|| anyhow!("SetPaneBorderless missing pane_id"))? .try_into()?, borderless: set_borderless_action.borderless, }) }, ActionType::TogglePaneInGroup(_) => { Ok(crate::input::actions::Action::TogglePaneInGroup) }, ActionType::ToggleGroupMarking(_) => { Ok(crate::input::actions::Action::ToggleGroupMarking) }, ActionType::SetPaneColor(set_pane_color_action) => { Ok(crate::input::actions::Action::SetPaneColor { pane_id: set_pane_color_action .pane_id .ok_or_else(|| anyhow!("SetPaneColor missing pane_id"))? .try_into()?, fg: set_pane_color_action.fg, bg: set_pane_color_action.bg, }) }, // Pane-targeting CLI-only variants ActionType::ScrollUpByPaneId(a) => { Ok(crate::input::actions::Action::ScrollUpByPaneId { pane_id: a .pane_id .ok_or_else(|| anyhow!("ScrollUpByPaneId missing pane_id"))? .try_into()?, }) }, ActionType::ScrollDownByPaneId(a) => { Ok(crate::input::actions::Action::ScrollDownByPaneId { pane_id: a .pane_id .ok_or_else(|| anyhow!("ScrollDownByPaneId missing pane_id"))? .try_into()?, }) }, ActionType::ScrollToTopByPaneId(a) => { Ok(crate::input::actions::Action::ScrollToTopByPaneId { pane_id: a .pane_id .ok_or_else(|| anyhow!("ScrollToTopByPaneId missing pane_id"))? .try_into()?, }) }, ActionType::ScrollToBottomByPaneId(a) => { Ok(crate::input::actions::Action::ScrollToBottomByPaneId { pane_id: a .pane_id .ok_or_else(|| anyhow!("ScrollToBottomByPaneId missing pane_id"))? .try_into()?, }) }, ActionType::PageScrollUpByPaneId(a) => { Ok(crate::input::actions::Action::PageScrollUpByPaneId { pane_id: a .pane_id .ok_or_else(|| anyhow!("PageScrollUpByPaneId missing pane_id"))? .try_into()?, }) }, ActionType::PageScrollDownByPaneId(a) => { Ok(crate::input::actions::Action::PageScrollDownByPaneId { pane_id: a .pane_id .ok_or_else(|| anyhow!("PageScrollDownByPaneId missing pane_id"))? .try_into()?, }) }, ActionType::HalfPageScrollUpByPaneId(a) => { Ok(crate::input::actions::Action::HalfPageScrollUpByPaneId { pane_id: a .pane_id .ok_or_else(|| anyhow!("HalfPageScrollUpByPaneId missing pane_id"))? .try_into()?, }) }, ActionType::HalfPageScrollDownByPaneId(a) => { Ok(crate::input::actions::Action::HalfPageScrollDownByPaneId { pane_id: a .pane_id .ok_or_else(|| anyhow!("HalfPageScrollDownByPaneId missing pane_id"))? .try_into()?, }) }, ActionType::ResizeByPaneId(a) => { let resize_action = a .resize_action .ok_or_else(|| anyhow!("ResizeByPaneId missing resize_action"))?; let resize = proto_i32_to_resize(resize_action.resize)?; let direction = resize_action .direction .map(|d| proto_i32_to_direction(d)) .transpose()?; Ok(crate::input::actions::Action::ResizeByPaneId { pane_id: a .pane_id .ok_or_else(|| anyhow!("ResizeByPaneId missing pane_id"))? .try_into()?, resize, direction, }) }, ActionType::MovePaneByPaneId(a) => { let direction = a.direction.map(|d| proto_i32_to_direction(d)).transpose()?; Ok(crate::input::actions::Action::MovePaneByPaneId { pane_id: a .pane_id .ok_or_else(|| anyhow!("MovePaneByPaneId missing pane_id"))? .try_into()?, direction, }) }, ActionType::MovePaneBackwardsByPaneId(a) => { Ok(crate::input::actions::Action::MovePaneBackwardsByPaneId { pane_id: a .pane_id .ok_or_else(|| anyhow!("MovePaneBackwardsByPaneId missing pane_id"))? .try_into()?, }) }, ActionType::ClearScreenByPaneId(a) => { Ok(crate::input::actions::Action::ClearScreenByPaneId { pane_id: a .pane_id .ok_or_else(|| anyhow!("ClearScreenByPaneId missing pane_id"))? .try_into()?, }) }, ActionType::EditScrollbackByPaneId(a) => { Ok(crate::input::actions::Action::EditScrollbackByPaneId { pane_id: a .pane_id .ok_or_else(|| anyhow!("EditScrollbackByPaneId missing pane_id"))? .try_into()?, ansi: a.ansi, }) }, ActionType::ToggleFullscreenByPaneId(a) => Ok( crate::input::actions::Action::ToggleFocusFullscreenByPaneId { pane_id: a .pane_id .ok_or_else(|| anyhow!("ToggleFullscreenByPaneId missing pane_id"))? .try_into()?, }, ), ActionType::ToggleNoUiFullscreenByPaneId(a) => Ok( crate::input::actions::Action::ToggleFocusNoUiFullscreenByPaneId { pane_id: a .pane_id .ok_or_else(|| anyhow!("ToggleNoUiFullscreenByPaneId missing pane_id"))? .try_into()?, }, ), ActionType::TogglePaneEmbedOrFloatingByPaneId(a) => Ok( crate::input::actions::Action::TogglePaneEmbedOrFloatingByPaneId { pane_id: a .pane_id .ok_or_else(|| { anyhow!("TogglePaneEmbedOrFloatingByPaneId missing pane_id") })? .try_into()?, }, ), ActionType::CloseFocusByPaneId(a) => { Ok(crate::input::actions::Action::CloseFocusByPaneId { pane_id: a .pane_id .ok_or_else(|| anyhow!("CloseFocusByPaneId missing pane_id"))? .try_into()?, }) }, ActionType::RenamePaneByPaneId(a) => { Ok(crate::input::actions::Action::RenamePaneByPaneId { pane_id: a.pane_id.map(|p| p.try_into()).transpose()?, name: a.name, }) }, ActionType::UndoRenamePaneByPaneId(a) => { Ok(crate::input::actions::Action::UndoRenamePaneByPaneId { pane_id: a .pane_id .ok_or_else(|| anyhow!("UndoRenamePaneByPaneId missing pane_id"))? .try_into()?, }) }, ActionType::TogglePanePinnedByPaneId(a) => { Ok(crate::input::actions::Action::TogglePanePinnedByPaneId { pane_id: a .pane_id .ok_or_else(|| anyhow!("TogglePanePinnedByPaneId missing pane_id"))? .try_into()?, }) }, ActionType::FocusPaneByPaneId(a) => { Ok(crate::input::actions::Action::FocusPaneByPaneId { pane_id: a .pane_id .ok_or_else(|| anyhow!("FocusPaneByPaneId missing pane_id"))? .try_into()?, }) }, // Tab-targeting CLI-only variants ActionType::UndoRenameTabByTabId(a) => { Ok(crate::input::actions::Action::UndoRenameTabByTabId { id: a.id }) }, ActionType::ToggleActiveSyncTabByTabId(a) => { Ok(crate::input::actions::Action::ToggleActiveSyncTabByTabId { id: a.id }) }, ActionType::ToggleFloatingPanesByTabId(a) => { Ok(crate::input::actions::Action::ToggleFloatingPanesByTabId { id: a.id }) }, ActionType::PreviousSwapLayoutByTabId(a) => { Ok(crate::input::actions::Action::PreviousSwapLayoutByTabId { id: a.id }) }, ActionType::NextSwapLayoutByTabId(a) => { Ok(crate::input::actions::Action::NextSwapLayoutByTabId { id: a.id }) }, ActionType::MoveTabByTabId(a) => { let direction = proto_i32_to_direction(a.direction)?; Ok(crate::input::actions::Action::MoveTabByTabId { id: a.id, direction, }) }, } } } impl From for crate::client_server_contract::client_server_contract::KeyWithModifier { fn from(key: crate::data::KeyWithModifier) -> Self { use crate::ipc::enum_conversions::{bare_key_to_proto_i32, key_modifier_to_proto_i32}; // Handle character keys specially - store the character for Char variant let (bare_key_enum, char_data) = match &key.bare_key { crate::data::BareKey::Char(c) => ( crate::client_server_contract::client_server_contract::BareKey::Char as i32, Some(c.to_string()), ), other => (bare_key_to_proto_i32(*other), None), }; Self { bare_key: bare_key_enum, key_modifiers: key .key_modifiers .into_iter() .map(|modifier| key_modifier_to_proto_i32(modifier)) .collect(), character: char_data, } } } impl TryFrom for crate::data::KeyWithModifier { type Error = anyhow::Error; fn try_from( key: crate::client_server_contract::client_server_contract::KeyWithModifier, ) -> Result { use crate::ipc::enum_conversions::{bare_key_from_proto_i32, key_modifier_from_proto_i32}; use std::collections::BTreeSet; // Handle character keys specially let bare_key = if key.bare_key == crate::client_server_contract::client_server_contract::BareKey::Char as i32 { let character_str = key .character .ok_or_else(|| anyhow!("Character key missing character data"))?; let character = character_str .chars() .next() .ok_or_else(|| anyhow!("Empty character string"))?; crate::data::BareKey::Char(character) } else { bare_key_from_proto_i32(key.bare_key)? }; let key_modifiers: Result> = key .key_modifiers .into_iter() .map(|modifier| key_modifier_from_proto_i32(modifier)) .collect(); Ok(Self { bare_key, key_modifiers: key_modifiers?, }) } } impl From for crate::client_server_contract::client_server_contract::ConnectToSession { fn from(connect: crate::data::ConnectToSession) -> Self { Self { name: connect.name, tab_position: connect.tab_position.map(|p| p as u32), pane_id: connect.pane_id.map(|(id, is_plugin)| { crate::client_server_contract::client_server_contract::PaneIdWithPlugin { pane_id: id, is_plugin, } }), layout: connect.layout.map(|l| l.into()), cwd: connect.cwd.map(|p| p.to_string_lossy().to_string()), } } } impl TryFrom for crate::data::ConnectToSession { type Error = anyhow::Error; fn try_from( connect: crate::client_server_contract::client_server_contract::ConnectToSession, ) -> Result { Ok(Self { name: connect.name, tab_position: connect.tab_position.map(|p| p as usize), pane_id: connect.pane_id.map(|p| (p.pane_id, p.is_plugin)), layout: connect.layout.map(|l| l.try_into()).transpose()?, cwd: connect.cwd.map(PathBuf::from), }) } } impl From for crate::client_server_contract::client_server_contract::LayoutInfo { fn from(layout: crate::data::LayoutInfo) -> Self { use crate::client_server_contract::client_server_contract::layout_info::LayoutType; let (layout_type, layout_metadata) = match layout { crate::data::LayoutInfo::BuiltIn(name) => (LayoutType::BuiltinName(name), None), crate::data::LayoutInfo::File(path, metadata) => { (LayoutType::FilePath(path), Some(metadata.into())) }, crate::data::LayoutInfo::Url(url) => (LayoutType::Url(url), None), crate::data::LayoutInfo::Stringified(content) => { (LayoutType::Stringified(content), None) }, }; Self { layout_type: Some(layout_type), layout_metadata, } } } impl TryFrom for crate::data::LayoutInfo { type Error = anyhow::Error; fn try_from( layout: crate::client_server_contract::client_server_contract::LayoutInfo, ) -> Result { use crate::client_server_contract::client_server_contract::layout_info::LayoutType; match layout.layout_type { Some(LayoutType::BuiltinName(name)) => Ok(crate::data::LayoutInfo::BuiltIn(name)), Some(LayoutType::FilePath(path)) => { let layout_metadata = layout .layout_metadata .map(|m| m.try_into()) .transpose()? .unwrap_or_default(); Ok(crate::data::LayoutInfo::File(path, layout_metadata)) }, Some(LayoutType::Url(url)) => Ok(crate::data::LayoutInfo::Url(url)), Some(LayoutType::Stringified(content)) => { Ok(crate::data::LayoutInfo::Stringified(content)) }, None => Err(anyhow!("LayoutInfo missing layout_type")), } } } impl From for ProtoLayoutMetadata { fn from(metadata: crate::data::LayoutMetadata) -> Self { ProtoLayoutMetadata { tabs: metadata.tabs.into_iter().map(|t| t.into()).collect(), creation_time: metadata.creation_time, update_time: metadata.update_time, } } } impl TryFrom for crate::data::LayoutMetadata { type Error = anyhow::Error; fn try_from(proto_metadata: ProtoLayoutMetadata) -> Result { let tabs = proto_metadata .tabs .into_iter() .map(|t| t.try_into()) .collect::>>()?; Ok(crate::data::LayoutMetadata { tabs, creation_time: proto_metadata.creation_time, update_time: proto_metadata.update_time, }) } } impl From for ProtoTabMetadata { fn from(metadata: crate::data::TabMetadata) -> Self { ProtoTabMetadata { pane_metadata: metadata.panes.into_iter().map(|p| p.into()).collect(), name: metadata.name, } } } impl TryFrom for crate::data::TabMetadata { type Error = anyhow::Error; fn try_from(proto_metadata: ProtoTabMetadata) -> Result { let panes = proto_metadata .pane_metadata .into_iter() .map(|p| p.try_into()) .collect::>>()?; Ok(crate::data::TabMetadata { panes, name: proto_metadata.name, }) } } impl From for ProtoPaneMetadata { fn from(metadata: crate::data::PaneMetadata) -> Self { ProtoPaneMetadata { name: metadata.name, is_plugin: metadata.is_plugin, is_builtin_plugin: metadata.is_builtin_plugin, } } } impl TryFrom for crate::data::PaneMetadata { type Error = anyhow::Error; fn try_from(proto_metadata: ProtoPaneMetadata) -> Result { Ok(crate::data::PaneMetadata { name: proto_metadata.name, is_plugin: proto_metadata.is_plugin, is_builtin_plugin: proto_metadata.is_builtin_plugin, }) } } impl From for ProtoExitReason { fn from(reason: ExitReason) -> Self { match reason { ExitReason::Normal => ProtoExitReason::Normal, ExitReason::NormalDetached => ProtoExitReason::NormalDetached, ExitReason::ForceDetached => ProtoExitReason::ForceDetached, ExitReason::CannotAttach => ProtoExitReason::CannotAttach, ExitReason::Disconnect => ProtoExitReason::Disconnect, ExitReason::WebClientsForbidden => ProtoExitReason::WebClientsForbidden, ExitReason::KickedByHost => ProtoExitReason::KickedByHost, ExitReason::Error(_msg) => ProtoExitReason::Error, ExitReason::CustomExitStatus(_status) => ProtoExitReason::CustomExitStatus, } } } impl TryFrom for ExitReason { type Error = anyhow::Error; fn try_from(reason: ProtoExitReason) -> Result { match reason { ProtoExitReason::Normal => Ok(ExitReason::Normal), ProtoExitReason::NormalDetached => Ok(ExitReason::NormalDetached), ProtoExitReason::ForceDetached => Ok(ExitReason::ForceDetached), ProtoExitReason::CannotAttach => Ok(ExitReason::CannotAttach), ProtoExitReason::Disconnect => Ok(ExitReason::Disconnect), ProtoExitReason::WebClientsForbidden => Ok(ExitReason::WebClientsForbidden), ProtoExitReason::KickedByHost => Ok(ExitReason::KickedByHost), ProtoExitReason::Error => Ok(ExitReason::Error("Protobuf error".to_string())), ProtoExitReason::CustomExitStatus => Ok(ExitReason::CustomExitStatus(0)), ProtoExitReason::Unspecified => Err(anyhow!("Unspecified exit reason")), } } } impl From for ProtoResizeCause { fn from(cause: ResizeCause) -> Self { match cause { ResizeCause::Viewport => ProtoResizeCause::Viewport, ResizeCause::RenderingPreference => ProtoResizeCause::RenderingPreference, ResizeCause::SizeSettled => ProtoResizeCause::SizeSettled, } } } impl From for ResizeCause { fn from(cause: ProtoResizeCause) -> Self { match cause { ProtoResizeCause::Viewport => ResizeCause::Viewport, ProtoResizeCause::RenderingPreference => ResizeCause::RenderingPreference, ProtoResizeCause::SizeSettled => ResizeCause::SizeSettled, } } } impl From for ProtoHostTerminalThemeIndication { fn from(mode: HostTerminalThemeMode) -> Self { match mode { HostTerminalThemeMode::Dark => ProtoHostTerminalThemeIndication::Dark, HostTerminalThemeMode::Light => ProtoHostTerminalThemeIndication::Light, } } } impl From for HostTerminalThemeMode { fn from(mode: ProtoHostTerminalThemeIndication) -> Self { match mode { ProtoHostTerminalThemeIndication::Dark => HostTerminalThemeMode::Dark, ProtoHostTerminalThemeIndication::Light => HostTerminalThemeMode::Light, } } } // InputMode conversion helper functions fn input_mode_to_proto_i32(mode: InputMode) -> i32 { match mode { InputMode::Normal => ProtoInputMode::Normal as i32, InputMode::Locked => ProtoInputMode::Locked as i32, InputMode::Resize => ProtoInputMode::Resize as i32, InputMode::Pane => ProtoInputMode::Pane as i32, InputMode::Tab => ProtoInputMode::Tab as i32, InputMode::Scroll => ProtoInputMode::Scroll as i32, InputMode::EnterSearch => ProtoInputMode::EnterSearch as i32, InputMode::Search => ProtoInputMode::Search as i32, InputMode::RenameTab => ProtoInputMode::RenameTab as i32, InputMode::RenamePane => ProtoInputMode::RenamePane as i32, InputMode::Session => ProtoInputMode::Session as i32, InputMode::Move => ProtoInputMode::Move as i32, InputMode::Prompt => ProtoInputMode::Prompt as i32, InputMode::Tmux => ProtoInputMode::Tmux as i32, } } fn proto_i32_to_input_mode(i: i32) -> Result { match ProtoInputMode::from_i32(i) { Some(ProtoInputMode::Normal) => Ok(InputMode::Normal), Some(ProtoInputMode::Locked) => Ok(InputMode::Locked), Some(ProtoInputMode::Resize) => Ok(InputMode::Resize), Some(ProtoInputMode::Pane) => Ok(InputMode::Pane), Some(ProtoInputMode::Tab) => Ok(InputMode::Tab), Some(ProtoInputMode::Scroll) => Ok(InputMode::Scroll), Some(ProtoInputMode::EnterSearch) => Ok(InputMode::EnterSearch), Some(ProtoInputMode::Search) => Ok(InputMode::Search), Some(ProtoInputMode::RenameTab) => Ok(InputMode::RenameTab), Some(ProtoInputMode::RenamePane) => Ok(InputMode::RenamePane), Some(ProtoInputMode::Session) => Ok(InputMode::Session), Some(ProtoInputMode::Move) => Ok(InputMode::Move), Some(ProtoInputMode::Prompt) => Ok(InputMode::Prompt), Some(ProtoInputMode::Tmux) => Ok(InputMode::Tmux), _ => Err(anyhow!("Invalid InputMode value: {}", i)), } } // Additional helper functions for Action conversion fn resize_to_proto_i32(resize: crate::data::Resize) -> i32 { use crate::client_server_contract::client_server_contract::ResizeType; match resize { crate::data::Resize::Increase => ResizeType::Increase as i32, crate::data::Resize::Decrease => ResizeType::Decrease as i32, } } fn direction_to_proto_i32(direction: crate::data::Direction) -> i32 { use crate::client_server_contract::client_server_contract::Direction as ProtoDirection; match direction { crate::data::Direction::Left => ProtoDirection::Left as i32, crate::data::Direction::Right => ProtoDirection::Right as i32, crate::data::Direction::Up => ProtoDirection::Up as i32, crate::data::Direction::Down => ProtoDirection::Down as i32, } } fn search_direction_to_proto_i32(direction: crate::input::actions::SearchDirection) -> i32 { use crate::client_server_contract::client_server_contract::SearchDirection as ProtoSearchDirection; match direction { crate::input::actions::SearchDirection::Up => ProtoSearchDirection::Up as i32, crate::input::actions::SearchDirection::Down => ProtoSearchDirection::Down as i32, } } fn search_option_to_proto_i32(option: crate::input::actions::SearchOption) -> i32 { use crate::client_server_contract::client_server_contract::SearchOption as ProtoSearchOption; match option { crate::input::actions::SearchOption::CaseSensitivity => { ProtoSearchOption::CaseSensitivity as i32 }, crate::input::actions::SearchOption::Wrap => ProtoSearchOption::Wrap as i32, crate::input::actions::SearchOption::WholeWord => ProtoSearchOption::WholeWord as i32, } } fn unblock_condition_to_proto_i32(condition: crate::data::UnblockCondition) -> i32 { use crate::client_server_contract::client_server_contract::UnblockCondition as ProtoUnblockCondition; match condition { crate::data::UnblockCondition::OnExitSuccess => ProtoUnblockCondition::OnExitSuccess as i32, crate::data::UnblockCondition::OnExitFailure => ProtoUnblockCondition::OnExitFailure as i32, crate::data::UnblockCondition::OnAnyExit => ProtoUnblockCondition::OnAnyExit as i32, } } // Reverse helper functions for Action conversion fn proto_i32_to_resize(resize: i32) -> Result { use crate::client_server_contract::client_server_contract::ResizeType as ProtoResize; let proto_resize = match resize { x if x == ProtoResize::Increase as i32 => ProtoResize::Increase, x if x == ProtoResize::Decrease as i32 => ProtoResize::Decrease, _ => return Err(anyhow!("Invalid ResizeType: {}", resize)), }; match proto_resize { ProtoResize::Increase => Ok(crate::data::Resize::Increase), ProtoResize::Decrease => Ok(crate::data::Resize::Decrease), ProtoResize::Unspecified => Err(anyhow!("Unspecified ResizeType")), } } fn proto_i32_to_direction(direction: i32) -> Result { use crate::client_server_contract::client_server_contract::Direction as ProtoDirection; let proto_direction = match direction { x if x == ProtoDirection::Left as i32 => ProtoDirection::Left, x if x == ProtoDirection::Right as i32 => ProtoDirection::Right, x if x == ProtoDirection::Up as i32 => ProtoDirection::Up, x if x == ProtoDirection::Down as i32 => ProtoDirection::Down, _ => return Err(anyhow!("Invalid Direction: {}", direction)), }; match proto_direction { ProtoDirection::Left => Ok(crate::data::Direction::Left), ProtoDirection::Right => Ok(crate::data::Direction::Right), ProtoDirection::Up => Ok(crate::data::Direction::Up), ProtoDirection::Down => Ok(crate::data::Direction::Down), ProtoDirection::Unspecified => Err(anyhow!("Unspecified direction")), } } fn proto_i32_to_search_direction(direction: i32) -> Result { use crate::client_server_contract::client_server_contract::SearchDirection as ProtoSearchDirection; let proto_direction = match direction { x if x == ProtoSearchDirection::Up as i32 => ProtoSearchDirection::Up, x if x == ProtoSearchDirection::Down as i32 => ProtoSearchDirection::Down, _ => return Err(anyhow!("Invalid SearchDirection: {}", direction)), }; match proto_direction { ProtoSearchDirection::Up => Ok(crate::input::actions::SearchDirection::Up), ProtoSearchDirection::Down => Ok(crate::input::actions::SearchDirection::Down), ProtoSearchDirection::Unspecified => Err(anyhow!("Unspecified search direction")), } } fn proto_i32_to_search_option(option: i32) -> Result { use crate::client_server_contract::client_server_contract::SearchOption as ProtoSearchOption; let proto_option = match option { x if x == ProtoSearchOption::CaseSensitivity as i32 => ProtoSearchOption::CaseSensitivity, x if x == ProtoSearchOption::WholeWord as i32 => ProtoSearchOption::WholeWord, x if x == ProtoSearchOption::Wrap as i32 => ProtoSearchOption::Wrap, _ => return Err(anyhow!("Invalid SearchOption: {}", option)), }; match proto_option { ProtoSearchOption::CaseSensitivity => { Ok(crate::input::actions::SearchOption::CaseSensitivity) }, ProtoSearchOption::Wrap => Ok(crate::input::actions::SearchOption::Wrap), ProtoSearchOption::WholeWord => Ok(crate::input::actions::SearchOption::WholeWord), ProtoSearchOption::Unspecified => Err(anyhow!("Unspecified search option")), } } fn proto_i32_to_unblock_condition(condition: i32) -> Result { use crate::client_server_contract::client_server_contract::UnblockCondition as ProtoUnblockCondition; let proto_condition = match condition { x if x == ProtoUnblockCondition::OnExitSuccess as i32 => { ProtoUnblockCondition::OnExitSuccess }, x if x == ProtoUnblockCondition::OnExitFailure as i32 => { ProtoUnblockCondition::OnExitFailure }, x if x == ProtoUnblockCondition::OnAnyExit as i32 => ProtoUnblockCondition::OnAnyExit, _ => return Err(anyhow!("Invalid UnblockCondition: {}", condition)), }; match proto_condition { ProtoUnblockCondition::OnExitSuccess => Ok(crate::data::UnblockCondition::OnExitSuccess), ProtoUnblockCondition::OnExitFailure => Ok(crate::data::UnblockCondition::OnExitFailure), ProtoUnblockCondition::OnAnyExit => Ok(crate::data::UnblockCondition::OnAnyExit), ProtoUnblockCondition::Unspecified => Err(anyhow!("Unspecified unblock condition")), } } // Position conversion impl From for crate::client_server_contract::client_server_contract::Position { fn from(pos: crate::position::Position) -> Self { Self { line: pos.line.0 as i32, column: pos.column.0 as u64, } } } // Reverse Position conversion impl TryFrom for crate::position::Position { type Error = anyhow::Error; fn try_from( pos: crate::client_server_contract::client_server_contract::Position, ) -> Result { Ok(Self { line: crate::position::Line(pos.line as isize), column: crate::position::Column(pos.column as usize), }) } } // OpenFilePayload conversion impl From for crate::client_server_contract::client_server_contract::OpenFilePayload { fn from(payload: crate::input::command::OpenFilePayload) -> Self { Self { file_to_open: payload.path.to_string_lossy().to_string(), line_number: payload.line_number.map(|n| n as u32), cwd: payload.cwd.map(|p| p.to_string_lossy().to_string()), originating_plugin: payload.originating_plugin.map(|op| op.into()), } } } // Reverse OpenFilePayload conversion impl TryFrom for crate::input::command::OpenFilePayload { type Error = anyhow::Error; fn try_from( payload: crate::client_server_contract::client_server_contract::OpenFilePayload, ) -> Result { Ok(Self { path: PathBuf::from(payload.file_to_open), line_number: payload.line_number.map(|n| n as usize), cwd: payload.cwd.map(PathBuf::from), originating_plugin: payload .originating_plugin .map(|op| op.try_into()) .transpose()?, }) } } // PaneId conversion impl From for crate::client_server_contract::client_server_contract::PaneId { fn from(pane_id: crate::data::PaneId) -> Self { use crate::client_server_contract::client_server_contract::pane_id::PaneType; match pane_id { crate::data::PaneId::Terminal(id) => Self { pane_type: Some(PaneType::Terminal(id)), }, crate::data::PaneId::Plugin(id) => Self { pane_type: Some(PaneType::Plugin(id)), }, } } } // Reverse PaneId conversion impl TryFrom for crate::data::PaneId { type Error = anyhow::Error; fn try_from( pane_id: crate::client_server_contract::client_server_contract::PaneId, ) -> Result { use crate::client_server_contract::client_server_contract::pane_id::PaneType; match pane_id .pane_type .ok_or_else(|| anyhow!("PaneId missing pane_type"))? { PaneType::Terminal(id) => Ok(crate::data::PaneId::Terminal(id)), PaneType::Plugin(id) => Ok(crate::data::PaneId::Plugin(id)), } } } // FloatingCoordinate conversion - SplitSize to FloatingCoordinate impl From for crate::client_server_contract::client_server_contract::FloatingCoordinate { fn from(size: crate::input::layout::SplitSize) -> Self { match size { crate::input::layout::SplitSize::Percent(p) => Self { coordinate_type: Some(crate::client_server_contract::client_server_contract::floating_coordinate::CoordinateType::Percent(p as f32)), }, crate::input::layout::SplitSize::Fixed(f) => Self { coordinate_type: Some(crate::client_server_contract::client_server_contract::floating_coordinate::CoordinateType::Fixed(f as u32)), }, } } } // Reverse FloatingCoordinate conversion impl TryFrom for crate::input::layout::SplitSize { type Error = anyhow::Error; fn try_from( coord: crate::client_server_contract::client_server_contract::FloatingCoordinate, ) -> Result { use crate::client_server_contract::client_server_contract::floating_coordinate::CoordinateType; match coord .coordinate_type .ok_or_else(|| anyhow!("FloatingCoordinate missing coordinate_type"))? { CoordinateType::Percent(p) => Ok(crate::input::layout::SplitSize::Percent(p as usize)), CoordinateType::Fixed(f) => Ok(crate::input::layout::SplitSize::Fixed(f as usize)), } } } // FloatingCoordinate conversion - PercentOrFixed to FloatingCoordinate impl From for crate::client_server_contract::client_server_contract::FloatingCoordinate { fn from(size: crate::input::layout::PercentOrFixed) -> Self { match size { crate::input::layout::PercentOrFixed::Percent(p) => Self { coordinate_type: Some(crate::client_server_contract::client_server_contract::floating_coordinate::CoordinateType::Percent(p as f32)), }, crate::input::layout::PercentOrFixed::Fixed(f) => Self { coordinate_type: Some(crate::client_server_contract::client_server_contract::floating_coordinate::CoordinateType::Fixed(f as u32)), }, } } } // Reverse FloatingCoordinate conversion for PercentOrFixed impl TryFrom for crate::input::layout::PercentOrFixed { type Error = anyhow::Error; fn try_from( coord: crate::client_server_contract::client_server_contract::FloatingCoordinate, ) -> Result { use crate::client_server_contract::client_server_contract::floating_coordinate::CoordinateType; match coord .coordinate_type .ok_or_else(|| anyhow!("FloatingCoordinate missing coordinate_type"))? { CoordinateType::Percent(p) => { Ok(crate::input::layout::PercentOrFixed::Percent(p as usize)) }, CoordinateType::Fixed(f) => Ok(crate::input::layout::PercentOrFixed::Fixed(f as usize)), } } } // FloatingPaneCoordinates conversion impl From for crate::client_server_contract::client_server_contract::FloatingPaneCoordinates { fn from(coords: crate::data::FloatingPaneCoordinates) -> Self { Self { x: coords.x.map(|x| x.into()), y: coords.y.map(|y| y.into()), width: coords.width.map(|w| w.into()), height: coords.height.map(|h| h.into()), pinned: coords.pinned, borderless: coords.borderless, } } } // Reverse FloatingPaneCoordinates conversion impl TryFrom for crate::data::FloatingPaneCoordinates { type Error = anyhow::Error; fn try_from( coords: crate::client_server_contract::client_server_contract::FloatingPaneCoordinates, ) -> Result { Ok(Self { x: coords.x.map(|x| x.try_into()).transpose()?, y: coords.y.map(|y| y.try_into()).transpose()?, width: coords.width.map(|w| w.try_into()).transpose()?, height: coords.height.map(|h| h.try_into()).transpose()?, pinned: coords.pinned, borderless: coords.borderless, }) } } // NewPanePlacement conversion impl From for crate::client_server_contract::client_server_contract::NewPanePlacement { fn from(placement: crate::data::NewPanePlacement) -> Self { use crate::client_server_contract::client_server_contract::new_pane_placement::PlacementType; use crate::client_server_contract::client_server_contract::{ NoPreferencePlacement, StackedPlacement, TiledPlacement, }; let placement_type = match placement { crate::data::NewPanePlacement::NoPreference { borderless: Some(b), } => PlacementType::NoPreferenceWithOptions(NoPreferencePlacement { borderless: Some(b), }), crate::data::NewPanePlacement::NoPreference { borderless: None } => { PlacementType::NoPreference(true) }, crate::data::NewPanePlacement::Tiled { direction, borderless: Some(b), } => PlacementType::TiledWithOptions(TiledPlacement { direction: direction.map(direction_to_proto_i32), borderless: Some(b), }), crate::data::NewPanePlacement::Tiled { direction, borderless: None, } => PlacementType::Tiled(direction.map(direction_to_proto_i32).unwrap_or(0)), crate::data::NewPanePlacement::Floating(coords) => { PlacementType::Floating(coords.map(|c| c.into()).unwrap_or_default()) }, crate::data::NewPanePlacement::InPlace { pane_id_to_replace, close_replaced_pane, borderless, } => PlacementType::InPlace( crate::client_server_contract::client_server_contract::NewPanePlacementInPlace { pane_id_to_replace: pane_id_to_replace.map(|id| id.into()), close_replaced_pane, borderless, }, ), crate::data::NewPanePlacement::Stacked { pane_id_to_stack_under, borderless: Some(b), } => PlacementType::StackedWithOptions(StackedPlacement { pane_id_to_stack_under: pane_id_to_stack_under.map(|id| id.into()), borderless: Some(b), }), crate::data::NewPanePlacement::Stacked { pane_id_to_stack_under, borderless: None, } => PlacementType::Stacked( pane_id_to_stack_under .map(|id| id.into()) .unwrap_or_default(), ), }; Self { placement_type: Some(placement_type), } } } // Reverse NewPanePlacement conversion impl TryFrom for crate::data::NewPanePlacement { type Error = anyhow::Error; fn try_from( placement: crate::client_server_contract::client_server_contract::NewPanePlacement, ) -> Result { use crate::client_server_contract::client_server_contract::new_pane_placement::PlacementType; match placement .placement_type .ok_or_else(|| anyhow!("NewPanePlacement missing placement_type"))? { // New fields (with borderless support) take priority PlacementType::NoPreferenceWithOptions(opts) => { Ok(crate::data::NewPanePlacement::NoPreference { borderless: opts.borderless, }) }, PlacementType::TiledWithOptions(opts) => { let direction = opts.direction.map(proto_i32_to_direction).transpose()?; Ok(crate::data::NewPanePlacement::Tiled { direction, borderless: opts.borderless, }) }, PlacementType::StackedWithOptions(opts) => { let pane_id = opts .pane_id_to_stack_under .map(|id| id.try_into()) .transpose()?; Ok(crate::data::NewPanePlacement::Stacked { pane_id_to_stack_under: pane_id, borderless: opts.borderless, }) }, // Legacy fields (without borderless support) PlacementType::NoPreference(_) => { Ok(crate::data::NewPanePlacement::NoPreference { borderless: None }) }, PlacementType::Tiled(direction) => { let direction = if direction == 0 { None } else { Some(proto_i32_to_direction(direction)?) }; Ok(crate::data::NewPanePlacement::Tiled { direction, borderless: None, }) }, PlacementType::Floating(coords) => { let coords = if coords == Default::default() { None } else { Some(coords.try_into()?) }; Ok(crate::data::NewPanePlacement::Floating(coords)) }, PlacementType::InPlace(in_place) => Ok(crate::data::NewPanePlacement::InPlace { pane_id_to_replace: in_place .pane_id_to_replace .map(|id| id.try_into()) .transpose()?, close_replaced_pane: in_place.close_replaced_pane, borderless: in_place.borderless, }), PlacementType::Stacked(pane_id) => { let pane_id = if pane_id == Default::default() { None } else { Some(pane_id.try_into()?) }; Ok(crate::data::NewPanePlacement::Stacked { pane_id_to_stack_under: pane_id, borderless: None, }) }, } } } // MouseEvent conversion impl From for crate::client_server_contract::client_server_contract::MouseEvent { fn from(event: crate::input::mouse::MouseEvent) -> Self { use crate::client_server_contract::client_server_contract::{ MouseEventType as ProtoMouseEventType, Position, }; let position = Position { line: event.position.line.0 as i32, column: event.position.column.0 as u64, }; let event_type = match event.event_type { crate::input::mouse::MouseEventType::Press => ProtoMouseEventType::Press as i32, crate::input::mouse::MouseEventType::Release => ProtoMouseEventType::Release as i32, crate::input::mouse::MouseEventType::Motion => ProtoMouseEventType::Motion as i32, }; Self { event_type, left: event.left, right: event.right, middle: event.middle, wheel_up: event.wheel_up, wheel_down: event.wheel_down, wheel_left: event.wheel_left, wheel_right: event.wheel_right, shift: event.shift, alt: event.alt, ctrl: event.ctrl, position: Some(position), } } } // RunCommandAction conversion impl From for crate::client_server_contract::client_server_contract::RunCommandAction { fn from(action: crate::input::command::RunCommandAction) -> Self { Self { command: action.command.to_string_lossy().to_string(), args: action.args, cwd: action.cwd.map(|p| p.to_string_lossy().to_string()), direction: action.direction.map(|d| direction_to_proto_i32(d)), hold_on_close: action.hold_on_close, hold_on_start: action.hold_on_start, originating_plugin: action.originating_plugin.map(|op| op.into()), use_terminal_title: action.use_terminal_title, } } } // OriginatingPlugin conversion impl From for crate::client_server_contract::client_server_contract::OriginatingPlugin { fn from(orig: crate::data::OriginatingPlugin) -> Self { use std::collections::HashMap; let context: HashMap = orig.context.into_iter().map(|(k, v)| (k, v)).collect(); Self { plugin_id: orig.plugin_id, client_id: orig.client_id as u32, context, } } } // OriginatingPlugin reverse conversion impl TryFrom for crate::data::OriginatingPlugin { type Error = anyhow::Error; fn try_from( orig: crate::client_server_contract::client_server_contract::OriginatingPlugin, ) -> Result { use std::collections::BTreeMap; let context: BTreeMap = orig.context.into_iter().collect(); Ok(Self { plugin_id: orig.plugin_id, client_id: orig.client_id as u16, context, }) } } // SplitDirection conversion helper fn split_direction_to_proto_i32(direction: crate::input::layout::SplitDirection) -> i32 { use crate::client_server_contract::client_server_contract::SplitDirection as ProtoSplitDirection; match direction { crate::input::layout::SplitDirection::Horizontal => ProtoSplitDirection::Horizontal as i32, crate::input::layout::SplitDirection::Vertical => ProtoSplitDirection::Vertical as i32, } } // SplitSize conversion impl From for crate::client_server_contract::client_server_contract::SplitSize { fn from(size: crate::input::layout::SplitSize) -> Self { use crate::client_server_contract::client_server_contract::split_size::SizeType; match size { crate::input::layout::SplitSize::Percent(p) => Self { size_type: Some(SizeType::Percent(p as u32)), }, crate::input::layout::SplitSize::Fixed(f) => Self { size_type: Some(SizeType::Fixed(f as u32)), }, } } } // PercentOrFixed conversion impl From for crate::client_server_contract::client_server_contract::PercentOrFixed { fn from(size: crate::input::layout::PercentOrFixed) -> Self { use crate::client_server_contract::client_server_contract::percent_or_fixed::SizeType; match size { crate::input::layout::PercentOrFixed::Percent(p) => Self { size_type: Some(SizeType::Percent(p as u32)), }, crate::input::layout::PercentOrFixed::Fixed(f) => Self { size_type: Some(SizeType::Fixed(f as u32)), }, } } } // Run conversion impl From for crate::client_server_contract::client_server_contract::Run { fn from(run: crate::input::layout::Run) -> Self { use crate::client_server_contract::client_server_contract::run::RunType; match run { crate::input::layout::Run::Command(cmd) => Self { run_type: Some(RunType::Command( crate::client_server_contract::client_server_contract::RunCommandAction { command: cmd.command.to_string_lossy().to_string(), args: cmd.args, cwd: cmd.cwd.map(|p| p.to_string_lossy().to_string()), direction: None, // RunCommand doesn't have direction field hold_on_close: cmd.hold_on_close, hold_on_start: cmd.hold_on_start, originating_plugin: cmd.originating_plugin.map(|op| op.into()), use_terminal_title: cmd.use_terminal_title, }, )), }, crate::input::layout::Run::Plugin(plugin) => Self { run_type: Some(RunType::Plugin(plugin.into())), }, crate::input::layout::Run::EditFile(path, line_number, cwd) => Self { run_type: Some(RunType::EditFile( crate::client_server_contract::client_server_contract::RunEditFileAction { file_path: path.to_string_lossy().to_string(), line_number: line_number.map(|n| n as u32), cwd: cwd.map(|p| p.to_string_lossy().to_string()), }, )), }, crate::input::layout::Run::Cwd(path) => Self { run_type: Some(RunType::Cwd(path.to_string_lossy().to_string())), }, } } } // TabLayoutInfo conversion impl From for crate::client_server_contract::client_server_contract::TabLayoutInfo { fn from(tab_info: crate::input::layout::TabLayoutInfo) -> Self { Self { tab_index: tab_info.tab_index as u32, tab_name: tab_info.tab_name, tiled_layout: Some(tab_info.tiled_layout.into()), floating_layouts: tab_info .floating_layouts .into_iter() .map(|l| l.into()) .collect(), swap_tiled_layouts: tab_info .swap_tiled_layouts .unwrap_or_default() .into_iter() .map(|l| l.into()) .collect(), swap_floating_layouts: tab_info .swap_floating_layouts .unwrap_or_default() .into_iter() .map(|l| l.into()) .collect(), } } } impl TryFrom for crate::input::layout::TabLayoutInfo { type Error = anyhow::Error; fn try_from( protobuf_tab: crate::client_server_contract::client_server_contract::TabLayoutInfo, ) -> Result { Ok(crate::input::layout::TabLayoutInfo { tab_index: protobuf_tab.tab_index as usize, tab_name: protobuf_tab.tab_name.filter(|s| !s.is_empty()), tiled_layout: protobuf_tab .tiled_layout .ok_or_else(|| anyhow!("missing tiled_layout"))? .try_into()?, floating_layouts: protobuf_tab .floating_layouts .into_iter() .map(|l| l.try_into()) .collect::>>()?, swap_tiled_layouts: if protobuf_tab.swap_tiled_layouts.is_empty() { None } else { Some( protobuf_tab .swap_tiled_layouts .into_iter() .map(|l| l.try_into()) .collect::>>()?, ) }, swap_floating_layouts: if protobuf_tab.swap_floating_layouts.is_empty() { None } else { Some( protobuf_tab .swap_floating_layouts .into_iter() .map(|l| l.try_into()) .collect::>>()?, ) }, }) } } // TiledPaneLayout conversion impl From for crate::client_server_contract::client_server_contract::TiledPaneLayout { fn from(layout: crate::input::layout::TiledPaneLayout) -> Self { Self { children_split_direction: split_direction_to_proto_i32(layout.children_split_direction), name: layout.name, children: layout.children.into_iter().map(|c| c.into()).collect(), split_size: layout.split_size.map(|s| s.into()), run: layout.run.map(|r| r.into()), borderless: layout.borderless, focus: layout.focus.map(|f| f.to_string()), exclude_from_sync: layout.exclude_from_sync, children_are_stacked: layout.children_are_stacked, external_children_index: layout.external_children_index.map(|l| l as u32), is_expanded_in_stack: layout.is_expanded_in_stack, hide_floating_panes: layout.hide_floating_panes, pane_initial_contents: layout.pane_initial_contents, default_fg: layout.default_fg, default_bg: layout.default_bg, } } } impl From for crate::client_server_contract::client_server_contract::FloatingPaneLayout { fn from(layout: crate::input::layout::FloatingPaneLayout) -> Self { Self { name: layout.name, height: layout.height.map(|h| h.into()), width: layout.width.map(|w| w.into()), x: layout.x.map(|x| x.into()), y: layout.y.map(|y| y.into()), pinned: layout.pinned, run: layout.run.map(|r| r.into()), focus: layout.focus, already_running: layout.already_running, pane_initial_contents: layout.pane_initial_contents, logical_position: layout.logical_position.map(|l| l as u32), borderless: layout.borderless, default_fg: layout.default_fg, default_bg: layout.default_bg, } } } impl From for crate::client_server_contract::client_server_contract::SwapTiledLayout { fn from(layout: crate::input::layout::SwapTiledLayout) -> Self { use crate::client_server_contract::client_server_contract::LayoutConstraintTiledPair; let constraint_map = layout .0 .into_iter() .map(|(constraint, tiled_layout)| LayoutConstraintTiledPair { constraint: Some(constraint.into()), layout: Some(tiled_layout.into()), }) .collect(); Self { constraint_map, name: layout.1, } } } impl From for crate::client_server_contract::client_server_contract::SwapFloatingLayout { fn from(layout: crate::input::layout::SwapFloatingLayout) -> Self { use crate::client_server_contract::client_server_contract::LayoutConstraintFloatingPair; let constraint_map = layout .0 .into_iter() .map( |(constraint, floating_layouts)| LayoutConstraintFloatingPair { constraint: Some(constraint.into()), layouts: floating_layouts.into_iter().map(|l| l.into()).collect(), }, ) .collect(); Self { constraint_map, name: layout.1, } } } // PluginUserConfiguration conversion impl From for crate::client_server_contract::client_server_contract::PluginUserConfiguration { fn from(config: crate::input::layout::PluginUserConfiguration) -> Self { Self { configuration: config.inner().clone().into_iter().collect(), // Convert BTreeMap to HashMap } } } // LayoutConstraint conversion impl From for crate::client_server_contract::client_server_contract::LayoutConstraintWithValue { fn from(constraint: crate::input::layout::LayoutConstraint) -> Self { use crate::client_server_contract::client_server_contract::LayoutConstraint as ProtoLayoutConstraint; match constraint { crate::input::layout::LayoutConstraint::MaxPanes(n) => Self { constraint_type: ProtoLayoutConstraint::MaxPanes as i32, value: Some(n as u32), }, crate::input::layout::LayoutConstraint::MinPanes(n) => Self { constraint_type: ProtoLayoutConstraint::MinPanes as i32, value: Some(n as u32), }, crate::input::layout::LayoutConstraint::ExactPanes(n) => Self { constraint_type: ProtoLayoutConstraint::ExactPanes as i32, value: Some(n as u32), }, crate::input::layout::LayoutConstraint::NoConstraint => Self { constraint_type: ProtoLayoutConstraint::NoConstraint as i32, value: None, }, } } } // RunPlugin conversion impl From for crate::client_server_contract::client_server_contract::RunPlugin { fn from(plugin: crate::input::layout::RunPlugin) -> Self { Self { allow_exec_host_cmd: plugin._allow_exec_host_cmd, location: Some(plugin.location.into()), configuration: Some(plugin.configuration.into()), initial_cwd: plugin.initial_cwd.map(|p| p.display().to_string()), } } } // PluginAlias conversion impl From for crate::client_server_contract::client_server_contract::PluginAlias { fn from(plugin: crate::input::layout::PluginAlias) -> Self { Self { name: plugin.name, configuration: plugin.configuration.map(|c| c.into()), initial_cwd: plugin.initial_cwd.map(|i| i.display().to_string()), run_plugin: plugin.run_plugin.map(|r| r.into()), } } } // RunPluginLocation conversion impl From for crate::client_server_contract::client_server_contract::RunPluginLocationData { fn from(location: crate::input::layout::RunPluginLocation) -> Self { use crate::client_server_contract::client_server_contract::{ run_plugin_location_data::LocationData, RunPluginLocation as ProtoRunPluginLocation, }; match location { crate::input::layout::RunPluginLocation::File(path) => Self { location_type: ProtoRunPluginLocation::File as i32, location_data: Some(LocationData::FilePath(path.to_string_lossy().to_string())), }, crate::input::layout::RunPluginLocation::Zellij(tag) => Self { location_type: ProtoRunPluginLocation::Zellij as i32, location_data: Some(LocationData::ZellijTag( crate::client_server_contract::client_server_contract::PluginTag { tag: tag.to_string(), }, )), }, crate::input::layout::RunPluginLocation::Remote(url) => Self { location_type: ProtoRunPluginLocation::Remote as i32, location_data: Some(LocationData::RemoteUrl(url)), }, } } } // RunPluginOrAlias conversion impl From for crate::client_server_contract::client_server_contract::RunPluginOrAlias { fn from(plugin: crate::input::layout::RunPluginOrAlias) -> Self { use crate::client_server_contract::client_server_contract::run_plugin_or_alias::PluginType; match plugin { crate::input::layout::RunPluginOrAlias::RunPlugin(run_plugin) => Self { plugin_type: Some(PluginType::Plugin(run_plugin.into())), }, crate::input::layout::RunPluginOrAlias::Alias(alias) => Self { plugin_type: Some(PluginType::Alias(alias.into())), }, } } } // CommandOrPlugin conversion impl From for crate::client_server_contract::client_server_contract::CommandOrPlugin { fn from(cmd_or_plugin: crate::data::CommandOrPlugin) -> Self { use crate::client_server_contract::client_server_contract::command_or_plugin::CommandOrPluginType; use crate::client_server_contract::client_server_contract::CommandOrPluginFile; match cmd_or_plugin { crate::data::CommandOrPlugin::Command(cmd) => Self { command_or_plugin_type: Some(CommandOrPluginType::Command(cmd.into())), }, crate::data::CommandOrPlugin::Plugin(plugin) => Self { command_or_plugin_type: Some(CommandOrPluginType::Plugin(plugin.into())), }, crate::data::CommandOrPlugin::File(f) => Self { command_or_plugin_type: Some(CommandOrPluginType::File(CommandOrPluginFile { path: f.path.display().to_string(), line_number: f.line_number.map(|n| n as i32), cwd: f.cwd.map(|c| c.display().to_string()), })), }, } } } impl TryFrom for crate::data::CommandOrPlugin { type Error = anyhow::Error; fn try_from( proto: crate::client_server_contract::client_server_contract::CommandOrPlugin, ) -> Result { use crate::client_server_contract::client_server_contract::command_or_plugin::CommandOrPluginType; let cmd_or_plugin_type = proto .command_or_plugin_type .ok_or_else(|| anyhow!("CommandOrPlugin missing command_or_plugin_type"))?; match cmd_or_plugin_type { CommandOrPluginType::Command(cmd) => { Ok(crate::data::CommandOrPlugin::Command(cmd.try_into()?)) }, CommandOrPluginType::Plugin(plugin) => { Ok(crate::data::CommandOrPlugin::Plugin(plugin.try_into()?)) }, CommandOrPluginType::File(f) => Ok(crate::data::CommandOrPlugin::File( crate::data::FileToOpen { path: std::path::PathBuf::from(&f.path), line_number: f.line_number.map(|n| n as usize), cwd: f.cwd.map(std::path::PathBuf::from), }, )), } } } // Run reverse conversion impl TryFrom for crate::input::layout::Run { type Error = anyhow::Error; fn try_from(run: crate::client_server_contract::client_server_contract::Run) -> Result { use crate::client_server_contract::client_server_contract::run::RunType; let run_type = run .run_type .ok_or_else(|| anyhow!("Run missing run_type"))?; match run_type { RunType::Command(cmd) => Ok(crate::input::layout::Run::Command( crate::input::command::RunCommand { command: std::path::PathBuf::from(cmd.command), args: cmd.args, cwd: cmd.cwd.map(std::path::PathBuf::from), hold_on_close: cmd.hold_on_close, hold_on_start: cmd.hold_on_start, originating_plugin: cmd .originating_plugin .map(|op| op.try_into()) .transpose()?, use_terminal_title: cmd.use_terminal_title, }, )), RunType::EditFile(edit) => Ok(crate::input::layout::Run::EditFile( std::path::PathBuf::from(edit.file_path), edit.line_number.map(|n| n as usize), edit.cwd.map(std::path::PathBuf::from), )), RunType::Cwd(cwd_str) => Ok(crate::input::layout::Run::Cwd(std::path::PathBuf::from( cwd_str, ))), RunType::Plugin(plugin) => Ok(crate::input::layout::Run::Plugin(plugin.try_into()?)), } } } // PercentOrFixed reverse conversion impl TryFrom for crate::input::layout::PercentOrFixed { type Error = anyhow::Error; fn try_from( value: crate::client_server_contract::client_server_contract::PercentOrFixed, ) -> Result { use crate::client_server_contract::client_server_contract::percent_or_fixed::SizeType; let size_type = value .size_type .ok_or_else(|| anyhow!("PercentOrFixed missing size_type"))?; match size_type { SizeType::Percent(percent) => Ok(crate::input::layout::PercentOrFixed::Percent( percent as usize, )), SizeType::Fixed(fixed) => { Ok(crate::input::layout::PercentOrFixed::Fixed(fixed as usize)) }, } } } // ===== REVERSE CONVERSIONS ===== // MouseEvent reverse conversion impl TryFrom for crate::input::mouse::MouseEvent { type Error = anyhow::Error; fn try_from( event: crate::client_server_contract::client_server_contract::MouseEvent, ) -> Result { use crate::client_server_contract::client_server_contract::MouseEventType as ProtoMouseEventType; let event_type = match event.event_type { x if x == ProtoMouseEventType::Press as i32 => { crate::input::mouse::MouseEventType::Press }, x if x == ProtoMouseEventType::Release as i32 => { crate::input::mouse::MouseEventType::Release }, x if x == ProtoMouseEventType::Motion as i32 => { crate::input::mouse::MouseEventType::Motion }, _ => return Err(anyhow!("Invalid MouseEventType: {}", event.event_type)), }; let position = event .position .ok_or_else(|| anyhow!("MouseEvent missing position"))? .try_into()?; Ok(crate::input::mouse::MouseEvent { event_type, left: event.left, right: event.right, middle: event.middle, wheel_up: event.wheel_up, wheel_down: event.wheel_down, wheel_left: event.wheel_left, wheel_right: event.wheel_right, shift: event.shift, alt: event.alt, ctrl: event.ctrl, position, }) } } // RunCommandAction reverse conversion impl TryFrom for crate::input::command::RunCommandAction { type Error = anyhow::Error; fn try_from( action: crate::client_server_contract::client_server_contract::RunCommandAction, ) -> Result { Ok(crate::input::command::RunCommandAction { command: std::path::PathBuf::from(action.command), args: action.args, cwd: action.cwd.map(std::path::PathBuf::from), direction: action.direction.map(proto_i32_to_direction).transpose()?, hold_on_close: action.hold_on_close, hold_on_start: action.hold_on_start, originating_plugin: action .originating_plugin .map(|op| op.try_into()) .transpose()?, use_terminal_title: action.use_terminal_title, }) } } // TiledPaneLayout reverse conversion impl TryFrom for crate::input::layout::TiledPaneLayout { type Error = anyhow::Error; fn try_from( layout: crate::client_server_contract::client_server_contract::TiledPaneLayout, ) -> Result { use crate::input::layout::{SplitDirection, SplitSize, TiledPaneLayout}; let children_split_direction = match layout.children_split_direction { x if x == crate::client_server_contract::client_server_contract::SplitDirection::Horizontal as i32 => { SplitDirection::Horizontal }, x if x == crate::client_server_contract::client_server_contract::SplitDirection::Vertical as i32 => { SplitDirection::Vertical }, _ => SplitDirection::Horizontal, // default }; let children: Result> = layout.children.into_iter().map(|c| c.try_into()).collect(); let run = layout.run.map(|r| r.try_into()).transpose()?; let split_size = layout.split_size.and_then(|size| { use crate::client_server_contract::client_server_contract::split_size::SizeType; match size.size_type { Some(SizeType::Percent(percent)) => Some(SplitSize::Percent(percent as usize)), Some(SizeType::Fixed(fixed)) => Some(SplitSize::Fixed(fixed as usize)), None => None, } }); Ok(TiledPaneLayout { children_split_direction, name: layout.name, children: children?, split_size, run, borderless: layout.borderless, focus: layout.focus.map(|f| f == "true"), // Convert string to bool external_children_index: layout.external_children_index.map(|l| l as usize), children_are_stacked: layout.children_are_stacked, is_expanded_in_stack: layout.is_expanded_in_stack, exclude_from_sync: layout.exclude_from_sync, run_instructions_to_ignore: vec![], // not represented in protobuf hide_floating_panes: layout.hide_floating_panes, pane_initial_contents: layout.pane_initial_contents, default_fg: layout.default_fg, default_bg: layout.default_bg, // no protobuf representation, same as default_fg/default_bg children_arrangement: None, }) } } // FloatingPaneLayout reverse conversion impl TryFrom for crate::input::layout::FloatingPaneLayout { type Error = anyhow::Error; fn try_from( layout: crate::client_server_contract::client_server_contract::FloatingPaneLayout, ) -> Result { let run = layout.run.map(|r| r.try_into()).transpose()?; let height = layout.height.map(|h| h.try_into()).transpose()?; let width = layout.width.map(|w| w.try_into()).transpose()?; let x = layout.x.map(|x| x.try_into()).transpose()?; let y = layout.y.map(|y| y.try_into()).transpose()?; Ok(crate::input::layout::FloatingPaneLayout { name: layout.name, height, width, x, y, pinned: layout.pinned, run, focus: layout.focus, already_running: layout.already_running, pane_initial_contents: layout.pane_initial_contents, logical_position: layout.logical_position.map(|p| p as usize), borderless: layout.borderless, default_fg: layout.default_fg, default_bg: layout.default_bg, }) } } // SwapTiledLayout reverse conversion impl TryFrom for crate::input::layout::SwapTiledLayout { type Error = anyhow::Error; fn try_from( layout: crate::client_server_contract::client_server_contract::SwapTiledLayout, ) -> Result { let constraint_map: Result> = layout .constraint_map .into_iter() .map(|pair| { Ok(( pair.constraint .ok_or_else(|| anyhow!("Missing constraint"))? .try_into()?, pair.layout .ok_or_else(|| anyhow!("Missing layout"))? .try_into()?, )) }) .collect(); Ok((constraint_map?, layout.name)) } } // SwapFloatingLayout reverse conversion impl TryFrom for crate::input::layout::SwapFloatingLayout { type Error = anyhow::Error; fn try_from( layout: crate::client_server_contract::client_server_contract::SwapFloatingLayout, ) -> Result { let constraint_map: Result> = layout .constraint_map .into_iter() .map(|pair| { let floating_layouts: Result> = pair.layouts.into_iter().map(|l| l.try_into()).collect(); Ok(( pair.constraint .ok_or_else(|| anyhow!("Missing constraint"))? .try_into()?, floating_layouts?, )) }) .collect(); Ok((constraint_map?, layout.name)) } } // PluginUserConfiguration reverse conversion impl TryFrom for crate::input::layout::PluginUserConfiguration { type Error = anyhow::Error; fn try_from( config: crate::client_server_contract::client_server_contract::PluginUserConfiguration, ) -> Result { let btree_map: BTreeMap = config.configuration.into_iter().collect(); Ok(crate::input::layout::PluginUserConfiguration::new( btree_map, )) } } // LayoutConstraint reverse conversion impl TryFrom for crate::input::layout::LayoutConstraint { type Error = anyhow::Error; fn try_from( constraint: crate::client_server_contract::client_server_contract::LayoutConstraintWithValue, ) -> Result { use crate::client_server_contract::client_server_contract::LayoutConstraint as ProtoLayoutConstraint; match constraint.constraint_type { x if x == ProtoLayoutConstraint::MaxPanes as i32 => { let value = constraint .value .ok_or_else(|| anyhow!("MaxPanes constraint missing value"))? as usize; Ok(crate::input::layout::LayoutConstraint::MaxPanes(value)) }, x if x == ProtoLayoutConstraint::MinPanes as i32 => { let value = constraint .value .ok_or_else(|| anyhow!("MinPanes constraint missing value"))? as usize; Ok(crate::input::layout::LayoutConstraint::MinPanes(value)) }, x if x == ProtoLayoutConstraint::ExactPanes as i32 => { let value = constraint .value .ok_or_else(|| anyhow!("ExactPanes constraint missing value"))? as usize; Ok(crate::input::layout::LayoutConstraint::ExactPanes(value)) }, x if x == ProtoLayoutConstraint::NoConstraint as i32 => { Ok(crate::input::layout::LayoutConstraint::NoConstraint) }, _ => Err(anyhow!( "Invalid LayoutConstraint type: {}", constraint.constraint_type )), } } } // RunPlugin reverse conversion impl TryFrom for crate::input::layout::RunPlugin { type Error = anyhow::Error; fn try_from( plugin: crate::client_server_contract::client_server_contract::RunPlugin, ) -> Result { let location = plugin .location .ok_or_else(|| anyhow!("RunPlugin missing location"))? .try_into()?; let configuration = plugin .configuration .ok_or_else(|| anyhow!("RunPlugin missing configuration"))? .try_into()?; let initial_cwd = plugin.initial_cwd.map(std::path::PathBuf::from); Ok(crate::input::layout::RunPlugin { _allow_exec_host_cmd: plugin.allow_exec_host_cmd, location, configuration, initial_cwd, }) } } // PluginAlias reverse conversion impl TryFrom for crate::input::layout::PluginAlias { type Error = anyhow::Error; fn try_from( plugin_alias: crate::client_server_contract::client_server_contract::PluginAlias, ) -> Result { let run_plugin = plugin_alias.run_plugin.and_then(|r| r.try_into().ok()); let configuration = plugin_alias.configuration.and_then(|c| c.try_into().ok()); let initial_cwd = plugin_alias.initial_cwd.map(std::path::PathBuf::from); Ok(crate::input::layout::PluginAlias { name: plugin_alias.name, configuration, initial_cwd, run_plugin, }) } } // RunPluginLocation reverse conversion impl TryFrom for crate::input::layout::RunPluginLocation { type Error = anyhow::Error; fn try_from( location: crate::client_server_contract::client_server_contract::RunPluginLocationData, ) -> Result { use crate::client_server_contract::client_server_contract::{ run_plugin_location_data::LocationData, RunPluginLocation as ProtoRunPluginLocation, }; let location_data = location .location_data .ok_or_else(|| anyhow!("RunPluginLocationData missing location_data"))?; match location.location_type { x if x == ProtoRunPluginLocation::File as i32 => { if let LocationData::FilePath(path) = location_data { Ok(crate::input::layout::RunPluginLocation::File( std::path::PathBuf::from(path), )) } else { Err(anyhow!("File location type but wrong data variant")) } }, x if x == ProtoRunPluginLocation::Zellij as i32 => { if let LocationData::ZellijTag(tag) = location_data { Ok(crate::input::layout::RunPluginLocation::Zellij( crate::data::PluginTag::new(tag.tag), )) } else { Err(anyhow!("Zellij location type but wrong data variant")) } }, x if x == ProtoRunPluginLocation::Remote as i32 => { if let LocationData::RemoteUrl(url) = location_data { Ok(crate::input::layout::RunPluginLocation::Remote(url)) } else { Err(anyhow!("Remote location type but wrong data variant")) } }, _ => Err(anyhow!( "Invalid RunPluginLocation type: {}", location.location_type )), } } } // RunPluginOrAlias reverse conversion impl TryFrom for crate::input::layout::RunPluginOrAlias { type Error = anyhow::Error; fn try_from( plugin: crate::client_server_contract::client_server_contract::RunPluginOrAlias, ) -> Result { use crate::client_server_contract::client_server_contract::run_plugin_or_alias::PluginType; let plugin_type = plugin .plugin_type .ok_or_else(|| anyhow!("RunPluginOrAlias missing plugin_type"))?; match plugin_type { PluginType::Plugin(run_plugin) => Ok( crate::input::layout::RunPluginOrAlias::RunPlugin(run_plugin.try_into()?), ), PluginType::Alias(plugin_alias) => Ok(crate::input::layout::RunPluginOrAlias::Alias( plugin_alias.try_into()?, )), } } }