Skip to main content

zellij_utils/plugin_api/
action.rs

1pub use super::generated_api::api::{
2    action::{
3        action::OptionalPayload,
4        command_or_plugin::CommandOrPluginType,
5        pane_run::RunType,
6        run_plugin_location_data::LocationData,
7        run_plugin_or_alias::PluginType,
8        Action as ProtobufAction,
9        ActionName as ProtobufActionName,
10        AreFloatingPanesVisiblePayload,
11        BareKey as ProtobufBareKey,
12        // New layout-related types
13        CommandOrPlugin as ProtobufCommandOrPlugin,
14        DumpScreenPayload,
15        EditFilePayload,
16        FloatingPaneCoordinates as ProtobufFloatingPaneCoordinates,
17        FloatingPaneLayout as ProtobufFloatingPaneLayout,
18        FloatingPlacement as ProtobufFloatingPlacement,
19        GoToTabNamePayload,
20        HideFloatingPanesPayload,
21        IdAndName,
22        InPlaceConfig as ProtobufInPlaceConfig,
23        KeyModifier as ProtobufKeyModifier,
24        KeyWithModifier as ProtobufKeyWithModifier,
25        LaunchOrFocusPluginPayload,
26        LayoutConstraint as ProtobufLayoutConstraint,
27        LayoutConstraintFloatingPair as ProtobufLayoutConstraintFloatingPair,
28        LayoutConstraintTiledPair as ProtobufLayoutConstraintTiledPair,
29        LayoutConstraintWithValue as ProtobufLayoutConstraintWithValue,
30        MouseEventPayload as ProtobufMouseEventPayload,
31        MovePanePayload,
32        MoveTabDirection as ProtobufMoveTabDirection,
33        NameAndValue as ProtobufNameAndValue,
34        NewBlockingPanePayload,
35        NewFloatingPanePayload,
36        NewInPlacePanePayload,
37        NewPanePayload,
38        NewPanePlacement as ProtobufNewPanePlacement,
39        NewPluginPanePayload,
40        NewTabPayload,
41        NewTiledPanePayload,
42        OverrideLayoutPayload,
43        PaneId as ProtobufPaneId,
44        PaneIdAndShouldFloat,
45        PaneRun as ProtobufPaneRun,
46        PercentOrFixed as ProtobufPercentOrFixed,
47        PluginAlias as ProtobufPluginAlias,
48        PluginConfiguration as ProtobufPluginConfiguration,
49        PluginTag as ProtobufPluginTag,
50        PluginUserConfiguration as ProtobufPluginUserConfiguration,
51        Position as ProtobufPosition,
52        RunCommandAction as ProtobufRunCommandAction,
53        RunEditFileAction as ProtobufRunEditFileAction,
54        RunPlugin as ProtobufRunPlugin,
55        RunPluginLocation as ProtobufRunPluginLocation,
56        RunPluginLocationData as ProtobufRunPluginLocationData,
57        RunPluginOrAlias as ProtobufRunPluginOrAlias,
58        ScrollAtPayload,
59        SearchDirection as ProtobufSearchDirection,
60        SearchOption as ProtobufSearchOption,
61        SetPaneFrameStylePayload,
62        ShowFloatingPanesPayload,
63        SplitDirection as ProtobufSplitDirection,
64        SplitSize as ProtobufSplitSize,
65        StackedPlacement as ProtobufStackedPlacement,
66        SwapFloatingLayout as ProtobufSwapFloatingLayout,
67        SwapTiledLayout as ProtobufSwapTiledLayout,
68        SwitchToModePayload,
69        TabIdAndName,
70        TabLayoutInfo as ProtobufTabLayoutInfo,
71        TiledPaneLayout as ProtobufTiledPaneLayout,
72        TiledPlacement as ProtobufTiledPlacement,
73        UnblockCondition as ProtobufUnblockCondition,
74        WriteCharsPayload,
75        WritePayload,
76    },
77    input_mode::InputMode as ProtobufInputMode,
78    pane_frame_style::PaneFrameStyle as ProtobufPaneFrameStyle,
79    resize::{Resize as ProtobufResize, ResizeDirection as ProtobufResizeDirection},
80};
81use crate::data::{
82    CommandOrPlugin, Direction, FloatingPaneCoordinates, InputMode, KeyWithModifier,
83    NewPanePlacement, PaneId, PluginTag, ResizeStrategy, UnblockCondition,
84};
85use crate::errors::prelude::*;
86use crate::input::actions::Action;
87use crate::input::actions::{SearchDirection, SearchOption};
88use crate::input::command::{OpenFilePayload, RunCommandAction};
89use crate::input::layout::SplitSize;
90use crate::input::layout::{
91    FloatingPaneLayout, LayoutConstraint, PercentOrFixed, PluginAlias, PluginUserConfiguration,
92    Run, RunPlugin, RunPluginLocation, RunPluginOrAlias, SplitDirection, SwapFloatingLayout,
93    SwapTiledLayout, TabLayoutInfo, TiledPaneLayout,
94};
95use crate::input::mouse::{MouseEvent, MouseEventType};
96use crate::position::Position;
97
98use std::collections::BTreeMap;
99use std::convert::TryFrom;
100use std::path::PathBuf;
101
102impl TryFrom<ProtobufAction> for Action {
103    type Error = &'static str;
104    fn try_from(protobuf_action: ProtobufAction) -> Result<Self, &'static str> {
105        match ProtobufActionName::try_from(protobuf_action.name).ok() {
106            Some(ProtobufActionName::Quit) => match protobuf_action.optional_payload {
107                Some(_) => Err("The Quit Action should not have a payload"),
108                None => Ok(Action::Quit),
109            },
110            Some(ProtobufActionName::Write) => match protobuf_action.optional_payload {
111                Some(OptionalPayload::WritePayload(write_payload)) => {
112                    let key_with_modifier = write_payload
113                        .key_with_modifier
114                        .and_then(|k| k.try_into().ok());
115                    Ok(Action::Write {
116                        key_with_modifier,
117                        bytes: write_payload.bytes_to_write,
118                        is_kitty_keyboard_protocol: write_payload.is_kitty_keyboard_protocol,
119                    })
120                },
121                _ => Err("Wrong payload for Action::Write"),
122            },
123            Some(ProtobufActionName::WriteChars) => match protobuf_action.optional_payload {
124                Some(OptionalPayload::WriteCharsPayload(write_chars_payload)) => {
125                    Ok(Action::WriteChars {
126                        chars: write_chars_payload.chars,
127                    })
128                },
129                _ => Err("Wrong payload for Action::WriteChars"),
130            },
131            Some(ProtobufActionName::SwitchToMode) => match protobuf_action.optional_payload {
132                Some(OptionalPayload::SwitchToModePayload(switch_to_mode_payload)) => {
133                    let input_mode: InputMode =
134                        ProtobufInputMode::try_from(switch_to_mode_payload.input_mode)
135                            .ok()
136                            .ok_or("Malformed input mode for SwitchToMode Action")?
137                            .try_into()?;
138                    Ok(Action::SwitchToMode { input_mode })
139                },
140                _ => Err("Wrong payload for Action::SwitchToModePayload"),
141            },
142            Some(ProtobufActionName::SwitchModeForAllClients) => {
143                match protobuf_action.optional_payload {
144                    Some(OptionalPayload::SwitchModeForAllClientsPayload(
145                        switch_to_mode_payload,
146                    )) => {
147                        let input_mode: InputMode =
148                            ProtobufInputMode::try_from(switch_to_mode_payload.input_mode)
149                                .ok()
150                                .ok_or("Malformed input mode for SwitchToMode Action")?
151                                .try_into()?;
152                        Ok(Action::SwitchModeForAllClients { input_mode })
153                    },
154                    _ => Err("Wrong payload for Action::SwitchModeForAllClients"),
155                }
156            },
157            Some(ProtobufActionName::Resize) => match protobuf_action.optional_payload {
158                Some(OptionalPayload::ResizePayload(resize_payload)) => {
159                    let resize_strategy: ResizeStrategy = resize_payload.try_into()?;
160                    Ok(Action::Resize {
161                        resize: resize_strategy.resize,
162                        direction: resize_strategy.direction,
163                    })
164                },
165                _ => Err("Wrong payload for Action::Resize"),
166            },
167            Some(ProtobufActionName::FocusNextPane) => match protobuf_action.optional_payload {
168                Some(_) => Err("FocusNextPane should not have a payload"),
169                None => Ok(Action::FocusNextPane),
170            },
171            Some(ProtobufActionName::FocusPreviousPane) => match protobuf_action.optional_payload {
172                Some(_) => Err("FocusPreviousPane should not have a payload"),
173                None => Ok(Action::FocusPreviousPane),
174            },
175            Some(ProtobufActionName::FocusLastPane) => match protobuf_action.optional_payload {
176                Some(_) => Err("FocusLastPane should not have a payload"),
177                None => Ok(Action::FocusLastPane),
178            },
179            Some(ProtobufActionName::SwitchFocus) => match protobuf_action.optional_payload {
180                Some(_) => Err("SwitchFocus should not have a payload"),
181                None => Ok(Action::SwitchFocus),
182            },
183            Some(ProtobufActionName::MoveFocus) => match protobuf_action.optional_payload {
184                Some(OptionalPayload::MoveFocusPayload(move_focus_payload)) => {
185                    let direction: Direction =
186                        ProtobufResizeDirection::try_from(move_focus_payload)
187                            .ok()
188                            .ok_or("Malformed resize direction for Action::MoveFocus")?
189                            .try_into()?;
190                    Ok(Action::MoveFocus { direction })
191                },
192                _ => Err("Wrong payload for Action::MoveFocus"),
193            },
194            Some(ProtobufActionName::MoveFocusOrTab) => match protobuf_action.optional_payload {
195                Some(OptionalPayload::MoveFocusOrTabPayload(move_focus_or_tab_payload)) => {
196                    let direction: Direction =
197                        ProtobufResizeDirection::try_from(move_focus_or_tab_payload)
198                            .ok()
199                            .ok_or("Malformed resize direction for Action::MoveFocusOrTab")?
200                            .try_into()?;
201                    Ok(Action::MoveFocusOrTab { direction })
202                },
203                _ => Err("Wrong payload for Action::MoveFocusOrTab"),
204            },
205            Some(ProtobufActionName::MovePane) => match protobuf_action.optional_payload {
206                Some(OptionalPayload::MovePanePayload(payload)) => {
207                    let direction: Option<Direction> = payload
208                        .direction
209                        .and_then(|d| ProtobufResizeDirection::try_from(d).ok())
210                        .and_then(|d| d.try_into().ok());
211                    Ok(Action::MovePane { direction })
212                },
213                _ => Err("Wrong payload for Action::MovePane"),
214            },
215            Some(ProtobufActionName::MovePaneBackwards) => match protobuf_action.optional_payload {
216                Some(_) => Err("MovePaneBackwards should not have a payload"),
217                None => Ok(Action::MovePaneBackwards),
218            },
219            Some(ProtobufActionName::ClearScreen) => match protobuf_action.optional_payload {
220                Some(_) => Err("ClearScreen should not have a payload"),
221                None => Ok(Action::ClearScreen),
222            },
223            Some(ProtobufActionName::DumpScreen) => match protobuf_action.optional_payload {
224                Some(OptionalPayload::DumpScreenPayload(payload)) => {
225                    let file_path = if payload.dump_to_stdout {
226                        None
227                    } else {
228                        Some(payload.file_path)
229                    };
230                    let include_scrollback = payload.include_scrollback;
231                    let pane_id = payload.pane_id.and_then(|p| p.try_into().ok());
232                    Ok(Action::DumpScreen {
233                        file_path,
234                        include_scrollback,
235                        pane_id,
236                        ansi: payload.ansi,
237                    })
238                },
239                _ => Err("Wrong payload for Action::DumpScreen"),
240            },
241            Some(ProtobufActionName::EditScrollback) => match protobuf_action.optional_payload {
242                Some(_) => Err("EditScrollback should not have a payload"),
243                None => Ok(Action::EditScrollback { ansi: false }),
244            },
245            Some(ProtobufActionName::ScrollUp) => match protobuf_action.optional_payload {
246                Some(_) => Err("ScrollUp should not have a payload"),
247                None => Ok(Action::ScrollUp),
248            },
249            Some(ProtobufActionName::ScrollDown) => match protobuf_action.optional_payload {
250                Some(_) => Err("ScrollDown should not have a payload"),
251                None => Ok(Action::ScrollDown),
252            },
253            Some(ProtobufActionName::ScrollUpAt) => match protobuf_action.optional_payload {
254                Some(OptionalPayload::ScrollUpAtPayload(payload)) => {
255                    let position = payload
256                        .position
257                        .ok_or("ScrollUpAtPayload must have a position")?
258                        .try_into()?;
259                    Ok(Action::ScrollUpAt { position })
260                },
261                _ => Err("Wrong payload for Action::ScrollUpAt"),
262            },
263            Some(ProtobufActionName::ScrollDownAt) => match protobuf_action.optional_payload {
264                Some(OptionalPayload::ScrollDownAtPayload(payload)) => {
265                    let position = payload
266                        .position
267                        .ok_or("ScrollDownAtPayload must have a position")?
268                        .try_into()?;
269                    Ok(Action::ScrollDownAt { position })
270                },
271                _ => Err("Wrong payload for Action::ScrollDownAt"),
272            },
273            Some(ProtobufActionName::ScrollToBottom) => match protobuf_action.optional_payload {
274                Some(_) => Err("ScrollToBottom should not have a payload"),
275                None => Ok(Action::ScrollToBottom),
276            },
277            Some(ProtobufActionName::ScrollToTop) => match protobuf_action.optional_payload {
278                Some(_) => Err("ScrollToTop should not have a payload"),
279                None => Ok(Action::ScrollToTop),
280            },
281            Some(ProtobufActionName::ScrollToPreviousPrompt) => {
282                match protobuf_action.optional_payload {
283                    Some(_) => Err("ScrollToPreviousPrompt should not have a payload"),
284                    None => Ok(Action::ScrollToPreviousPrompt),
285                }
286            },
287            Some(ProtobufActionName::ScrollToNextPrompt) => {
288                match protobuf_action.optional_payload {
289                    Some(_) => Err("ScrollToNextPrompt should not have a payload"),
290                    None => Ok(Action::ScrollToNextPrompt),
291                }
292            },
293            Some(ProtobufActionName::SelectCommandAtScrollPosition) => {
294                match protobuf_action.optional_payload {
295                    Some(_) => Err("SelectCommandAtScrollPosition should not have a payload"),
296                    None => Ok(Action::SelectCommandAtScrollPosition),
297                }
298            },
299            Some(ProtobufActionName::CopyLastCommandOutput) => {
300                match protobuf_action.optional_payload {
301                    Some(_) => Err("CopyLastCommandOutput should not have a payload"),
302                    None => Ok(Action::CopyLastCommandOutput),
303                }
304            },
305            Some(ProtobufActionName::PageScrollUp) => match protobuf_action.optional_payload {
306                Some(_) => Err("PageScrollUp should not have a payload"),
307                None => Ok(Action::PageScrollUp),
308            },
309            Some(ProtobufActionName::PageScrollDown) => match protobuf_action.optional_payload {
310                Some(_) => Err("PageScrollDown should not have a payload"),
311                None => Ok(Action::PageScrollDown),
312            },
313            Some(ProtobufActionName::HalfPageScrollUp) => match protobuf_action.optional_payload {
314                Some(_) => Err("HalfPageScrollUp should not have a payload"),
315                None => Ok(Action::HalfPageScrollUp),
316            },
317            Some(ProtobufActionName::HalfPageScrollDown) => {
318                match protobuf_action.optional_payload {
319                    Some(_) => Err("HalfPageScrollDown should not have a payload"),
320                    None => Ok(Action::HalfPageScrollDown),
321                }
322            },
323            Some(ProtobufActionName::ToggleFocusFullscreen) => {
324                match protobuf_action.optional_payload {
325                    Some(_) => Err("ToggleFocusFullscreen should not have a payload"),
326                    None => Ok(Action::ToggleFocusFullscreen),
327                }
328            },
329            Some(ProtobufActionName::ToggleFocusNoUiFullscreen) => {
330                match protobuf_action.optional_payload {
331                    Some(_) => Err("ToggleFocusNoUiFullscreen should not have a payload"),
332                    None => Ok(Action::ToggleFocusNoUiFullscreen),
333                }
334            },
335            Some(ProtobufActionName::TogglePaneFrames) => match protobuf_action.optional_payload {
336                Some(_) => Err("TogglePaneFrames should not have a payload"),
337                None => Ok(Action::TogglePaneFrames),
338            },
339            Some(ProtobufActionName::SetPaneFrameStyle) => match protobuf_action.optional_payload {
340                Some(OptionalPayload::SetPaneFrameStylePayload(payload)) => {
341                    match ProtobufPaneFrameStyle::try_from(payload.pane_frame_style).ok() {
342                        Some(protobuf_pane_frame_style) => Ok(Action::SetPaneFrameStyle(
343                            protobuf_pane_frame_style.try_into()?,
344                        )),
345                        None => Err("Malformed payload for Action::SetPaneFrameStyle"),
346                    }
347                },
348                _ => Err("Wrong payload for Action::SetPaneFrameStyle"),
349            },
350            Some(ProtobufActionName::ToggleActiveSyncTab) => {
351                match protobuf_action.optional_payload {
352                    Some(_) => Err("ToggleActiveSyncTab should not have a payload"),
353                    None => Ok(Action::ToggleActiveSyncTab),
354                }
355            },
356            Some(ProtobufActionName::NewPane) => match protobuf_action.optional_payload {
357                Some(OptionalPayload::NewPanePayload(payload)) => {
358                    let direction: Option<Direction> = payload
359                        .direction
360                        .and_then(|d| ProtobufResizeDirection::try_from(d).ok())
361                        .and_then(|d| d.try_into().ok());
362                    let pane_name = payload.pane_name;
363                    Ok(Action::NewPane {
364                        direction,
365                        pane_name,
366                        start_suppressed: false,
367                    })
368                },
369                _ => Err("Wrong payload for Action::NewPane"),
370            },
371            Some(ProtobufActionName::EditFile) => match protobuf_action.optional_payload {
372                Some(OptionalPayload::EditFilePayload(payload)) => {
373                    let file_to_edit = PathBuf::from(payload.file_to_edit);
374                    let line_number: Option<usize> = payload.line_number.map(|l| l as usize);
375                    let cwd: Option<PathBuf> = payload.cwd.map(|p| PathBuf::from(p));
376                    let direction: Option<Direction> = payload
377                        .direction
378                        .and_then(|d| ProtobufResizeDirection::try_from(d).ok())
379                        .and_then(|d| d.try_into().ok());
380                    let near_current_pane = payload.near_current_pane;
381                    let should_float = payload.should_float;
382                    let should_be_in_place = false;
383                    Ok(Action::EditFile {
384                        payload: OpenFilePayload::new(file_to_edit, line_number, cwd),
385                        direction,
386                        floating: should_float,
387                        in_place: should_be_in_place,
388                        close_replaced_pane: false,
389                        start_suppressed: false,
390                        coordinates: None,
391                        near_current_pane,
392                        no_focus: false,
393                        tab_id: None,
394                    })
395                },
396                _ => Err("Wrong payload for Action::NewPane"),
397            },
398            Some(ProtobufActionName::NewFloatingPane) => match protobuf_action.optional_payload {
399                Some(OptionalPayload::NewFloatingPanePayload(payload)) => {
400                    let near_current_pane = payload.near_current_pane;
401                    if let Some(payload) = payload.command {
402                        let pane_name = payload.pane_name.clone();
403                        let run_command_action: RunCommandAction = payload.try_into()?;
404                        Ok(Action::NewFloatingPane {
405                            command: Some(run_command_action),
406                            pane_name,
407                            coordinates: None,
408                            near_current_pane,
409                            no_focus: false,
410                            tab_id: None,
411                        })
412                    } else {
413                        Ok(Action::NewFloatingPane {
414                            command: None,
415                            pane_name: None,
416                            coordinates: None,
417                            near_current_pane,
418                            no_focus: false,
419                            tab_id: None,
420                        })
421                    }
422                },
423                _ => Err("Wrong payload for Action::NewFloatingPane"),
424            },
425            Some(ProtobufActionName::NewTiledPane) => match protobuf_action.optional_payload {
426                Some(OptionalPayload::NewTiledPanePayload(payload)) => {
427                    let direction: Option<Direction> = payload
428                        .direction
429                        .and_then(|d| ProtobufResizeDirection::try_from(d).ok())
430                        .and_then(|d| d.try_into().ok());
431                    let near_current_pane = payload.near_current_pane;
432                    let borderless = payload.borderless;
433                    if let Some(payload) = payload.command {
434                        let pane_name = payload.pane_name.clone();
435                        let run_command_action: RunCommandAction = payload.try_into()?;
436                        Ok(Action::NewTiledPane {
437                            direction,
438                            command: Some(run_command_action),
439                            pane_name,
440                            near_current_pane,
441                            no_focus: false,
442                            borderless,
443                            tab_id: None,
444                        })
445                    } else {
446                        Ok(Action::NewTiledPane {
447                            direction,
448                            command: None,
449                            pane_name: None,
450                            near_current_pane,
451                            no_focus: false,
452                            borderless,
453                            tab_id: None,
454                        })
455                    }
456                },
457                _ => Err("Wrong payload for Action::NewTiledPane"),
458            },
459            Some(ProtobufActionName::TogglePaneEmbedOrFloating) => {
460                match protobuf_action.optional_payload {
461                    Some(_) => Err("TogglePaneEmbedOrFloating should not have a payload"),
462                    None => Ok(Action::TogglePaneEmbedOrFloating),
463                }
464            },
465            Some(ProtobufActionName::ToggleFloatingPanes) => {
466                match protobuf_action.optional_payload {
467                    Some(_) => Err("ToggleFloatingPanes should not have a payload"),
468                    None => Ok(Action::ToggleFloatingPanes),
469                }
470            },
471            Some(ProtobufActionName::ShowFloatingPanes) => match protobuf_action.optional_payload {
472                Some(OptionalPayload::ShowFloatingPanesPayload(payload)) => {
473                    Ok(Action::ShowFloatingPanes {
474                        tab_id: payload.tab_id.map(|id| id as usize),
475                    })
476                },
477                None => Ok(Action::ShowFloatingPanes { tab_id: None }),
478                _ => Err("Wrong payload for ShowFloatingPanes"),
479            },
480            Some(ProtobufActionName::HideFloatingPanes) => match protobuf_action.optional_payload {
481                Some(OptionalPayload::HideFloatingPanesPayload(payload)) => {
482                    Ok(Action::HideFloatingPanes {
483                        tab_id: payload.tab_id.map(|id| id as usize),
484                    })
485                },
486                None => Ok(Action::HideFloatingPanes { tab_id: None }),
487                _ => Err("Wrong payload for HideFloatingPanes"),
488            },
489            Some(ProtobufActionName::AreFloatingPanesVisible) => {
490                match protobuf_action.optional_payload {
491                    Some(OptionalPayload::AreFloatingPanesVisiblePayload(payload)) => {
492                        Ok(Action::AreFloatingPanesVisible {
493                            tab_id: payload.tab_id.map(|id| id as usize),
494                        })
495                    },
496                    None => Ok(Action::AreFloatingPanesVisible { tab_id: None }),
497                    _ => Err("Wrong payload for AreFloatingPanesVisible"),
498                }
499            },
500            Some(ProtobufActionName::CloseFocus) => match protobuf_action.optional_payload {
501                Some(_) => Err("CloseFocus should not have a payload"),
502                None => Ok(Action::CloseFocus),
503            },
504            Some(ProtobufActionName::PaneNameInput) => match protobuf_action.optional_payload {
505                Some(OptionalPayload::PaneNameInputPayload(bytes)) => {
506                    Ok(Action::PaneNameInput { input: bytes })
507                },
508                _ => Err("Wrong payload for Action::PaneNameInput"),
509            },
510            Some(ProtobufActionName::UndoRenamePane) => match protobuf_action.optional_payload {
511                Some(_) => Err("UndoRenamePane should not have a payload"),
512                None => Ok(Action::UndoRenamePane),
513            },
514            Some(ProtobufActionName::NewTab) => {
515                match protobuf_action.optional_payload {
516                    Some(OptionalPayload::NewTabPayload(payload)) => {
517                        // New behavior: extract all fields from payload
518                        let tiled_layout =
519                            payload.tiled_layout.map(|l| l.try_into()).transpose()?;
520
521                        let floating_layouts = payload
522                            .floating_layouts
523                            .into_iter()
524                            .map(|l| l.try_into())
525                            .collect::<Result<Vec<_>, _>>()?;
526
527                        let swap_tiled_layouts = if payload.swap_tiled_layouts.is_empty() {
528                            None
529                        } else {
530                            Some(
531                                payload
532                                    .swap_tiled_layouts
533                                    .into_iter()
534                                    .map(|l| l.try_into())
535                                    .collect::<Result<Vec<_>, _>>()?,
536                            )
537                        };
538
539                        let swap_floating_layouts = if payload.swap_floating_layouts.is_empty() {
540                            None
541                        } else {
542                            Some(
543                                payload
544                                    .swap_floating_layouts
545                                    .into_iter()
546                                    .map(|l| l.try_into())
547                                    .collect::<Result<Vec<_>, _>>()?,
548                            )
549                        };
550
551                        let tab_name = payload.tab_name;
552                        let should_change_focus_to_new_tab = payload.should_change_focus_to_new_tab;
553                        let cwd = payload.cwd.map(PathBuf::from);
554
555                        let initial_panes = if payload.initial_panes.is_empty() {
556                            None
557                        } else {
558                            Some(
559                                payload
560                                    .initial_panes
561                                    .into_iter()
562                                    .map(|p| p.try_into())
563                                    .collect::<Result<Vec<_>, _>>()?,
564                            )
565                        };
566
567                        let first_pane_unblock_condition = payload
568                            .first_pane_unblock_condition
569                            .and_then(|uc| ProtobufUnblockCondition::try_from(uc).ok())
570                            .and_then(|uc| uc.try_into().ok());
571
572                        Ok(Action::NewTab {
573                            tiled_layout,
574                            floating_layouts,
575                            swap_tiled_layouts,
576                            swap_floating_layouts,
577                            tab_name,
578                            should_change_focus_to_new_tab,
579                            cwd,
580                            initial_panes,
581                            first_pane_unblock_condition,
582                        })
583                    },
584                    None => {
585                        // Backwards compatibility: accept None payload for existing plugins
586                        // Return the same defaults as before
587                        Ok(Action::NewTab {
588                            tiled_layout: None,
589                            floating_layouts: vec![],
590                            swap_tiled_layouts: None,
591                            swap_floating_layouts: None,
592                            tab_name: None,
593                            should_change_focus_to_new_tab: true,
594                            cwd: None,
595                            initial_panes: None,
596                            first_pane_unblock_condition: None,
597                        })
598                    },
599                    _ => Err("Wrong payload for Action::NewTab"),
600                }
601            },
602            Some(ProtobufActionName::NoOp) => match protobuf_action.optional_payload {
603                Some(_) => Err("NoOp should not have a payload"),
604                None => Ok(Action::NoOp),
605            },
606            Some(ProtobufActionName::GoToNextTab) => match protobuf_action.optional_payload {
607                Some(_) => Err("GoToNextTab should not have a payload"),
608                None => Ok(Action::GoToNextTab),
609            },
610            Some(ProtobufActionName::GoToPreviousTab) => match protobuf_action.optional_payload {
611                Some(_) => Err("GoToPreviousTab should not have a payload"),
612                None => Ok(Action::GoToPreviousTab),
613            },
614            Some(ProtobufActionName::CloseTab) => match protobuf_action.optional_payload {
615                Some(_) => Err("CloseTab should not have a payload"),
616                None => Ok(Action::CloseTab),
617            },
618            Some(ProtobufActionName::GoToTab) => match protobuf_action.optional_payload {
619                Some(OptionalPayload::GoToTabPayload(index)) => Ok(Action::GoToTab { index }),
620                _ => Err("Wrong payload for Action::GoToTab"),
621            },
622            Some(ProtobufActionName::GoToTabName) => match protobuf_action.optional_payload {
623                Some(OptionalPayload::GoToTabNamePayload(payload)) => {
624                    let tab_name = payload.tab_name;
625                    let create = payload.create;
626                    Ok(Action::GoToTabName {
627                        name: tab_name,
628                        create,
629                    })
630                },
631                _ => Err("Wrong payload for Action::GoToTabName"),
632            },
633            Some(ProtobufActionName::ToggleTab) => match protobuf_action.optional_payload {
634                Some(_) => Err("ToggleTab should not have a payload"),
635                None => Ok(Action::ToggleTab),
636            },
637            Some(ProtobufActionName::TabNameInput) => match protobuf_action.optional_payload {
638                Some(OptionalPayload::TabNameInputPayload(bytes)) => {
639                    Ok(Action::TabNameInput { input: bytes })
640                },
641                _ => Err("Wrong payload for Action::TabNameInput"),
642            },
643            Some(ProtobufActionName::UndoRenameTab) => match protobuf_action.optional_payload {
644                Some(_) => Err("UndoRenameTab should not have a payload"),
645                None => Ok(Action::UndoRenameTab),
646            },
647            Some(ProtobufActionName::MoveTab) => match protobuf_action.optional_payload {
648                Some(OptionalPayload::MoveTabPayload(move_tab_payload)) => {
649                    let direction: Direction = ProtobufMoveTabDirection::try_from(move_tab_payload)
650                        .ok()
651                        .ok_or("Malformed move tab direction for Action::MoveTab")?
652                        .try_into()?;
653                    Ok(Action::MoveTab { direction })
654                },
655                _ => Err("Wrong payload for Action::MoveTab"),
656            },
657            Some(ProtobufActionName::Run) => match protobuf_action.optional_payload {
658                Some(OptionalPayload::RunPayload(run_command_action)) => {
659                    let run_command_action = run_command_action.try_into()?;
660                    Ok(Action::Run {
661                        command: run_command_action,
662                        near_current_pane: false,
663                        no_focus: false,
664                    })
665                },
666                _ => Err("Wrong payload for Action::Run"),
667            },
668            Some(ProtobufActionName::Detach) => match protobuf_action.optional_payload {
669                Some(_) => Err("Detach should not have a payload"),
670                None => Ok(Action::Detach),
671            },
672            Some(ProtobufActionName::SetDarkTheme) => match protobuf_action.optional_payload {
673                Some(_) => Err("SetDarkTheme should not have a payload"),
674                None => Ok(Action::SetDarkTheme),
675            },
676            Some(ProtobufActionName::SetLightTheme) => match protobuf_action.optional_payload {
677                Some(_) => Err("SetLightTheme should not have a payload"),
678                None => Ok(Action::SetLightTheme),
679            },
680            Some(ProtobufActionName::ToggleTheme) => match protobuf_action.optional_payload {
681                Some(_) => Err("ToggleTheme should not have a payload"),
682                None => Ok(Action::ToggleTheme),
683            },
684            Some(ProtobufActionName::LeftClick) => match protobuf_action.optional_payload {
685                Some(OptionalPayload::LeftClickPayload(payload)) => {
686                    let position = payload.try_into()?;
687                    Ok(Action::MouseEvent {
688                        event: MouseEvent::new_left_press_event(position),
689                    })
690                },
691                _ => Err("Wrong payload for Action::LeftClick"),
692            },
693            Some(ProtobufActionName::RightClick) => match protobuf_action.optional_payload {
694                Some(OptionalPayload::RightClickPayload(payload)) => {
695                    let position = payload.try_into()?;
696                    Ok(Action::MouseEvent {
697                        event: MouseEvent::new_right_press_event(position),
698                    })
699                },
700                _ => Err("Wrong payload for Action::RightClick"),
701            },
702            Some(ProtobufActionName::MiddleClick) => match protobuf_action.optional_payload {
703                Some(OptionalPayload::MiddleClickPayload(payload)) => {
704                    let position = payload.try_into()?;
705                    Ok(Action::MouseEvent {
706                        event: MouseEvent::new_middle_press_event(position),
707                    })
708                },
709                _ => Err("Wrong payload for Action::MiddleClick"),
710            },
711            Some(ProtobufActionName::LaunchOrFocusPlugin) => {
712                match protobuf_action.optional_payload {
713                    Some(OptionalPayload::LaunchOrFocusPluginPayload(payload)) => {
714                        let configuration: PluginUserConfiguration = payload
715                            .plugin_configuration
716                            .and_then(|p| PluginUserConfiguration::try_from(p).ok())
717                            .unwrap_or_default();
718                        let run_plugin_or_alias = RunPluginOrAlias::from_url(
719                            &payload.plugin_url.as_str(),
720                            &Some(configuration.inner().clone()),
721                            None,
722                            None,
723                        )
724                        .map_err(|_| "Malformed LaunchOrFocusPlugin payload")?;
725                        let should_float = payload.should_float;
726                        let move_to_focused_tab = payload.move_to_focused_tab;
727                        let should_open_in_place = payload.should_open_in_place;
728                        let skip_plugin_cache = payload.skip_plugin_cache;
729                        Ok(Action::LaunchOrFocusPlugin {
730                            plugin: run_plugin_or_alias,
731                            should_float,
732                            move_to_focused_tab,
733                            should_open_in_place,
734                            close_replaced_pane: false,
735                            skip_cache: skip_plugin_cache,
736                            tab_id: None,
737                        })
738                    },
739                    _ => Err("Wrong payload for Action::LaunchOrFocusPlugin"),
740                }
741            },
742            Some(ProtobufActionName::LaunchPlugin) => match protobuf_action.optional_payload {
743                Some(OptionalPayload::LaunchOrFocusPluginPayload(payload)) => {
744                    let configuration: PluginUserConfiguration = payload
745                        .plugin_configuration
746                        .and_then(|p| PluginUserConfiguration::try_from(p).ok())
747                        .unwrap_or_default();
748                    let run_plugin_or_alias = RunPluginOrAlias::from_url(
749                        &payload.plugin_url.as_str(),
750                        &Some(configuration.inner().clone()),
751                        None,
752                        None,
753                    )
754                    .map_err(|_| "Malformed LaunchOrFocusPlugin payload")?;
755                    let should_float = payload.should_float;
756                    let _move_to_focused_tab = payload.move_to_focused_tab; // not actually used in
757                                                                            // this action
758                    let should_open_in_place = payload.should_open_in_place;
759                    let skip_plugin_cache = payload.skip_plugin_cache;
760                    Ok(Action::LaunchPlugin {
761                        plugin: run_plugin_or_alias,
762                        should_float,
763                        should_open_in_place,
764                        close_replaced_pane: false,
765                        skip_cache: skip_plugin_cache,
766                        cwd: None,
767                        no_focus: false,
768                        tab_id: None,
769                    })
770                },
771                _ => Err("Wrong payload for Action::LaunchOrFocusPlugin"),
772            },
773            Some(ProtobufActionName::LeftMouseRelease) => match protobuf_action.optional_payload {
774                Some(OptionalPayload::LeftMouseReleasePayload(payload)) => {
775                    let position = payload.try_into()?;
776                    Ok(Action::MouseEvent {
777                        event: MouseEvent::new_left_release_event(position),
778                    })
779                },
780                _ => Err("Wrong payload for Action::LeftMouseRelease"),
781            },
782            Some(ProtobufActionName::RightMouseRelease) => match protobuf_action.optional_payload {
783                Some(OptionalPayload::RightMouseReleasePayload(payload)) => {
784                    let position = payload.try_into()?;
785                    Ok(Action::MouseEvent {
786                        event: MouseEvent::new_right_release_event(position),
787                    })
788                },
789                _ => Err("Wrong payload for Action::RightMouseRelease"),
790            },
791            Some(ProtobufActionName::MiddleMouseRelease) => {
792                match protobuf_action.optional_payload {
793                    Some(OptionalPayload::MiddleMouseReleasePayload(payload)) => {
794                        let position = payload.try_into()?;
795                        Ok(Action::MouseEvent {
796                            event: MouseEvent::new_middle_release_event(position),
797                        })
798                    },
799                    _ => Err("Wrong payload for Action::MiddleMouseRelease"),
800                }
801            },
802            Some(ProtobufActionName::MouseEvent) => match protobuf_action.optional_payload {
803                Some(OptionalPayload::MouseEventPayload(payload)) => {
804                    let event = payload.try_into()?;
805                    Ok(Action::MouseEvent { event })
806                },
807                _ => Err("Wrong payload for Action::MouseEvent"),
808            },
809            Some(ProtobufActionName::SearchInput) => match protobuf_action.optional_payload {
810                Some(OptionalPayload::SearchInputPayload(payload)) => {
811                    Ok(Action::SearchInput { input: payload })
812                },
813                _ => Err("Wrong payload for Action::SearchInput"),
814            },
815            Some(ProtobufActionName::Search) => match protobuf_action.optional_payload {
816                Some(OptionalPayload::SearchPayload(search_direction)) => Ok(Action::Search {
817                    direction: ProtobufSearchDirection::try_from(search_direction)
818                        .ok()
819                        .ok_or("Malformed payload for Action::Search")?
820                        .try_into()?,
821                }),
822                _ => Err("Wrong payload for Action::Search"),
823            },
824            Some(ProtobufActionName::SearchToggleOption) => {
825                match protobuf_action.optional_payload {
826                    Some(OptionalPayload::SearchToggleOptionPayload(search_option)) => {
827                        Ok(Action::SearchToggleOption {
828                            option: ProtobufSearchOption::try_from(search_option)
829                                .ok()
830                                .ok_or("Malformed payload for Action::SearchToggleOption")?
831                                .try_into()?,
832                        })
833                    },
834                    _ => Err("Wrong payload for Action::SearchToggleOption"),
835                }
836            },
837            Some(ProtobufActionName::ToggleMouseMode) => match protobuf_action.optional_payload {
838                Some(_) => Err("ToggleMouseMode should not have a payload"),
839                None => Ok(Action::ToggleMouseMode),
840            },
841            Some(ProtobufActionName::PreviousSwapLayout) => {
842                match protobuf_action.optional_payload {
843                    Some(_) => Err("PreviousSwapLayout should not have a payload"),
844                    None => Ok(Action::PreviousSwapLayout),
845                }
846            },
847            Some(ProtobufActionName::NextSwapLayout) => match protobuf_action.optional_payload {
848                Some(_) => Err("NextSwapLayout should not have a payload"),
849                None => Ok(Action::NextSwapLayout),
850            },
851            Some(ProtobufActionName::OverrideLayout) => match protobuf_action.optional_payload {
852                Some(OptionalPayload::OverrideLayoutPayload(payload)) => {
853                    Ok(Action::OverrideLayout {
854                        tabs: payload
855                            .tabs
856                            .into_iter()
857                            .map(|t| t.try_into())
858                            .collect::<Result<Vec<_>, _>>()?,
859                        retain_existing_terminal_panes: payload.retain_existing_terminal_panes,
860                        retain_existing_plugin_panes: payload.retain_existing_plugin_panes,
861                        apply_only_to_active_tab: payload.apply_only_to_active_tab,
862                    })
863                },
864                Some(_) => Err("Mismatched payload for OverrideLayout"),
865                None => Err("Missing payload for OverrideLayout"),
866            },
867            Some(ProtobufActionName::QueryTabNames) => match protobuf_action.optional_payload {
868                Some(_) => Err("QueryTabNames should not have a payload"),
869                None => Ok(Action::QueryTabNames),
870            },
871            Some(ProtobufActionName::NewTiledPluginPane) => {
872                match protobuf_action.optional_payload {
873                    Some(OptionalPayload::NewTiledPluginPanePayload(payload)) => {
874                        let run_plugin_location =
875                            RunPluginLocation::parse(&payload.plugin_url, None)
876                                .map_err(|_| "Malformed NewTiledPluginPane payload")?;
877                        let run_plugin = RunPluginOrAlias::RunPlugin(RunPlugin {
878                            location: run_plugin_location,
879                            _allow_exec_host_cmd: false,
880                            configuration: PluginUserConfiguration::default(),
881                            ..Default::default()
882                        });
883                        let pane_name = payload.pane_name;
884                        let skip_plugin_cache = payload.skip_plugin_cache;
885                        Ok(Action::NewTiledPluginPane {
886                            plugin: run_plugin,
887                            pane_name,
888                            skip_cache: skip_plugin_cache,
889                            cwd: None,
890                            no_focus: false,
891                            tab_id: None,
892                        })
893                    },
894                    _ => Err("Wrong payload for Action::NewTiledPluginPane"),
895                }
896            },
897            Some(ProtobufActionName::NewFloatingPluginPane) => {
898                match protobuf_action.optional_payload {
899                    Some(OptionalPayload::NewFloatingPluginPanePayload(payload)) => {
900                        let run_plugin_location =
901                            RunPluginLocation::parse(&payload.plugin_url, None)
902                                .map_err(|_| "Malformed NewTiledPluginPane payload")?;
903                        let run_plugin = RunPluginOrAlias::RunPlugin(RunPlugin {
904                            location: run_plugin_location,
905                            _allow_exec_host_cmd: false,
906                            configuration: PluginUserConfiguration::default(),
907                            ..Default::default()
908                        });
909                        let pane_name = payload.pane_name;
910                        let skip_plugin_cache = payload.skip_plugin_cache;
911                        Ok(Action::NewFloatingPluginPane {
912                            plugin: run_plugin,
913                            pane_name,
914                            skip_cache: skip_plugin_cache,
915                            cwd: None,
916                            coordinates: None,
917                            no_focus: false,
918                            tab_id: None,
919                        })
920                    },
921                    _ => Err("Wrong payload for Action::MiddleClick"),
922                }
923            },
924            Some(ProtobufActionName::StartOrReloadPlugin) => {
925                match protobuf_action.optional_payload {
926                    Some(OptionalPayload::StartOrReloadPluginPayload(payload)) => {
927                        let run_plugin_or_alias =
928                            RunPluginOrAlias::from_url(&payload.as_str(), &None, None, None)
929                                .map_err(|_| "Malformed LaunchOrFocusPlugin payload")?;
930
931                        Ok(Action::StartOrReloadPlugin {
932                            plugin: run_plugin_or_alias,
933                        })
934                    },
935                    _ => Err("Wrong payload for Action::StartOrReloadPlugin"),
936                }
937            },
938            Some(ProtobufActionName::CloseTerminalPane) => match protobuf_action.optional_payload {
939                Some(OptionalPayload::CloseTerminalPanePayload(payload)) => {
940                    Ok(Action::CloseTerminalPane { pane_id: payload })
941                },
942                _ => Err("Wrong payload for Action::CloseTerminalPane"),
943            },
944            Some(ProtobufActionName::ClosePluginPane) => match protobuf_action.optional_payload {
945                Some(OptionalPayload::ClosePluginPanePayload(payload)) => {
946                    Ok(Action::ClosePluginPane { pane_id: payload })
947                },
948                _ => Err("Wrong payload for Action::ClosePluginPane"),
949            },
950            Some(ProtobufActionName::FocusTerminalPaneWithId) => {
951                match protobuf_action.optional_payload {
952                    Some(OptionalPayload::FocusTerminalPaneWithIdPayload(payload)) => {
953                        let terminal_pane_id = payload.pane_id;
954                        let should_float_if_hidden = payload.should_float;
955                        let should_be_in_place_if_hidden = payload.should_be_in_place;
956                        Ok(Action::FocusTerminalPaneWithId {
957                            pane_id: terminal_pane_id,
958                            should_float_if_hidden,
959                            should_be_in_place_if_hidden,
960                        })
961                    },
962                    _ => Err("Wrong payload for Action::FocusTerminalPaneWithId"),
963                }
964            },
965            Some(ProtobufActionName::FocusPluginPaneWithId) => {
966                match protobuf_action.optional_payload {
967                    Some(OptionalPayload::FocusPluginPaneWithIdPayload(payload)) => {
968                        let plugin_pane_id = payload.pane_id;
969                        let should_float_if_hidden = payload.should_float;
970                        let should_be_in_place_if_hidden = payload.should_be_in_place;
971                        Ok(Action::FocusPluginPaneWithId {
972                            pane_id: plugin_pane_id,
973                            should_float_if_hidden,
974                            should_be_in_place_if_hidden,
975                        })
976                    },
977                    _ => Err("Wrong payload for Action::FocusPluginPaneWithId"),
978                }
979            },
980            Some(ProtobufActionName::RenameTerminalPane) => {
981                match protobuf_action.optional_payload {
982                    Some(OptionalPayload::RenameTerminalPanePayload(payload)) => {
983                        let terminal_pane_id = payload.id;
984                        let new_pane_name = payload.name;
985                        Ok(Action::RenameTerminalPane {
986                            pane_id: terminal_pane_id,
987                            name: new_pane_name,
988                        })
989                    },
990                    _ => Err("Wrong payload for Action::RenameTerminalPane"),
991                }
992            },
993            Some(ProtobufActionName::RenamePluginPane) => match protobuf_action.optional_payload {
994                Some(OptionalPayload::RenamePluginPanePayload(payload)) => {
995                    let plugin_pane_id = payload.id;
996                    let new_pane_name = payload.name;
997                    Ok(Action::RenamePluginPane {
998                        pane_id: plugin_pane_id,
999                        name: new_pane_name,
1000                    })
1001                },
1002                _ => Err("Wrong payload for Action::RenamePluginPane"),
1003            },
1004            Some(ProtobufActionName::RenameTab) => match protobuf_action.optional_payload {
1005                Some(OptionalPayload::RenameTabPayload(payload)) => {
1006                    let tab_index = payload.id;
1007                    let new_tab_name = payload.name;
1008                    Ok(Action::RenameTab {
1009                        tab_index,
1010                        name: new_tab_name,
1011                    })
1012                },
1013                _ => Err("Wrong payload for Action::RenameTab"),
1014            },
1015            Some(ProtobufActionName::BreakPane) => match protobuf_action.optional_payload {
1016                Some(_) => Err("BreakPane should not have a payload"),
1017                None => Ok(Action::BreakPane),
1018            },
1019            Some(ProtobufActionName::BreakPaneRight) => match protobuf_action.optional_payload {
1020                Some(_) => Err("BreakPaneRight should not have a payload"),
1021                None => Ok(Action::BreakPaneRight),
1022            },
1023            Some(ProtobufActionName::BreakPaneLeft) => match protobuf_action.optional_payload {
1024                Some(_) => Err("BreakPaneLeft should not have a payload"),
1025                None => Ok(Action::BreakPaneLeft),
1026            },
1027            Some(ProtobufActionName::RenameSession) => match protobuf_action.optional_payload {
1028                Some(OptionalPayload::RenameSessionPayload(name)) => {
1029                    Ok(Action::RenameSession { name })
1030                },
1031                _ => Err("Wrong payload for Action::RenameSession"),
1032            },
1033            Some(ProtobufActionName::TogglePanePinned) => match protobuf_action.optional_payload {
1034                Some(_) => Err("TogglePanePinned should not have a payload"),
1035                None => Ok(Action::TogglePanePinned),
1036            },
1037            Some(ProtobufActionName::TogglePaneInGroup) => match protobuf_action.optional_payload {
1038                Some(_) => Err("TogglePaneInGroup should not have a payload"),
1039                None => Ok(Action::TogglePaneInGroup),
1040            },
1041            Some(ProtobufActionName::ToggleGroupMarking) => {
1042                match protobuf_action.optional_payload {
1043                    Some(_) => Err("ToggleGroupMarking should not have a payload"),
1044                    None => Ok(Action::ToggleGroupMarking),
1045                }
1046            },
1047            Some(ProtobufActionName::KeybindPipe) => match protobuf_action.optional_payload {
1048                Some(_) => Err("KeybindPipe should not have a payload"),
1049                // TODO: at some point we might want to support a payload here
1050                None => Ok(Action::KeybindPipe {
1051                    name: None,
1052                    payload: None,
1053                    args: None,
1054                    plugin: None,
1055                    configuration: None,
1056                    launch_new: false,
1057                    skip_cache: false,
1058                    floating: None,
1059                    in_place: None,
1060                    cwd: None,
1061                    pane_title: None,
1062                    plugin_id: None,
1063                }),
1064            },
1065            Some(ProtobufActionName::NewStackedPane) => match protobuf_action.optional_payload {
1066                Some(_) => Err("NewStackedPane should not have a payload"),
1067                None => Ok(Action::NewStackedPane {
1068                    command: None,
1069                    pane_name: None,
1070                    near_current_pane: false,
1071                    no_focus: false,
1072                    tab_id: None,
1073                }),
1074            },
1075            Some(ProtobufActionName::NewBlockingPane) => match protobuf_action.optional_payload {
1076                Some(OptionalPayload::NewBlockingPanePayload(payload)) => {
1077                    let placement: NewPanePlacement = payload
1078                        .placement
1079                        .ok_or("NewBlockingPanePayload must have a placement")?
1080                        .try_into()?;
1081                    let pane_name = payload.pane_name;
1082                    let command = payload.command.and_then(|c| c.try_into().ok());
1083                    let unblock_condition = payload
1084                        .unblock_condition
1085                        .and_then(|uc| ProtobufUnblockCondition::try_from(uc).ok())
1086                        .and_then(|uc| uc.try_into().ok());
1087                    let near_current_pane = payload.near_current_pane;
1088                    Ok(Action::NewBlockingPane {
1089                        placement,
1090                        pane_name,
1091                        command,
1092                        unblock_condition,
1093                        near_current_pane,
1094                        no_focus: false,
1095                        tab_id: None,
1096                    })
1097                },
1098                _ => Err("Wrong payload for Action::NewBlockingPane"),
1099            },
1100            Some(ProtobufActionName::NewInPlacePane) => match protobuf_action.optional_payload {
1101                Some(OptionalPayload::NewInPlacePanePayload(payload)) => {
1102                    let near_current_pane = payload.near_current_pane;
1103                    let pane_id_to_replace =
1104                        payload.pane_id_to_replace.and_then(|p| p.try_into().ok());
1105                    let close_replaced_pane = payload.close_replace_pane;
1106                    if let Some(command) = payload.command {
1107                        let pane_name = command.pane_name.clone();
1108                        let run_command_action: RunCommandAction = command.try_into()?;
1109                        Ok(Action::NewInPlacePane {
1110                            command: Some(run_command_action),
1111                            pane_name,
1112                            near_current_pane,
1113                            no_focus: false,
1114                            pane_id_to_replace,
1115                            close_replaced_pane,
1116                            tab_id: None,
1117                        })
1118                    } else {
1119                        Ok(Action::NewInPlacePane {
1120                            command: None,
1121                            pane_name: payload.pane_name,
1122                            near_current_pane,
1123                            no_focus: false,
1124                            pane_id_to_replace,
1125                            close_replaced_pane,
1126                            tab_id: None,
1127                        })
1128                    }
1129                },
1130                _ => Err("Wrong payload for Action::NewInPlacePane"),
1131            },
1132            Some(ProtobufActionName::FocusHostSession) => match protobuf_action.optional_payload {
1133                Some(_) => Err("FocusHostSession should not have a payload"),
1134                None => Ok(Action::FocusHostSession),
1135            },
1136            Some(ProtobufActionName::FocusGuestSession) => match protobuf_action.optional_payload {
1137                Some(_) => Err("FocusGuestSession should not have a payload"),
1138                None => Ok(Action::FocusGuestSession),
1139            },
1140            Some(ProtobufActionName::ToggleHostFullscreen) => {
1141                match protobuf_action.optional_payload {
1142                    Some(_) => Err("ToggleHostFullscreen should not have a payload"),
1143                    None => Ok(Action::ToggleHostFullscreen),
1144                }
1145            },
1146            _ => Err("Unknown Action"),
1147        }
1148    }
1149}
1150
1151impl TryFrom<Action> for ProtobufAction {
1152    type Error = &'static str;
1153    fn try_from(action: Action) -> Result<Self, &'static str> {
1154        match action {
1155            Action::Quit => Ok(ProtobufAction {
1156                name: ProtobufActionName::Quit as i32,
1157                optional_payload: None,
1158            }),
1159            Action::Write {
1160                key_with_modifier,
1161                bytes,
1162                is_kitty_keyboard_protocol,
1163            } => {
1164                let protobuf_key_with_modifier = key_with_modifier.and_then(|k| k.try_into().ok());
1165                Ok(ProtobufAction {
1166                    name: ProtobufActionName::Write as i32,
1167                    optional_payload: Some(OptionalPayload::WritePayload(WritePayload {
1168                        key_with_modifier: protobuf_key_with_modifier,
1169                        bytes_to_write: bytes,
1170                        is_kitty_keyboard_protocol,
1171                    })),
1172                })
1173            },
1174            Action::WriteChars {
1175                chars: chars_to_write,
1176            } => Ok(ProtobufAction {
1177                name: ProtobufActionName::WriteChars as i32,
1178                optional_payload: Some(OptionalPayload::WriteCharsPayload(WriteCharsPayload {
1179                    chars: chars_to_write,
1180                })),
1181            }),
1182            Action::WriteToPaneId { .. }
1183            | Action::WriteCharsToPaneId { .. }
1184            | Action::Paste { .. }
1185            | Action::GoToTabById { .. }
1186            | Action::CloseTabById { .. }
1187            | Action::RenameTabById { .. }
1188            | Action::ScrollUpByPaneId { .. }
1189            | Action::ScrollDownByPaneId { .. }
1190            | Action::ScrollToTopByPaneId { .. }
1191            | Action::ScrollToBottomByPaneId { .. }
1192            | Action::PageScrollUpByPaneId { .. }
1193            | Action::PageScrollDownByPaneId { .. }
1194            | Action::HalfPageScrollUpByPaneId { .. }
1195            | Action::HalfPageScrollDownByPaneId { .. }
1196            | Action::ResizeByPaneId { .. }
1197            | Action::MovePaneByPaneId { .. }
1198            | Action::MovePaneBackwardsByPaneId { .. }
1199            | Action::ClearScreenByPaneId { .. }
1200            | Action::EditScrollbackByPaneId { .. }
1201            | Action::ToggleFocusFullscreenByPaneId { .. }
1202            | Action::ToggleFocusNoUiFullscreenByPaneId { .. }
1203            | Action::TogglePaneEmbedOrFloatingByPaneId { .. }
1204            | Action::CloseFocusByPaneId { .. }
1205            | Action::RenamePaneByPaneId { .. }
1206            | Action::UndoRenamePaneByPaneId { .. }
1207            | Action::TogglePanePinnedByPaneId { .. }
1208            | Action::FocusPaneByPaneId { .. }
1209            | Action::UndoRenameTabByTabId { .. }
1210            | Action::ToggleActiveSyncTabByTabId { .. }
1211            | Action::ToggleFloatingPanesByTabId { .. }
1212            | Action::PreviousSwapLayoutByTabId { .. }
1213            | Action::NextSwapLayoutByTabId { .. }
1214            | Action::MoveTabByTabId { .. } => {
1215                Err("These are CLI-only actions, not available in keybindings")
1216            },
1217            Action::SwitchToMode { input_mode } => {
1218                let input_mode: ProtobufInputMode = input_mode.try_into()?;
1219                Ok(ProtobufAction {
1220                    name: ProtobufActionName::SwitchToMode as i32,
1221                    optional_payload: Some(OptionalPayload::SwitchToModePayload(
1222                        SwitchToModePayload {
1223                            input_mode: input_mode as i32,
1224                        },
1225                    )),
1226                })
1227            },
1228            Action::SwitchModeForAllClients { input_mode } => {
1229                let input_mode: ProtobufInputMode = input_mode.try_into()?;
1230                Ok(ProtobufAction {
1231                    name: ProtobufActionName::SwitchModeForAllClients as i32,
1232                    optional_payload: Some(OptionalPayload::SwitchModeForAllClientsPayload(
1233                        SwitchToModePayload {
1234                            input_mode: input_mode as i32,
1235                        },
1236                    )),
1237                })
1238            },
1239            Action::Resize { resize, direction } => {
1240                let mut resize: ProtobufResize = resize.try_into()?;
1241                resize.direction = direction.and_then(|d| {
1242                    let resize_direction: ProtobufResizeDirection = d.try_into().ok()?;
1243                    Some(resize_direction as i32)
1244                });
1245                Ok(ProtobufAction {
1246                    name: ProtobufActionName::Resize as i32,
1247                    optional_payload: Some(OptionalPayload::ResizePayload(resize)),
1248                })
1249            },
1250            Action::FocusNextPane => Ok(ProtobufAction {
1251                name: ProtobufActionName::FocusNextPane as i32,
1252                optional_payload: None,
1253            }),
1254            Action::FocusPreviousPane => Ok(ProtobufAction {
1255                name: ProtobufActionName::FocusPreviousPane as i32,
1256                optional_payload: None,
1257            }),
1258            Action::FocusLastPane => Ok(ProtobufAction {
1259                name: ProtobufActionName::FocusLastPane as i32,
1260                optional_payload: None,
1261            }),
1262            Action::SwitchFocus => Ok(ProtobufAction {
1263                name: ProtobufActionName::SwitchFocus as i32,
1264                optional_payload: None,
1265            }),
1266            Action::MoveFocus { direction } => {
1267                let direction: ProtobufResizeDirection = direction.try_into()?;
1268                Ok(ProtobufAction {
1269                    name: ProtobufActionName::MoveFocus as i32,
1270                    optional_payload: Some(OptionalPayload::MoveFocusPayload(direction as i32)),
1271                })
1272            },
1273            Action::MoveFocusOrTab { direction } => {
1274                let direction: ProtobufResizeDirection = direction.try_into()?;
1275                Ok(ProtobufAction {
1276                    name: ProtobufActionName::MoveFocusOrTab as i32,
1277                    optional_payload: Some(OptionalPayload::MoveFocusOrTabPayload(
1278                        direction as i32,
1279                    )),
1280                })
1281            },
1282            Action::MovePane { direction } => {
1283                let direction = direction.and_then(|direction| {
1284                    let protobuf_direction: ProtobufResizeDirection = direction.try_into().ok()?;
1285                    Some(protobuf_direction as i32)
1286                });
1287                Ok(ProtobufAction {
1288                    name: ProtobufActionName::MovePane as i32,
1289                    optional_payload: Some(OptionalPayload::MovePanePayload(MovePanePayload {
1290                        direction,
1291                    })),
1292                })
1293            },
1294            Action::MovePaneBackwards => Ok(ProtobufAction {
1295                name: ProtobufActionName::MovePaneBackwards as i32,
1296                optional_payload: None,
1297            }),
1298            Action::ClearScreen => Ok(ProtobufAction {
1299                name: ProtobufActionName::ClearScreen as i32,
1300                optional_payload: None,
1301            }),
1302            Action::DumpScreen {
1303                file_path,
1304                include_scrollback,
1305                pane_id,
1306                ansi,
1307            } => {
1308                let dump_to_stdout = file_path.is_none();
1309                Ok(ProtobufAction {
1310                    name: ProtobufActionName::DumpScreen as i32,
1311                    optional_payload: Some(OptionalPayload::DumpScreenPayload(DumpScreenPayload {
1312                        file_path: file_path.unwrap_or_default(),
1313                        include_scrollback,
1314                        pane_id: pane_id.and_then(|p| p.try_into().ok()),
1315                        dump_to_stdout,
1316                        ansi,
1317                    })),
1318                })
1319            },
1320            Action::EditScrollback { .. } => Ok(ProtobufAction {
1321                name: ProtobufActionName::EditScrollback as i32,
1322                optional_payload: None,
1323            }),
1324            Action::ScrollUp => Ok(ProtobufAction {
1325                name: ProtobufActionName::ScrollUp as i32,
1326                optional_payload: None,
1327            }),
1328            Action::ScrollToPreviousPrompt => Ok(ProtobufAction {
1329                name: ProtobufActionName::ScrollToPreviousPrompt as i32,
1330                optional_payload: None,
1331            }),
1332            Action::ScrollToNextPrompt => Ok(ProtobufAction {
1333                name: ProtobufActionName::ScrollToNextPrompt as i32,
1334                optional_payload: None,
1335            }),
1336            Action::SelectCommandAtScrollPosition => Ok(ProtobufAction {
1337                name: ProtobufActionName::SelectCommandAtScrollPosition as i32,
1338                optional_payload: None,
1339            }),
1340            Action::CopyLastCommandOutput => Ok(ProtobufAction {
1341                name: ProtobufActionName::CopyLastCommandOutput as i32,
1342                optional_payload: None,
1343            }),
1344            Action::ScrollUpAt { position } => {
1345                let position: ProtobufPosition = position.try_into()?;
1346                Ok(ProtobufAction {
1347                    name: ProtobufActionName::ScrollUpAt as i32,
1348                    optional_payload: Some(OptionalPayload::ScrollUpAtPayload(ScrollAtPayload {
1349                        position: Some(position),
1350                    })),
1351                })
1352            },
1353            Action::ScrollDown => Ok(ProtobufAction {
1354                name: ProtobufActionName::ScrollDown as i32,
1355                optional_payload: None,
1356            }),
1357            Action::ScrollDownAt { position } => {
1358                let position: ProtobufPosition = position.try_into()?;
1359                Ok(ProtobufAction {
1360                    name: ProtobufActionName::ScrollDownAt as i32,
1361                    optional_payload: Some(OptionalPayload::ScrollDownAtPayload(ScrollAtPayload {
1362                        position: Some(position),
1363                    })),
1364                })
1365            },
1366            Action::ScrollToBottom => Ok(ProtobufAction {
1367                name: ProtobufActionName::ScrollToBottom as i32,
1368                optional_payload: None,
1369            }),
1370            Action::ScrollToTop => Ok(ProtobufAction {
1371                name: ProtobufActionName::ScrollToTop as i32,
1372                optional_payload: None,
1373            }),
1374            Action::PageScrollUp => Ok(ProtobufAction {
1375                name: ProtobufActionName::PageScrollUp as i32,
1376                optional_payload: None,
1377            }),
1378            Action::PageScrollDown => Ok(ProtobufAction {
1379                name: ProtobufActionName::PageScrollDown as i32,
1380                optional_payload: None,
1381            }),
1382            Action::HalfPageScrollUp => Ok(ProtobufAction {
1383                name: ProtobufActionName::HalfPageScrollUp as i32,
1384                optional_payload: None,
1385            }),
1386            Action::HalfPageScrollDown => Ok(ProtobufAction {
1387                name: ProtobufActionName::HalfPageScrollDown as i32,
1388                optional_payload: None,
1389            }),
1390            Action::ToggleFocusFullscreen => Ok(ProtobufAction {
1391                name: ProtobufActionName::ToggleFocusFullscreen as i32,
1392                optional_payload: None,
1393            }),
1394            Action::ToggleFocusNoUiFullscreen => Ok(ProtobufAction {
1395                name: ProtobufActionName::ToggleFocusNoUiFullscreen as i32,
1396                optional_payload: None,
1397            }),
1398            Action::TogglePaneFrames => Ok(ProtobufAction {
1399                name: ProtobufActionName::TogglePaneFrames as i32,
1400                optional_payload: None,
1401            }),
1402            Action::SetPaneFrameStyle(pane_frame_style) => Ok(ProtobufAction {
1403                name: ProtobufActionName::SetPaneFrameStyle as i32,
1404                optional_payload: Some(OptionalPayload::SetPaneFrameStylePayload(
1405                    SetPaneFrameStylePayload {
1406                        pane_frame_style: ProtobufPaneFrameStyle::try_from(pane_frame_style)?
1407                            as i32,
1408                    },
1409                )),
1410            }),
1411            Action::ToggleActiveSyncTab => Ok(ProtobufAction {
1412                name: ProtobufActionName::ToggleActiveSyncTab as i32,
1413                optional_payload: None,
1414            }),
1415            Action::NewPane {
1416                direction,
1417                pane_name: new_pane_name,
1418                start_suppressed: _start_suppressed,
1419            } => {
1420                let direction = direction.and_then(|direction| {
1421                    let protobuf_direction: ProtobufResizeDirection = direction.try_into().ok()?;
1422                    Some(protobuf_direction as i32)
1423                });
1424                Ok(ProtobufAction {
1425                    name: ProtobufActionName::NewPane as i32,
1426                    optional_payload: Some(OptionalPayload::NewPanePayload(NewPanePayload {
1427                        direction,
1428                        pane_name: new_pane_name,
1429                    })),
1430                })
1431            },
1432            Action::EditFile {
1433                payload: open_file_payload,
1434                direction,
1435                floating: should_float,
1436                in_place: _should_be_in_place,
1437                close_replaced_pane: _close_replaced_pane,
1438                start_suppressed: _start_suppressed,
1439                coordinates: _floating_pane_coordinates,
1440                near_current_pane,
1441                ..
1442            } => {
1443                let file_to_edit = open_file_payload.path.display().to_string();
1444                let cwd = open_file_payload.cwd.map(|cwd| cwd.display().to_string());
1445                let direction: Option<i32> = direction
1446                    .and_then(|d| ProtobufResizeDirection::try_from(d).ok())
1447                    .map(|d| d as i32);
1448                let line_number = open_file_payload.line_number.map(|l| l as u32);
1449                Ok(ProtobufAction {
1450                    name: ProtobufActionName::EditFile as i32,
1451                    optional_payload: Some(OptionalPayload::EditFilePayload(EditFilePayload {
1452                        file_to_edit,
1453                        line_number,
1454                        should_float,
1455                        direction,
1456                        cwd,
1457                        near_current_pane,
1458                    })),
1459                })
1460            },
1461            Action::NewFloatingPane {
1462                command: run_command_action,
1463                pane_name,
1464                coordinates: _coordinates,
1465                near_current_pane,
1466                ..
1467            } => {
1468                let command = run_command_action.and_then(|r| {
1469                    let mut protobuf_run_command_action: ProtobufRunCommandAction =
1470                        r.try_into().ok()?;
1471                    protobuf_run_command_action.pane_name = pane_name;
1472                    Some(protobuf_run_command_action)
1473                });
1474                Ok(ProtobufAction {
1475                    name: ProtobufActionName::NewFloatingPane as i32,
1476                    optional_payload: Some(OptionalPayload::NewFloatingPanePayload(
1477                        NewFloatingPanePayload {
1478                            command,
1479                            near_current_pane,
1480                        },
1481                    )),
1482                })
1483            },
1484            Action::NewTiledPane {
1485                direction,
1486                command: run_command_action,
1487                pane_name,
1488                near_current_pane,
1489                borderless,
1490                ..
1491            } => {
1492                let direction = direction.and_then(|direction| {
1493                    let protobuf_direction: ProtobufResizeDirection = direction.try_into().ok()?;
1494                    Some(protobuf_direction as i32)
1495                });
1496                let command = run_command_action.and_then(|r| {
1497                    let mut protobuf_run_command_action: ProtobufRunCommandAction =
1498                        r.try_into().ok()?;
1499                    let pane_name = pane_name.and_then(|n| n.try_into().ok());
1500                    protobuf_run_command_action.pane_name = pane_name;
1501                    Some(protobuf_run_command_action)
1502                });
1503                Ok(ProtobufAction {
1504                    name: ProtobufActionName::NewTiledPane as i32,
1505                    optional_payload: Some(OptionalPayload::NewTiledPanePayload(
1506                        NewTiledPanePayload {
1507                            direction,
1508                            command,
1509                            near_current_pane,
1510                            borderless,
1511                        },
1512                    )),
1513                })
1514            },
1515            Action::TogglePaneEmbedOrFloating => Ok(ProtobufAction {
1516                name: ProtobufActionName::TogglePaneEmbedOrFloating as i32,
1517                optional_payload: None,
1518            }),
1519            Action::ToggleFloatingPanes => Ok(ProtobufAction {
1520                name: ProtobufActionName::ToggleFloatingPanes as i32,
1521                optional_payload: None,
1522            }),
1523            Action::ShowFloatingPanes { tab_id } => Ok(ProtobufAction {
1524                name: ProtobufActionName::ShowFloatingPanes as i32,
1525                optional_payload: Some(OptionalPayload::ShowFloatingPanesPayload(
1526                    ShowFloatingPanesPayload {
1527                        tab_id: tab_id.map(|id| id as u32),
1528                    },
1529                )),
1530            }),
1531            Action::HideFloatingPanes { tab_id } => Ok(ProtobufAction {
1532                name: ProtobufActionName::HideFloatingPanes as i32,
1533                optional_payload: Some(OptionalPayload::HideFloatingPanesPayload(
1534                    HideFloatingPanesPayload {
1535                        tab_id: tab_id.map(|id| id as u32),
1536                    },
1537                )),
1538            }),
1539            Action::AreFloatingPanesVisible { tab_id } => Ok(ProtobufAction {
1540                name: ProtobufActionName::AreFloatingPanesVisible as i32,
1541                optional_payload: Some(OptionalPayload::AreFloatingPanesVisiblePayload(
1542                    AreFloatingPanesVisiblePayload {
1543                        tab_id: tab_id.map(|id| id as u32),
1544                    },
1545                )),
1546            }),
1547            Action::CloseFocus => Ok(ProtobufAction {
1548                name: ProtobufActionName::CloseFocus as i32,
1549                optional_payload: None,
1550            }),
1551            Action::PaneNameInput { input: bytes } => Ok(ProtobufAction {
1552                name: ProtobufActionName::PaneNameInput as i32,
1553                optional_payload: Some(OptionalPayload::PaneNameInputPayload(bytes)),
1554            }),
1555            Action::UndoRenamePane => Ok(ProtobufAction {
1556                name: ProtobufActionName::UndoRenamePane as i32,
1557                optional_payload: None,
1558            }),
1559            Action::NewTab {
1560                tiled_layout,
1561                floating_layouts,
1562                swap_tiled_layouts,
1563                swap_floating_layouts,
1564                tab_name,
1565                should_change_focus_to_new_tab,
1566                cwd,
1567                initial_panes,
1568                first_pane_unblock_condition,
1569            } => {
1570                // Always send payload (even if all fields are default)
1571                let protobuf_tiled_layout = tiled_layout
1572                    .as_ref()
1573                    .map(|l| l.clone().try_into())
1574                    .transpose()?;
1575
1576                let protobuf_floating_layouts = floating_layouts
1577                    .iter()
1578                    .map(|l| l.clone().try_into())
1579                    .collect::<Result<Vec<_>, _>>()?;
1580
1581                let protobuf_swap_tiled_layouts = swap_tiled_layouts
1582                    .as_ref()
1583                    .map(|layouts| {
1584                        layouts
1585                            .iter()
1586                            .map(|l| l.clone().try_into())
1587                            .collect::<Result<Vec<_>, _>>()
1588                    })
1589                    .transpose()?
1590                    .unwrap_or_default();
1591
1592                let protobuf_swap_floating_layouts = swap_floating_layouts
1593                    .as_ref()
1594                    .map(|layouts| {
1595                        layouts
1596                            .iter()
1597                            .map(|l| l.clone().try_into())
1598                            .collect::<Result<Vec<_>, _>>()
1599                    })
1600                    .transpose()?
1601                    .unwrap_or_default();
1602
1603                let cwd_string = cwd.as_ref().map(|p| p.display().to_string());
1604
1605                let protobuf_initial_panes = initial_panes
1606                    .as_ref()
1607                    .map(|panes| {
1608                        panes
1609                            .iter()
1610                            .map(|p| p.clone().try_into())
1611                            .collect::<Result<Vec<_>, _>>()
1612                    })
1613                    .transpose()?
1614                    .unwrap_or_default();
1615
1616                let protobuf_first_pane_unblock_condition = first_pane_unblock_condition
1617                    .map(|uc| {
1618                        let protobuf_uc: ProtobufUnblockCondition = uc.try_into().ok()?;
1619                        Some(protobuf_uc as i32)
1620                    })
1621                    .flatten();
1622
1623                Ok(ProtobufAction {
1624                    name: ProtobufActionName::NewTab as i32,
1625                    optional_payload: Some(OptionalPayload::NewTabPayload(NewTabPayload {
1626                        tiled_layout: protobuf_tiled_layout,
1627                        floating_layouts: protobuf_floating_layouts,
1628                        swap_tiled_layouts: protobuf_swap_tiled_layouts,
1629                        swap_floating_layouts: protobuf_swap_floating_layouts,
1630                        tab_name: tab_name.clone(),
1631                        should_change_focus_to_new_tab,
1632                        cwd: cwd_string,
1633                        initial_panes: protobuf_initial_panes,
1634                        first_pane_unblock_condition: protobuf_first_pane_unblock_condition,
1635                    })),
1636                })
1637            },
1638            Action::GoToNextTab => Ok(ProtobufAction {
1639                name: ProtobufActionName::GoToNextTab as i32,
1640                optional_payload: None,
1641            }),
1642            Action::GoToPreviousTab => Ok(ProtobufAction {
1643                name: ProtobufActionName::GoToPreviousTab as i32,
1644                optional_payload: None,
1645            }),
1646            Action::CloseTab => Ok(ProtobufAction {
1647                name: ProtobufActionName::CloseTab as i32,
1648                optional_payload: None,
1649            }),
1650            Action::GoToTab { index: tab_index } => Ok(ProtobufAction {
1651                name: ProtobufActionName::GoToTab as i32,
1652                optional_payload: Some(OptionalPayload::GoToTabPayload(tab_index)),
1653            }),
1654            Action::GoToTabName {
1655                name: tab_name,
1656                create,
1657            } => Ok(ProtobufAction {
1658                name: ProtobufActionName::GoToTabName as i32,
1659                optional_payload: Some(OptionalPayload::GoToTabNamePayload(GoToTabNamePayload {
1660                    tab_name,
1661                    create,
1662                })),
1663            }),
1664            Action::ToggleTab => Ok(ProtobufAction {
1665                name: ProtobufActionName::ToggleTab as i32,
1666                optional_payload: None,
1667            }),
1668            Action::TabNameInput { input: bytes } => Ok(ProtobufAction {
1669                name: ProtobufActionName::TabNameInput as i32,
1670                optional_payload: Some(OptionalPayload::TabNameInputPayload(bytes)),
1671            }),
1672            Action::UndoRenameTab => Ok(ProtobufAction {
1673                name: ProtobufActionName::UndoRenameTab as i32,
1674                optional_payload: None,
1675            }),
1676            Action::MoveTab { direction } => {
1677                let direction: ProtobufMoveTabDirection = direction.try_into()?;
1678                Ok(ProtobufAction {
1679                    name: ProtobufActionName::MoveTab as i32,
1680                    optional_payload: Some(OptionalPayload::MoveTabPayload(direction as i32)),
1681                })
1682            },
1683            Action::Run {
1684                command: run_command_action,
1685                near_current_pane: _,
1686                no_focus: _,
1687            } => {
1688                let run_command_action: ProtobufRunCommandAction = run_command_action.try_into()?;
1689                Ok(ProtobufAction {
1690                    name: ProtobufActionName::Run as i32,
1691                    optional_payload: Some(OptionalPayload::RunPayload(run_command_action)),
1692                })
1693            },
1694            Action::Detach => Ok(ProtobufAction {
1695                name: ProtobufActionName::Detach as i32,
1696                optional_payload: None,
1697            }),
1698            Action::SetDarkTheme => Ok(ProtobufAction {
1699                name: ProtobufActionName::SetDarkTheme as i32,
1700                optional_payload: None,
1701            }),
1702            Action::SetLightTheme => Ok(ProtobufAction {
1703                name: ProtobufActionName::SetLightTheme as i32,
1704                optional_payload: None,
1705            }),
1706            Action::ToggleTheme => Ok(ProtobufAction {
1707                name: ProtobufActionName::ToggleTheme as i32,
1708                optional_payload: None,
1709            }),
1710            Action::LaunchOrFocusPlugin {
1711                plugin: run_plugin_or_alias,
1712                should_float,
1713                move_to_focused_tab,
1714                should_open_in_place,
1715                close_replaced_pane: _close_replaced_pane,
1716                skip_cache: skip_plugin_cache,
1717                ..
1718            } => {
1719                let configuration = run_plugin_or_alias.get_configuration().unwrap_or_default();
1720                Ok(ProtobufAction {
1721                    name: ProtobufActionName::LaunchOrFocusPlugin as i32,
1722                    optional_payload: Some(OptionalPayload::LaunchOrFocusPluginPayload(
1723                        LaunchOrFocusPluginPayload {
1724                            plugin_url: run_plugin_or_alias.location_string(),
1725                            should_float,
1726                            move_to_focused_tab,
1727                            should_open_in_place,
1728                            plugin_configuration: Some(configuration.try_into()?),
1729                            skip_plugin_cache,
1730                        },
1731                    )),
1732                })
1733            },
1734            Action::LaunchPlugin {
1735                plugin: run_plugin_or_alias,
1736                should_float,
1737                should_open_in_place,
1738                close_replaced_pane: _close_replaced_pane,
1739                skip_cache: skip_plugin_cache,
1740                cwd: _cwd,
1741                ..
1742            } => {
1743                let configuration = run_plugin_or_alias.get_configuration().unwrap_or_default();
1744                Ok(ProtobufAction {
1745                    name: ProtobufActionName::LaunchPlugin as i32,
1746                    optional_payload: Some(OptionalPayload::LaunchOrFocusPluginPayload(
1747                        LaunchOrFocusPluginPayload {
1748                            plugin_url: run_plugin_or_alias.location_string(),
1749                            should_float,
1750                            move_to_focused_tab: false,
1751                            should_open_in_place,
1752                            plugin_configuration: Some(configuration.try_into()?),
1753                            skip_plugin_cache,
1754                        },
1755                    )),
1756                })
1757            },
1758            Action::MouseEvent { event } => {
1759                let payload: ProtobufMouseEventPayload = event.try_into()?;
1760                Ok(ProtobufAction {
1761                    name: ProtobufActionName::MouseEvent as i32,
1762                    optional_payload: Some(OptionalPayload::MouseEventPayload(payload)),
1763                })
1764            },
1765            Action::SearchInput { input: bytes } => Ok(ProtobufAction {
1766                name: ProtobufActionName::SearchInput as i32,
1767                optional_payload: Some(OptionalPayload::SearchInputPayload(bytes)),
1768            }),
1769            Action::Search {
1770                direction: search_direction,
1771            } => {
1772                let search_direction: ProtobufSearchDirection = search_direction.try_into()?;
1773                Ok(ProtobufAction {
1774                    name: ProtobufActionName::Search as i32,
1775                    optional_payload: Some(OptionalPayload::SearchPayload(search_direction as i32)),
1776                })
1777            },
1778            Action::SearchToggleOption {
1779                option: search_option,
1780            } => {
1781                let search_option: ProtobufSearchOption = search_option.try_into()?;
1782                Ok(ProtobufAction {
1783                    name: ProtobufActionName::SearchToggleOption as i32,
1784                    optional_payload: Some(OptionalPayload::SearchToggleOptionPayload(
1785                        search_option as i32,
1786                    )),
1787                })
1788            },
1789            Action::ToggleMouseMode => Ok(ProtobufAction {
1790                name: ProtobufActionName::ToggleMouseMode as i32,
1791                optional_payload: None,
1792            }),
1793            Action::PreviousSwapLayout => Ok(ProtobufAction {
1794                name: ProtobufActionName::PreviousSwapLayout as i32,
1795                optional_payload: None,
1796            }),
1797            Action::NextSwapLayout => Ok(ProtobufAction {
1798                name: ProtobufActionName::NextSwapLayout as i32,
1799                optional_payload: None,
1800            }),
1801            Action::OverrideLayout {
1802                tabs,
1803                retain_existing_terminal_panes,
1804                retain_existing_plugin_panes,
1805                apply_only_to_active_tab,
1806            } => Ok(ProtobufAction {
1807                name: ProtobufActionName::OverrideLayout as i32,
1808                optional_payload: Some(OptionalPayload::OverrideLayoutPayload(
1809                    OverrideLayoutPayload {
1810                        tabs: tabs
1811                            .into_iter()
1812                            .map(|t| t.try_into())
1813                            .collect::<Result<Vec<_>, _>>()?,
1814                        retain_existing_terminal_panes,
1815                        retain_existing_plugin_panes,
1816                        apply_only_to_active_tab,
1817                    },
1818                )),
1819            }),
1820            Action::QueryTabNames => Ok(ProtobufAction {
1821                name: ProtobufActionName::QueryTabNames as i32,
1822                optional_payload: None,
1823            }),
1824            Action::NewTiledPluginPane {
1825                plugin: run_plugin,
1826                pane_name,
1827                skip_cache: skip_plugin_cache,
1828                cwd: _cwd,
1829                ..
1830            } => Ok(ProtobufAction {
1831                name: ProtobufActionName::NewTiledPluginPane as i32,
1832                optional_payload: Some(OptionalPayload::NewTiledPluginPanePayload(
1833                    NewPluginPanePayload {
1834                        plugin_url: run_plugin.location_string(),
1835                        pane_name,
1836                        skip_plugin_cache,
1837                    },
1838                )),
1839            }),
1840            Action::NewFloatingPluginPane {
1841                plugin: run_plugin,
1842                pane_name,
1843                skip_cache: skip_plugin_cache,
1844                cwd: _cwd,
1845                coordinates: _coordinates,
1846                ..
1847            } => Ok(ProtobufAction {
1848                name: ProtobufActionName::NewFloatingPluginPane as i32,
1849                optional_payload: Some(OptionalPayload::NewFloatingPluginPanePayload(
1850                    NewPluginPanePayload {
1851                        plugin_url: run_plugin.location_string(),
1852                        pane_name,
1853                        skip_plugin_cache,
1854                    },
1855                )),
1856            }),
1857            Action::StartOrReloadPlugin { plugin: run_plugin } => Ok(ProtobufAction {
1858                name: ProtobufActionName::StartOrReloadPlugin as i32,
1859                optional_payload: Some(OptionalPayload::StartOrReloadPluginPayload(
1860                    run_plugin.location_string(),
1861                )),
1862            }),
1863            Action::CloseTerminalPane {
1864                pane_id: terminal_pane_id,
1865            } => Ok(ProtobufAction {
1866                name: ProtobufActionName::CloseTerminalPane as i32,
1867                optional_payload: Some(OptionalPayload::CloseTerminalPanePayload(terminal_pane_id)),
1868            }),
1869            Action::ClosePluginPane {
1870                pane_id: plugin_pane_id,
1871            } => Ok(ProtobufAction {
1872                name: ProtobufActionName::ClosePluginPane as i32,
1873                optional_payload: Some(OptionalPayload::ClosePluginPanePayload(plugin_pane_id)),
1874            }),
1875            Action::FocusTerminalPaneWithId {
1876                pane_id: terminal_pane_id,
1877                should_float_if_hidden,
1878                should_be_in_place_if_hidden,
1879            } => Ok(ProtobufAction {
1880                name: ProtobufActionName::FocusTerminalPaneWithId as i32,
1881                optional_payload: Some(OptionalPayload::FocusTerminalPaneWithIdPayload(
1882                    PaneIdAndShouldFloat {
1883                        pane_id: terminal_pane_id,
1884                        should_float: should_float_if_hidden,
1885                        should_be_in_place: should_be_in_place_if_hidden,
1886                    },
1887                )),
1888            }),
1889            Action::FocusPluginPaneWithId {
1890                pane_id: plugin_pane_id,
1891                should_float_if_hidden,
1892                should_be_in_place_if_hidden,
1893            } => Ok(ProtobufAction {
1894                name: ProtobufActionName::FocusPluginPaneWithId as i32,
1895                optional_payload: Some(OptionalPayload::FocusPluginPaneWithIdPayload(
1896                    PaneIdAndShouldFloat {
1897                        pane_id: plugin_pane_id,
1898                        should_float: should_float_if_hidden,
1899                        should_be_in_place: should_be_in_place_if_hidden,
1900                    },
1901                )),
1902            }),
1903            Action::RenameTerminalPane {
1904                pane_id: terminal_pane_id,
1905                name: new_name,
1906            } => Ok(ProtobufAction {
1907                name: ProtobufActionName::RenameTerminalPane as i32,
1908                optional_payload: Some(OptionalPayload::RenameTerminalPanePayload(IdAndName {
1909                    name: new_name,
1910                    id: terminal_pane_id,
1911                })),
1912            }),
1913            Action::RenamePluginPane {
1914                pane_id: plugin_pane_id,
1915                name: new_name,
1916            } => Ok(ProtobufAction {
1917                name: ProtobufActionName::RenamePluginPane as i32,
1918                optional_payload: Some(OptionalPayload::RenamePluginPanePayload(IdAndName {
1919                    name: new_name,
1920                    id: plugin_pane_id,
1921                })),
1922            }),
1923            Action::RenameTab {
1924                tab_index,
1925                name: new_name,
1926            } => Ok(ProtobufAction {
1927                name: ProtobufActionName::RenameTab as i32,
1928                optional_payload: Some(OptionalPayload::RenameTabPayload(IdAndName {
1929                    name: new_name,
1930                    id: tab_index,
1931                })),
1932            }),
1933            Action::BreakPane => Ok(ProtobufAction {
1934                name: ProtobufActionName::BreakPane as i32,
1935                optional_payload: None,
1936            }),
1937            Action::BreakPaneRight => Ok(ProtobufAction {
1938                name: ProtobufActionName::BreakPaneRight as i32,
1939                optional_payload: None,
1940            }),
1941            Action::BreakPaneLeft => Ok(ProtobufAction {
1942                name: ProtobufActionName::BreakPaneLeft as i32,
1943                optional_payload: None,
1944            }),
1945            Action::RenameSession { name: session_name } => Ok(ProtobufAction {
1946                name: ProtobufActionName::RenameSession as i32,
1947                optional_payload: Some(OptionalPayload::RenameSessionPayload(session_name)),
1948            }),
1949            Action::KeybindPipe { .. } => Ok(ProtobufAction {
1950                name: ProtobufActionName::KeybindPipe as i32,
1951                optional_payload: None,
1952            }),
1953            Action::TogglePanePinned { .. } => Ok(ProtobufAction {
1954                name: ProtobufActionName::TogglePanePinned as i32,
1955                optional_payload: None,
1956            }),
1957            Action::TogglePaneInGroup { .. } => Ok(ProtobufAction {
1958                name: ProtobufActionName::TogglePaneInGroup as i32,
1959                optional_payload: None,
1960            }),
1961            Action::ToggleGroupMarking { .. } => Ok(ProtobufAction {
1962                name: ProtobufActionName::ToggleGroupMarking as i32,
1963                optional_payload: None,
1964            }),
1965            Action::NewStackedPane {
1966                command: _,
1967                pane_name: _,
1968                near_current_pane: _,
1969                ..
1970            } => Ok(ProtobufAction {
1971                name: ProtobufActionName::NewStackedPane as i32,
1972                optional_payload: None,
1973            }),
1974            Action::NewBlockingPane {
1975                placement,
1976                pane_name,
1977                command,
1978                unblock_condition,
1979                near_current_pane,
1980                ..
1981            } => {
1982                let placement: ProtobufNewPanePlacement = placement.try_into()?;
1983                let command = command.and_then(|c| {
1984                    let protobuf_command: ProtobufRunCommandAction = c.try_into().ok()?;
1985                    Some(protobuf_command)
1986                });
1987                let unblock_condition = unblock_condition
1988                    .map(|uc| {
1989                        let protobuf_uc: ProtobufUnblockCondition = uc.try_into().ok()?;
1990                        Some(protobuf_uc as i32)
1991                    })
1992                    .flatten();
1993                Ok(ProtobufAction {
1994                    name: ProtobufActionName::NewBlockingPane as i32,
1995                    optional_payload: Some(OptionalPayload::NewBlockingPanePayload(
1996                        NewBlockingPanePayload {
1997                            placement: Some(placement),
1998                            pane_name,
1999                            command,
2000                            unblock_condition,
2001                            near_current_pane,
2002                        },
2003                    )),
2004                })
2005            },
2006            Action::NewInPlacePane {
2007                command: run_command_action,
2008                pane_name,
2009                near_current_pane,
2010                pane_id_to_replace,
2011                close_replaced_pane,
2012                ..
2013            } => {
2014                let command = run_command_action.and_then(|r| {
2015                    let mut protobuf_run_command_action: ProtobufRunCommandAction =
2016                        r.try_into().ok()?;
2017                    let pane_name = pane_name.and_then(|n| n.try_into().ok());
2018                    protobuf_run_command_action.pane_name = pane_name;
2019                    Some(protobuf_run_command_action)
2020                });
2021                let pane_id_to_replace = pane_id_to_replace.and_then(|p| p.try_into().ok());
2022                Ok(ProtobufAction {
2023                    name: ProtobufActionName::NewInPlacePane as i32,
2024                    optional_payload: Some(OptionalPayload::NewInPlacePanePayload(
2025                        NewInPlacePanePayload {
2026                            command,
2027                            pane_name: None, // pane_name is already embedded in command
2028                            near_current_pane,
2029                            pane_id_to_replace,
2030                            close_replace_pane: close_replaced_pane,
2031                        },
2032                    )),
2033                })
2034            },
2035            Action::FocusHostSession => Ok(ProtobufAction {
2036                name: ProtobufActionName::FocusHostSession as i32,
2037                optional_payload: None,
2038            }),
2039            Action::FocusGuestSession => Ok(ProtobufAction {
2040                name: ProtobufActionName::FocusGuestSession as i32,
2041                optional_payload: None,
2042            }),
2043            Action::ToggleHostFullscreen => Ok(ProtobufAction {
2044                name: ProtobufActionName::ToggleHostFullscreen as i32,
2045                optional_payload: None,
2046            }),
2047            Action::NoOp
2048            | Action::Confirm
2049            | Action::NewInPlacePluginPane {
2050                plugin: _,
2051                pane_name: _,
2052                skip_cache: _,
2053                close_replaced_pane: _,
2054                ..
2055            }
2056            | Action::Deny
2057            | Action::Copy
2058            | Action::DumpLayout
2059            | Action::CliPipe { .. }
2060            | Action::ListClients
2061            | Action::ListPanes { .. }
2062            | Action::StackPanes { pane_ids: _ }
2063            | Action::ChangeFloatingPaneCoordinates {
2064                pane_id: _,
2065                coordinates: _,
2066            }
2067            | Action::TogglePaneBorderless { pane_id: _ }
2068            | Action::SetPaneBorderless { .. }
2069            | Action::SkipConfirm { action: _ }
2070            | Action::SwitchSession { .. }
2071            | Action::SaveSession
2072            | Action::ListTabs { .. }
2073            | Action::CurrentTabInfo { .. }
2074            | Action::SetPaneColor { .. } => Err("Unsupported action"),
2075        }
2076    }
2077}
2078
2079impl TryFrom<ProtobufSearchOption> for SearchOption {
2080    type Error = &'static str;
2081    fn try_from(protobuf_search_option: ProtobufSearchOption) -> Result<Self, &'static str> {
2082        match protobuf_search_option {
2083            ProtobufSearchOption::CaseSensitivity => Ok(SearchOption::CaseSensitivity),
2084            ProtobufSearchOption::WholeWord => Ok(SearchOption::WholeWord),
2085            ProtobufSearchOption::Wrap => Ok(SearchOption::Wrap),
2086        }
2087    }
2088}
2089
2090impl TryFrom<SearchOption> for ProtobufSearchOption {
2091    type Error = &'static str;
2092    fn try_from(search_option: SearchOption) -> Result<Self, &'static str> {
2093        match search_option {
2094            SearchOption::CaseSensitivity => Ok(ProtobufSearchOption::CaseSensitivity),
2095            SearchOption::WholeWord => Ok(ProtobufSearchOption::WholeWord),
2096            SearchOption::Wrap => Ok(ProtobufSearchOption::Wrap),
2097        }
2098    }
2099}
2100
2101impl TryFrom<ProtobufSearchDirection> for SearchDirection {
2102    type Error = &'static str;
2103    fn try_from(protobuf_search_direction: ProtobufSearchDirection) -> Result<Self, &'static str> {
2104        match protobuf_search_direction {
2105            ProtobufSearchDirection::Up => Ok(SearchDirection::Up),
2106            ProtobufSearchDirection::Down => Ok(SearchDirection::Down),
2107        }
2108    }
2109}
2110
2111impl TryFrom<SearchDirection> for ProtobufSearchDirection {
2112    type Error = &'static str;
2113    fn try_from(search_direction: SearchDirection) -> Result<Self, &'static str> {
2114        match search_direction {
2115            SearchDirection::Up => Ok(ProtobufSearchDirection::Up),
2116            SearchDirection::Down => Ok(ProtobufSearchDirection::Down),
2117        }
2118    }
2119}
2120
2121impl TryFrom<ProtobufMoveTabDirection> for Direction {
2122    type Error = &'static str;
2123    fn try_from(
2124        protobuf_move_tab_direction: ProtobufMoveTabDirection,
2125    ) -> Result<Self, &'static str> {
2126        match protobuf_move_tab_direction {
2127            ProtobufMoveTabDirection::Left => Ok(Direction::Left),
2128            ProtobufMoveTabDirection::Right => Ok(Direction::Right),
2129        }
2130    }
2131}
2132
2133impl TryFrom<Direction> for ProtobufMoveTabDirection {
2134    type Error = &'static str;
2135    fn try_from(direction: Direction) -> Result<Self, &'static str> {
2136        match direction {
2137            Direction::Left => Ok(ProtobufMoveTabDirection::Left),
2138            Direction::Right => Ok(ProtobufMoveTabDirection::Right),
2139            _ => Err("Wrong direction for ProtobufMoveTabDirection"),
2140        }
2141    }
2142}
2143
2144impl TryFrom<ProtobufRunCommandAction> for RunCommandAction {
2145    type Error = &'static str;
2146    fn try_from(
2147        protobuf_run_command_action: ProtobufRunCommandAction,
2148    ) -> Result<Self, &'static str> {
2149        let command = PathBuf::from(protobuf_run_command_action.command);
2150        let args: Vec<String> = protobuf_run_command_action.args;
2151        let cwd: Option<PathBuf> = protobuf_run_command_action.cwd.map(|c| PathBuf::from(c));
2152        let direction: Option<Direction> = protobuf_run_command_action
2153            .direction
2154            .and_then(|d| ProtobufResizeDirection::try_from(d).ok())
2155            .and_then(|d| d.try_into().ok());
2156        let hold_on_close = protobuf_run_command_action.hold_on_close;
2157        let hold_on_start = protobuf_run_command_action.hold_on_start;
2158        Ok(RunCommandAction {
2159            command,
2160            args,
2161            cwd,
2162            direction,
2163            hold_on_close,
2164            hold_on_start,
2165            ..Default::default()
2166        })
2167    }
2168}
2169
2170impl TryFrom<RunCommandAction> for ProtobufRunCommandAction {
2171    type Error = &'static str;
2172    fn try_from(run_command_action: RunCommandAction) -> Result<Self, &'static str> {
2173        let command = run_command_action.command.display().to_string();
2174        let args: Vec<String> = run_command_action.args;
2175        let cwd = run_command_action.cwd.map(|c| c.display().to_string());
2176        let direction = run_command_action.direction.and_then(|p| {
2177            let direction: ProtobufResizeDirection = p.try_into().ok()?;
2178            Some(direction as i32)
2179        });
2180        let hold_on_close = run_command_action.hold_on_close;
2181        let hold_on_start = run_command_action.hold_on_start;
2182        Ok(ProtobufRunCommandAction {
2183            command,
2184            args,
2185            cwd,
2186            direction,
2187            hold_on_close,
2188            hold_on_start,
2189            pane_name: None,
2190        })
2191    }
2192}
2193
2194impl TryFrom<ProtobufPosition> for Position {
2195    type Error = &'static str;
2196    fn try_from(protobuf_position: ProtobufPosition) -> Result<Self, &'static str> {
2197        Ok(Position::new(
2198            protobuf_position.line as i32,
2199            protobuf_position.column as u16,
2200        ))
2201    }
2202}
2203
2204impl TryFrom<Position> for ProtobufPosition {
2205    type Error = &'static str;
2206    fn try_from(position: Position) -> Result<Self, &'static str> {
2207        Ok(ProtobufPosition {
2208            line: position.line.0 as i64,
2209            column: position.column.0 as i64,
2210        })
2211    }
2212}
2213
2214impl TryFrom<ProtobufMouseEventPayload> for MouseEvent {
2215    type Error = &'static str;
2216    fn try_from(protobuf_event: ProtobufMouseEventPayload) -> Result<Self, &'static str> {
2217        Ok(MouseEvent {
2218            event_type: match protobuf_event.event_type as u32 {
2219                0 => MouseEventType::Press,
2220                1 => MouseEventType::Release,
2221                _ => MouseEventType::Motion,
2222            },
2223            left: protobuf_event.left as bool,
2224            right: protobuf_event.right as bool,
2225            middle: protobuf_event.middle as bool,
2226            wheel_up: protobuf_event.wheel_up as bool,
2227            wheel_down: protobuf_event.wheel_down as bool,
2228            wheel_left: protobuf_event.wheel_left as bool,
2229            wheel_right: protobuf_event.wheel_right as bool,
2230            shift: protobuf_event.shift as bool,
2231            alt: protobuf_event.alt as bool,
2232            ctrl: protobuf_event.ctrl as bool,
2233            position: Position::new(protobuf_event.line as i32, protobuf_event.column as u16),
2234        })
2235    }
2236}
2237
2238impl TryFrom<MouseEvent> for ProtobufMouseEventPayload {
2239    type Error = &'static str;
2240    fn try_from(event: MouseEvent) -> Result<Self, &'static str> {
2241        Ok(ProtobufMouseEventPayload {
2242            event_type: match event.event_type {
2243                MouseEventType::Press => 0,
2244                MouseEventType::Release => 1,
2245                MouseEventType::Motion => 2,
2246            } as u32,
2247            left: event.left as bool,
2248            right: event.right as bool,
2249            middle: event.middle as bool,
2250            wheel_up: event.wheel_up as bool,
2251            wheel_down: event.wheel_down as bool,
2252            wheel_left: event.wheel_left as bool,
2253            wheel_right: event.wheel_right as bool,
2254            shift: event.shift as bool,
2255            alt: event.alt as bool,
2256            ctrl: event.ctrl as bool,
2257            line: event.position.line.0 as i64,
2258            column: event.position.column.0 as i64,
2259        })
2260    }
2261}
2262
2263impl TryFrom<ProtobufPluginConfiguration> for PluginUserConfiguration {
2264    type Error = &'static str;
2265    fn try_from(plugin_configuration: ProtobufPluginConfiguration) -> Result<Self, &'static str> {
2266        let mut converted = BTreeMap::new();
2267        for name_and_value in plugin_configuration.name_and_value {
2268            converted.insert(name_and_value.name, name_and_value.value);
2269        }
2270        Ok(PluginUserConfiguration::new(converted))
2271    }
2272}
2273
2274impl TryFrom<PluginUserConfiguration> for ProtobufPluginConfiguration {
2275    type Error = &'static str;
2276    fn try_from(plugin_configuration: PluginUserConfiguration) -> Result<Self, &'static str> {
2277        let mut converted = vec![];
2278        for (name, value) in plugin_configuration.inner() {
2279            let name_and_value = ProtobufNameAndValue {
2280                name: name.to_owned(),
2281                value: value.to_owned(),
2282            };
2283            converted.push(name_and_value);
2284        }
2285        Ok(ProtobufPluginConfiguration {
2286            name_and_value: converted,
2287        })
2288    }
2289}
2290
2291impl TryFrom<&ProtobufPluginConfiguration> for BTreeMap<String, String> {
2292    type Error = &'static str;
2293    fn try_from(plugin_configuration: &ProtobufPluginConfiguration) -> Result<Self, &'static str> {
2294        let mut converted = BTreeMap::new();
2295        for name_and_value in &plugin_configuration.name_and_value {
2296            converted.insert(
2297                name_and_value.name.to_owned(),
2298                name_and_value.value.to_owned(),
2299            );
2300        }
2301        Ok(converted)
2302    }
2303}
2304
2305impl TryFrom<ProtobufKeyWithModifier> for KeyWithModifier {
2306    type Error = &'static str;
2307    fn try_from(protobuf_key: ProtobufKeyWithModifier) -> Result<Self, &'static str> {
2308        let bare_key = match ProtobufBareKey::try_from(protobuf_key.bare_key).ok() {
2309            Some(ProtobufBareKey::PageDown) => crate::data::BareKey::PageDown,
2310            Some(ProtobufBareKey::PageUp) => crate::data::BareKey::PageUp,
2311            Some(ProtobufBareKey::Left) => crate::data::BareKey::Left,
2312            Some(ProtobufBareKey::Down) => crate::data::BareKey::Down,
2313            Some(ProtobufBareKey::Up) => crate::data::BareKey::Up,
2314            Some(ProtobufBareKey::Right) => crate::data::BareKey::Right,
2315            Some(ProtobufBareKey::Home) => crate::data::BareKey::Home,
2316            Some(ProtobufBareKey::End) => crate::data::BareKey::End,
2317            Some(ProtobufBareKey::Backspace) => crate::data::BareKey::Backspace,
2318            Some(ProtobufBareKey::Delete) => crate::data::BareKey::Delete,
2319            Some(ProtobufBareKey::Insert) => crate::data::BareKey::Insert,
2320            Some(ProtobufBareKey::F1) => crate::data::BareKey::F(1),
2321            Some(ProtobufBareKey::F2) => crate::data::BareKey::F(2),
2322            Some(ProtobufBareKey::F3) => crate::data::BareKey::F(3),
2323            Some(ProtobufBareKey::F4) => crate::data::BareKey::F(4),
2324            Some(ProtobufBareKey::F5) => crate::data::BareKey::F(5),
2325            Some(ProtobufBareKey::F6) => crate::data::BareKey::F(6),
2326            Some(ProtobufBareKey::F7) => crate::data::BareKey::F(7),
2327            Some(ProtobufBareKey::F8) => crate::data::BareKey::F(8),
2328            Some(ProtobufBareKey::F9) => crate::data::BareKey::F(9),
2329            Some(ProtobufBareKey::F10) => crate::data::BareKey::F(10),
2330            Some(ProtobufBareKey::F11) => crate::data::BareKey::F(11),
2331            Some(ProtobufBareKey::F12) => crate::data::BareKey::F(12),
2332            Some(ProtobufBareKey::Char) => {
2333                if let Some(character) = protobuf_key.character {
2334                    let ch = character
2335                        .chars()
2336                        .next()
2337                        .ok_or("BareKey::Char requires a character")?;
2338                    crate::data::BareKey::Char(ch)
2339                } else {
2340                    return Err("BareKey::Char requires a character");
2341                }
2342            },
2343            Some(ProtobufBareKey::Tab) => crate::data::BareKey::Tab,
2344            Some(ProtobufBareKey::Esc) => crate::data::BareKey::Esc,
2345            Some(ProtobufBareKey::Enter) => crate::data::BareKey::Enter,
2346            Some(ProtobufBareKey::CapsLock) => crate::data::BareKey::CapsLock,
2347            Some(ProtobufBareKey::ScrollLock) => crate::data::BareKey::ScrollLock,
2348            Some(ProtobufBareKey::NumLock) => crate::data::BareKey::NumLock,
2349            Some(ProtobufBareKey::PrintScreen) => crate::data::BareKey::PrintScreen,
2350            Some(ProtobufBareKey::Pause) => crate::data::BareKey::Pause,
2351            Some(ProtobufBareKey::Menu) => crate::data::BareKey::Menu,
2352            _ => return Err("Unknown BareKey"),
2353        };
2354
2355        let mut key_modifiers = std::collections::BTreeSet::new();
2356        for modifier in protobuf_key.key_modifiers {
2357            let key_modifier = match ProtobufKeyModifier::try_from(modifier).ok() {
2358                Some(ProtobufKeyModifier::Ctrl) => crate::data::KeyModifier::Ctrl,
2359                Some(ProtobufKeyModifier::Alt) => crate::data::KeyModifier::Alt,
2360                Some(ProtobufKeyModifier::Shift) => crate::data::KeyModifier::Shift,
2361                Some(ProtobufKeyModifier::Super) => crate::data::KeyModifier::Super,
2362                _ => continue,
2363            };
2364            key_modifiers.insert(key_modifier);
2365        }
2366
2367        Ok(KeyWithModifier {
2368            bare_key,
2369            key_modifiers,
2370        })
2371    }
2372}
2373
2374impl TryFrom<KeyWithModifier> for ProtobufKeyWithModifier {
2375    type Error = &'static str;
2376    fn try_from(key: KeyWithModifier) -> Result<Self, &'static str> {
2377        let (bare_key, character) = match key.bare_key {
2378            crate::data::BareKey::PageDown => (ProtobufBareKey::PageDown as i32, None),
2379            crate::data::BareKey::PageUp => (ProtobufBareKey::PageUp as i32, None),
2380            crate::data::BareKey::Left => (ProtobufBareKey::Left as i32, None),
2381            crate::data::BareKey::Down => (ProtobufBareKey::Down as i32, None),
2382            crate::data::BareKey::Up => (ProtobufBareKey::Up as i32, None),
2383            crate::data::BareKey::Right => (ProtobufBareKey::Right as i32, None),
2384            crate::data::BareKey::Home => (ProtobufBareKey::Home as i32, None),
2385            crate::data::BareKey::End => (ProtobufBareKey::End as i32, None),
2386            crate::data::BareKey::Backspace => (ProtobufBareKey::Backspace as i32, None),
2387            crate::data::BareKey::Delete => (ProtobufBareKey::Delete as i32, None),
2388            crate::data::BareKey::Insert => (ProtobufBareKey::Insert as i32, None),
2389            crate::data::BareKey::F(1) => (ProtobufBareKey::F1 as i32, None),
2390            crate::data::BareKey::F(2) => (ProtobufBareKey::F2 as i32, None),
2391            crate::data::BareKey::F(3) => (ProtobufBareKey::F3 as i32, None),
2392            crate::data::BareKey::F(4) => (ProtobufBareKey::F4 as i32, None),
2393            crate::data::BareKey::F(5) => (ProtobufBareKey::F5 as i32, None),
2394            crate::data::BareKey::F(6) => (ProtobufBareKey::F6 as i32, None),
2395            crate::data::BareKey::F(7) => (ProtobufBareKey::F7 as i32, None),
2396            crate::data::BareKey::F(8) => (ProtobufBareKey::F8 as i32, None),
2397            crate::data::BareKey::F(9) => (ProtobufBareKey::F9 as i32, None),
2398            crate::data::BareKey::F(10) => (ProtobufBareKey::F10 as i32, None),
2399            crate::data::BareKey::F(11) => (ProtobufBareKey::F11 as i32, None),
2400            crate::data::BareKey::F(12) => (ProtobufBareKey::F12 as i32, None),
2401            crate::data::BareKey::Char(c) => (ProtobufBareKey::Char as i32, Some(c.to_string())),
2402            crate::data::BareKey::Tab => (ProtobufBareKey::Tab as i32, None),
2403            crate::data::BareKey::Esc => (ProtobufBareKey::Esc as i32, None),
2404            crate::data::BareKey::Enter => (ProtobufBareKey::Enter as i32, None),
2405            crate::data::BareKey::CapsLock => (ProtobufBareKey::CapsLock as i32, None),
2406            crate::data::BareKey::ScrollLock => (ProtobufBareKey::ScrollLock as i32, None),
2407            crate::data::BareKey::NumLock => (ProtobufBareKey::NumLock as i32, None),
2408            crate::data::BareKey::PrintScreen => (ProtobufBareKey::PrintScreen as i32, None),
2409            crate::data::BareKey::Pause => (ProtobufBareKey::Pause as i32, None),
2410            crate::data::BareKey::Menu => (ProtobufBareKey::Menu as i32, None),
2411            _ => return Err("Unsupported BareKey"),
2412        };
2413
2414        let key_modifiers: Vec<i32> = key
2415            .key_modifiers
2416            .iter()
2417            .map(|m| match m {
2418                crate::data::KeyModifier::Ctrl => ProtobufKeyModifier::Ctrl as i32,
2419                crate::data::KeyModifier::Alt => ProtobufKeyModifier::Alt as i32,
2420                crate::data::KeyModifier::Shift => ProtobufKeyModifier::Shift as i32,
2421                crate::data::KeyModifier::Super => ProtobufKeyModifier::Super as i32,
2422            })
2423            .collect();
2424
2425        Ok(ProtobufKeyWithModifier {
2426            bare_key,
2427            key_modifiers,
2428            character,
2429        })
2430    }
2431}
2432
2433// UnblockCondition conversions
2434impl TryFrom<ProtobufUnblockCondition> for UnblockCondition {
2435    type Error = &'static str;
2436    fn try_from(protobuf_uc: ProtobufUnblockCondition) -> Result<Self, &'static str> {
2437        match protobuf_uc {
2438            ProtobufUnblockCondition::UnblockOnExitSuccess => Ok(UnblockCondition::OnExitSuccess),
2439            ProtobufUnblockCondition::UnblockOnExitFailure => Ok(UnblockCondition::OnExitFailure),
2440            ProtobufUnblockCondition::UnblockOnAnyExit => Ok(UnblockCondition::OnAnyExit),
2441        }
2442    }
2443}
2444
2445impl TryFrom<UnblockCondition> for ProtobufUnblockCondition {
2446    type Error = &'static str;
2447    fn try_from(uc: UnblockCondition) -> Result<Self, &'static str> {
2448        match uc {
2449            UnblockCondition::OnExitSuccess => Ok(ProtobufUnblockCondition::UnblockOnExitSuccess),
2450            UnblockCondition::OnExitFailure => Ok(ProtobufUnblockCondition::UnblockOnExitFailure),
2451            UnblockCondition::OnAnyExit => Ok(ProtobufUnblockCondition::UnblockOnAnyExit),
2452        }
2453    }
2454}
2455
2456impl TryFrom<i32> for UnblockCondition {
2457    type Error = &'static str;
2458    fn try_from(value: i32) -> Result<Self, &'static str> {
2459        match ProtobufUnblockCondition::try_from(value).ok() {
2460            Some(uc) => uc.try_into(),
2461            None => Err("Invalid UnblockCondition value"),
2462        }
2463    }
2464}
2465
2466// PaneId conversions
2467impl TryFrom<ProtobufPaneId> for PaneId {
2468    type Error = &'static str;
2469    fn try_from(protobuf_pane_id: ProtobufPaneId) -> Result<Self, &'static str> {
2470        use super::generated_api::api::action::pane_id::PaneIdVariant;
2471
2472        match protobuf_pane_id.pane_id_variant {
2473            Some(PaneIdVariant::Terminal(id)) => Ok(PaneId::Terminal(id)),
2474            Some(PaneIdVariant::Plugin(id)) => Ok(PaneId::Plugin(id)),
2475            None => Err("PaneId must have either terminal or plugin id"),
2476        }
2477    }
2478}
2479
2480impl TryFrom<PaneId> for ProtobufPaneId {
2481    type Error = &'static str;
2482    fn try_from(pane_id: PaneId) -> Result<Self, &'static str> {
2483        use super::generated_api::api::action::pane_id::PaneIdVariant;
2484
2485        let pane_id_variant = match pane_id {
2486            PaneId::Terminal(id) => Some(PaneIdVariant::Terminal(id)),
2487            PaneId::Plugin(id) => Some(PaneIdVariant::Plugin(id)),
2488        };
2489        Ok(ProtobufPaneId { pane_id_variant })
2490    }
2491}
2492
2493// SplitSize conversions
2494impl TryFrom<ProtobufSplitSize> for SplitSize {
2495    type Error = &'static str;
2496    fn try_from(protobuf_split_size: ProtobufSplitSize) -> Result<Self, &'static str> {
2497        use super::generated_api::api::action::split_size::SplitSizeVariant;
2498
2499        match protobuf_split_size.split_size_variant {
2500            Some(SplitSizeVariant::Percent(p)) => Ok(SplitSize::Percent(p as usize)),
2501            Some(SplitSizeVariant::Fixed(f)) => Ok(SplitSize::Fixed(f as usize)),
2502            None => Err("SplitSize must have either percent or fixed value"),
2503        }
2504    }
2505}
2506
2507impl TryFrom<SplitSize> for ProtobufSplitSize {
2508    type Error = &'static str;
2509    fn try_from(split_size: SplitSize) -> Result<Self, &'static str> {
2510        use super::generated_api::api::action::split_size::SplitSizeVariant;
2511
2512        let split_size_variant = match split_size {
2513            SplitSize::Percent(p) => Some(SplitSizeVariant::Percent(p as u32)),
2514            SplitSize::Fixed(f) => Some(SplitSizeVariant::Fixed(f as u32)),
2515        };
2516        Ok(ProtobufSplitSize { split_size_variant })
2517    }
2518}
2519
2520impl TryFrom<ProtobufSplitSize> for PercentOrFixed {
2521    type Error = &'static str;
2522    fn try_from(protobuf_split_size: ProtobufSplitSize) -> Result<Self, &'static str> {
2523        use super::generated_api::api::action::split_size::SplitSizeVariant;
2524
2525        match protobuf_split_size.split_size_variant {
2526            Some(SplitSizeVariant::Percent(p)) => Ok(PercentOrFixed::Percent(p as usize)),
2527            Some(SplitSizeVariant::Fixed(f)) => Ok(PercentOrFixed::Fixed(f as usize)),
2528            None => Err("PercentOrFixed must have either percent or fixed value"),
2529        }
2530    }
2531}
2532
2533impl TryFrom<PercentOrFixed> for ProtobufSplitSize {
2534    type Error = &'static str;
2535    fn try_from(percent_or_fixed: PercentOrFixed) -> Result<Self, &'static str> {
2536        use super::generated_api::api::action::split_size::SplitSizeVariant;
2537
2538        let split_size_variant = match percent_or_fixed {
2539            PercentOrFixed::Percent(p) => Some(SplitSizeVariant::Percent(p as u32)),
2540            PercentOrFixed::Fixed(f) => Some(SplitSizeVariant::Fixed(f as u32)),
2541        };
2542        Ok(ProtobufSplitSize { split_size_variant })
2543    }
2544}
2545
2546// FloatingPaneCoordinates conversions
2547impl TryFrom<ProtobufFloatingPaneCoordinates> for FloatingPaneCoordinates {
2548    type Error = &'static str;
2549    fn try_from(protobuf_coords: ProtobufFloatingPaneCoordinates) -> Result<Self, &'static str> {
2550        Ok(FloatingPaneCoordinates {
2551            x: protobuf_coords.x.and_then(|x| x.try_into().ok()),
2552            y: protobuf_coords.y.and_then(|y| y.try_into().ok()),
2553            width: protobuf_coords.width.and_then(|w| w.try_into().ok()),
2554            height: protobuf_coords.height.and_then(|h| h.try_into().ok()),
2555            pinned: protobuf_coords.pinned,
2556            borderless: protobuf_coords.borderless,
2557        })
2558    }
2559}
2560
2561impl TryFrom<FloatingPaneCoordinates> for ProtobufFloatingPaneCoordinates {
2562    type Error = &'static str;
2563    fn try_from(coords: FloatingPaneCoordinates) -> Result<Self, &'static str> {
2564        Ok(ProtobufFloatingPaneCoordinates {
2565            x: coords.x.and_then(|x| x.try_into().ok()),
2566            y: coords.y.and_then(|y| y.try_into().ok()),
2567            width: coords.width.and_then(|w| w.try_into().ok()),
2568            height: coords.height.and_then(|h| h.try_into().ok()),
2569            pinned: coords.pinned,
2570            borderless: coords.borderless,
2571        })
2572    }
2573}
2574
2575// NewPanePlacement conversions
2576impl TryFrom<ProtobufNewPanePlacement> for NewPanePlacement {
2577    type Error = &'static str;
2578    fn try_from(protobuf_placement: ProtobufNewPanePlacement) -> Result<Self, &'static str> {
2579        use super::generated_api::api::action::new_pane_placement::PlacementVariant;
2580
2581        match protobuf_placement.placement_variant {
2582            Some(PlacementVariant::NoPreference(opts)) => Ok(NewPanePlacement::NoPreference {
2583                borderless: opts.borderless,
2584            }),
2585            Some(PlacementVariant::Tiled(tiled)) => {
2586                let direction = tiled
2587                    .direction
2588                    .and_then(|d| ProtobufResizeDirection::try_from(d).ok())
2589                    .and_then(|d| d.try_into().ok());
2590                Ok(NewPanePlacement::Tiled {
2591                    direction,
2592                    borderless: tiled.borderless,
2593                })
2594            },
2595            Some(PlacementVariant::Floating(floating)) => {
2596                let coords = floating.coordinates.and_then(|c| c.try_into().ok());
2597                Ok(NewPanePlacement::Floating(coords))
2598            },
2599            Some(PlacementVariant::InPlace(config)) => {
2600                let pane_id_to_replace =
2601                    config.pane_id_to_replace.and_then(|id| id.try_into().ok());
2602                Ok(NewPanePlacement::InPlace {
2603                    pane_id_to_replace,
2604                    close_replaced_pane: config.close_replaced_pane,
2605                    borderless: config.borderless,
2606                })
2607            },
2608            Some(PlacementVariant::Stacked(stacked)) => {
2609                let pane_id = stacked.pane_id.and_then(|id| id.try_into().ok());
2610                Ok(NewPanePlacement::Stacked {
2611                    pane_id_to_stack_under: pane_id,
2612                    borderless: stacked.borderless,
2613                })
2614            },
2615            None => Err("NewPanePlacement must have a placement variant"),
2616        }
2617    }
2618}
2619
2620impl TryFrom<NewPanePlacement> for ProtobufNewPanePlacement {
2621    type Error = &'static str;
2622    fn try_from(placement: NewPanePlacement) -> Result<Self, &'static str> {
2623        use super::generated_api::api::action::new_pane_placement::PlacementVariant;
2624        use super::generated_api::api::action::NoPreferenceOptions;
2625
2626        let placement_variant = match placement {
2627            NewPanePlacement::NoPreference { borderless } => {
2628                Some(PlacementVariant::NoPreference(NoPreferenceOptions {
2629                    borderless,
2630                }))
2631            },
2632            NewPanePlacement::Tiled {
2633                direction,
2634                borderless,
2635            } => {
2636                let direction = direction.and_then(|d| {
2637                    let protobuf_direction: ProtobufResizeDirection = d.try_into().ok()?;
2638                    Some(protobuf_direction as i32)
2639                });
2640                Some(PlacementVariant::Tiled(ProtobufTiledPlacement {
2641                    direction,
2642                    borderless,
2643                }))
2644            },
2645            NewPanePlacement::Floating(coords) => {
2646                let coordinates = coords.and_then(|c| c.try_into().ok());
2647                Some(PlacementVariant::Floating(ProtobufFloatingPlacement {
2648                    coordinates,
2649                }))
2650            },
2651            NewPanePlacement::InPlace {
2652                pane_id_to_replace,
2653                close_replaced_pane,
2654                borderless,
2655            } => {
2656                let pane_id_to_replace = pane_id_to_replace.and_then(|id| id.try_into().ok());
2657                Some(PlacementVariant::InPlace(ProtobufInPlaceConfig {
2658                    pane_id_to_replace,
2659                    close_replaced_pane,
2660                    borderless,
2661                }))
2662            },
2663            NewPanePlacement::Stacked {
2664                pane_id_to_stack_under,
2665                borderless,
2666            } => {
2667                let pane_id = pane_id_to_stack_under.and_then(|id| id.try_into().ok());
2668                Some(PlacementVariant::Stacked(ProtobufStackedPlacement {
2669                    pane_id,
2670                    borderless,
2671                }))
2672            },
2673        };
2674
2675        Ok(ProtobufNewPanePlacement { placement_variant })
2676    }
2677}
2678
2679// Layout type conversions
2680
2681impl TryFrom<ProtobufPercentOrFixed> for PercentOrFixed {
2682    type Error = &'static str;
2683    fn try_from(protobuf: ProtobufPercentOrFixed) -> Result<Self, Self::Error> {
2684        use super::generated_api::api::action::percent_or_fixed::SizeType;
2685        match protobuf.size_type {
2686            Some(SizeType::Percent(p)) => Ok(PercentOrFixed::Percent(p as usize)),
2687            Some(SizeType::Fixed(f)) => Ok(PercentOrFixed::Fixed(f as usize)),
2688            None => Err("PercentOrFixed must have a size_type"),
2689        }
2690    }
2691}
2692
2693impl TryFrom<PercentOrFixed> for ProtobufPercentOrFixed {
2694    type Error = &'static str;
2695    fn try_from(internal: PercentOrFixed) -> Result<Self, Self::Error> {
2696        use super::generated_api::api::action::percent_or_fixed::SizeType;
2697        let size_type = match internal {
2698            PercentOrFixed::Percent(p) => Some(SizeType::Percent(p as u32)),
2699            PercentOrFixed::Fixed(f) => Some(SizeType::Fixed(f as u32)),
2700        };
2701        Ok(ProtobufPercentOrFixed { size_type })
2702    }
2703}
2704
2705impl TryFrom<ProtobufSplitDirection> for SplitDirection {
2706    type Error = &'static str;
2707    fn try_from(protobuf: ProtobufSplitDirection) -> Result<Self, Self::Error> {
2708        match protobuf {
2709            ProtobufSplitDirection::Horizontal => Ok(SplitDirection::Horizontal),
2710            ProtobufSplitDirection::Vertical => Ok(SplitDirection::Vertical),
2711            ProtobufSplitDirection::Unspecified => Err("SplitDirection cannot be unspecified"),
2712        }
2713    }
2714}
2715
2716impl TryFrom<SplitDirection> for ProtobufSplitDirection {
2717    type Error = &'static str;
2718    fn try_from(internal: SplitDirection) -> Result<Self, Self::Error> {
2719        Ok(match internal {
2720            SplitDirection::Horizontal => ProtobufSplitDirection::Horizontal,
2721            SplitDirection::Vertical => ProtobufSplitDirection::Vertical,
2722        })
2723    }
2724}
2725
2726impl TryFrom<ProtobufLayoutConstraint> for LayoutConstraint {
2727    type Error = &'static str;
2728    fn try_from(protobuf: ProtobufLayoutConstraint) -> Result<Self, Self::Error> {
2729        match protobuf {
2730            ProtobufLayoutConstraint::MaxPanes => Ok(LayoutConstraint::MaxPanes(0)),
2731            ProtobufLayoutConstraint::MinPanes => Ok(LayoutConstraint::MinPanes(0)),
2732            ProtobufLayoutConstraint::ExactPanes => Ok(LayoutConstraint::ExactPanes(0)),
2733            ProtobufLayoutConstraint::NoConstraint => Ok(LayoutConstraint::NoConstraint),
2734            ProtobufLayoutConstraint::Unspecified => Err("LayoutConstraint cannot be unspecified"),
2735        }
2736    }
2737}
2738
2739impl TryFrom<LayoutConstraint> for ProtobufLayoutConstraint {
2740    type Error = &'static str;
2741    fn try_from(internal: LayoutConstraint) -> Result<Self, Self::Error> {
2742        Ok(match internal {
2743            LayoutConstraint::MaxPanes(_) => ProtobufLayoutConstraint::MaxPanes,
2744            LayoutConstraint::MinPanes(_) => ProtobufLayoutConstraint::MinPanes,
2745            LayoutConstraint::ExactPanes(_) => ProtobufLayoutConstraint::ExactPanes,
2746            LayoutConstraint::NoConstraint => ProtobufLayoutConstraint::NoConstraint,
2747        })
2748    }
2749}
2750
2751impl TryFrom<ProtobufLayoutConstraintWithValue> for LayoutConstraint {
2752    type Error = &'static str;
2753    fn try_from(protobuf: ProtobufLayoutConstraintWithValue) -> Result<Self, Self::Error> {
2754        let constraint_type = ProtobufLayoutConstraint::try_from(protobuf.constraint_type)
2755            .ok()
2756            .ok_or("Invalid constraint type")?;
2757        match constraint_type {
2758            ProtobufLayoutConstraint::MaxPanes => Ok(LayoutConstraint::MaxPanes(
2759                protobuf.value.unwrap_or(0) as usize,
2760            )),
2761            ProtobufLayoutConstraint::MinPanes => Ok(LayoutConstraint::MinPanes(
2762                protobuf.value.unwrap_or(0) as usize,
2763            )),
2764            ProtobufLayoutConstraint::ExactPanes => Ok(LayoutConstraint::ExactPanes(
2765                protobuf.value.unwrap_or(0) as usize,
2766            )),
2767            ProtobufLayoutConstraint::NoConstraint => Ok(LayoutConstraint::NoConstraint),
2768            ProtobufLayoutConstraint::Unspecified => Err("LayoutConstraint cannot be unspecified"),
2769        }
2770    }
2771}
2772
2773impl TryFrom<LayoutConstraint> for ProtobufLayoutConstraintWithValue {
2774    type Error = &'static str;
2775    fn try_from(internal: LayoutConstraint) -> Result<Self, Self::Error> {
2776        let (constraint_type, value) = match internal {
2777            LayoutConstraint::MaxPanes(v) => {
2778                (ProtobufLayoutConstraint::MaxPanes as i32, Some(v as u32))
2779            },
2780            LayoutConstraint::MinPanes(v) => {
2781                (ProtobufLayoutConstraint::MinPanes as i32, Some(v as u32))
2782            },
2783            LayoutConstraint::ExactPanes(v) => {
2784                (ProtobufLayoutConstraint::ExactPanes as i32, Some(v as u32))
2785            },
2786            LayoutConstraint::NoConstraint => (ProtobufLayoutConstraint::NoConstraint as i32, None),
2787        };
2788        Ok(ProtobufLayoutConstraintWithValue {
2789            constraint_type,
2790            value,
2791        })
2792    }
2793}
2794
2795impl TryFrom<ProtobufPluginTag> for PluginTag {
2796    type Error = &'static str;
2797    fn try_from(protobuf: ProtobufPluginTag) -> Result<Self, Self::Error> {
2798        Ok(PluginTag::new(protobuf.tag))
2799    }
2800}
2801
2802impl TryFrom<PluginTag> for ProtobufPluginTag {
2803    type Error = &'static str;
2804    fn try_from(internal: PluginTag) -> Result<Self, Self::Error> {
2805        Ok(ProtobufPluginTag {
2806            tag: internal.into(),
2807        })
2808    }
2809}
2810
2811impl TryFrom<ProtobufPluginUserConfiguration> for PluginUserConfiguration {
2812    type Error = &'static str;
2813    fn try_from(protobuf: ProtobufPluginUserConfiguration) -> Result<Self, Self::Error> {
2814        let btree_map: BTreeMap<String, String> = protobuf.configuration.into_iter().collect();
2815        Ok(PluginUserConfiguration::new(btree_map))
2816    }
2817}
2818
2819impl TryFrom<PluginUserConfiguration> for ProtobufPluginUserConfiguration {
2820    type Error = &'static str;
2821    fn try_from(internal: PluginUserConfiguration) -> Result<Self, Self::Error> {
2822        let configuration = internal.inner().clone().into_iter().collect();
2823        Ok(ProtobufPluginUserConfiguration { configuration })
2824    }
2825}
2826
2827impl TryFrom<ProtobufRunPluginLocationData> for RunPluginLocation {
2828    type Error = &'static str;
2829    fn try_from(protobuf: ProtobufRunPluginLocationData) -> Result<Self, Self::Error> {
2830        use super::generated_api::api::action::run_plugin_location_data::LocationData;
2831        match protobuf.location_data {
2832            Some(LocationData::FilePath(path)) => Ok(RunPluginLocation::File(PathBuf::from(path))),
2833            Some(LocationData::ZellijTag(tag)) => Ok(RunPluginLocation::Zellij(tag.try_into()?)),
2834            Some(LocationData::RemoteUrl(url)) => Ok(RunPluginLocation::Remote(url)),
2835            None => Err("RunPluginLocationData must have location_data"),
2836        }
2837    }
2838}
2839
2840impl TryFrom<RunPluginLocation> for ProtobufRunPluginLocationData {
2841    type Error = &'static str;
2842    fn try_from(internal: RunPluginLocation) -> Result<Self, Self::Error> {
2843        use super::generated_api::api::action::{
2844            run_plugin_location_data::LocationData,
2845            RunPluginLocation as ProtobufRunPluginLocationType,
2846        };
2847        let (location_type, location_data) = match internal {
2848            RunPluginLocation::File(path) => (
2849                ProtobufRunPluginLocationType::File as i32,
2850                Some(LocationData::FilePath(path.display().to_string())),
2851            ),
2852            RunPluginLocation::Zellij(tag) => (
2853                ProtobufRunPluginLocationType::Zellij as i32,
2854                Some(LocationData::ZellijTag(tag.try_into()?)),
2855            ),
2856            RunPluginLocation::Remote(url) => (
2857                ProtobufRunPluginLocationType::Remote as i32,
2858                Some(LocationData::RemoteUrl(url)),
2859            ),
2860        };
2861        Ok(ProtobufRunPluginLocationData {
2862            location_type,
2863            location_data,
2864        })
2865    }
2866}
2867
2868impl TryFrom<ProtobufRunPlugin> for RunPlugin {
2869    type Error = &'static str;
2870    fn try_from(protobuf: ProtobufRunPlugin) -> Result<Self, Self::Error> {
2871        let location = protobuf
2872            .location
2873            .ok_or("RunPlugin must have location")?
2874            .try_into()?;
2875        let configuration = protobuf
2876            .configuration
2877            .ok_or("RunPlugin must have configuration")?
2878            .try_into()?;
2879        let initial_cwd = protobuf.initial_cwd.map(PathBuf::from);
2880        Ok(RunPlugin {
2881            _allow_exec_host_cmd: protobuf.allow_exec_host_cmd,
2882            location,
2883            configuration,
2884            initial_cwd,
2885        })
2886    }
2887}
2888
2889impl TryFrom<RunPlugin> for ProtobufRunPlugin {
2890    type Error = &'static str;
2891    fn try_from(internal: RunPlugin) -> Result<Self, Self::Error> {
2892        Ok(ProtobufRunPlugin {
2893            allow_exec_host_cmd: internal._allow_exec_host_cmd,
2894            location: Some(internal.location.try_into()?),
2895            configuration: Some(internal.configuration.try_into()?),
2896            initial_cwd: internal.initial_cwd.map(|p| p.display().to_string()),
2897        })
2898    }
2899}
2900
2901impl TryFrom<ProtobufPluginAlias> for PluginAlias {
2902    type Error = &'static str;
2903    fn try_from(protobuf: ProtobufPluginAlias) -> Result<Self, Self::Error> {
2904        let configuration = protobuf.configuration.map(|c| c.try_into()).transpose()?;
2905        let initial_cwd = protobuf.initial_cwd.map(PathBuf::from);
2906        let run_plugin = protobuf.run_plugin.map(|r| r.try_into()).transpose()?;
2907        Ok(PluginAlias {
2908            name: protobuf.name,
2909            configuration,
2910            initial_cwd,
2911            run_plugin,
2912        })
2913    }
2914}
2915
2916impl TryFrom<PluginAlias> for ProtobufPluginAlias {
2917    type Error = &'static str;
2918    fn try_from(internal: PluginAlias) -> Result<Self, Self::Error> {
2919        Ok(ProtobufPluginAlias {
2920            name: internal.name,
2921            configuration: internal.configuration.map(|c| c.try_into()).transpose()?,
2922            initial_cwd: internal.initial_cwd.map(|p| p.display().to_string()),
2923            run_plugin: internal.run_plugin.map(|r| r.try_into()).transpose()?,
2924        })
2925    }
2926}
2927
2928impl TryFrom<ProtobufRunPluginOrAlias> for RunPluginOrAlias {
2929    type Error = &'static str;
2930    fn try_from(protobuf: ProtobufRunPluginOrAlias) -> Result<Self, Self::Error> {
2931        use super::generated_api::api::action::run_plugin_or_alias::PluginType;
2932        match protobuf.plugin_type {
2933            Some(PluginType::Plugin(plugin)) => Ok(RunPluginOrAlias::RunPlugin(plugin.try_into()?)),
2934            Some(PluginType::Alias(alias)) => Ok(RunPluginOrAlias::Alias(alias.try_into()?)),
2935            None => Err("RunPluginOrAlias must have plugin_type"),
2936        }
2937    }
2938}
2939
2940impl TryFrom<RunPluginOrAlias> for ProtobufRunPluginOrAlias {
2941    type Error = &'static str;
2942    fn try_from(internal: RunPluginOrAlias) -> Result<Self, Self::Error> {
2943        use super::generated_api::api::action::run_plugin_or_alias::PluginType;
2944        let plugin_type = match internal {
2945            RunPluginOrAlias::RunPlugin(plugin) => Some(PluginType::Plugin(plugin.try_into()?)),
2946            RunPluginOrAlias::Alias(alias) => Some(PluginType::Alias(alias.try_into()?)),
2947        };
2948        Ok(ProtobufRunPluginOrAlias { plugin_type })
2949    }
2950}
2951
2952impl TryFrom<ProtobufRunEditFileAction> for Run {
2953    type Error = &'static str;
2954    fn try_from(protobuf: ProtobufRunEditFileAction) -> Result<Self, Self::Error> {
2955        let file_path = PathBuf::from(protobuf.file_path);
2956        let line_number = protobuf.line_number.map(|n| n as usize);
2957        let cwd = protobuf.cwd.map(PathBuf::from);
2958        Ok(Run::EditFile(file_path, line_number, cwd))
2959    }
2960}
2961
2962impl TryFrom<ProtobufPaneRun> for Run {
2963    type Error = &'static str;
2964    fn try_from(protobuf: ProtobufPaneRun) -> Result<Self, Self::Error> {
2965        use super::generated_api::api::action::pane_run::RunType;
2966        use crate::input::command::RunCommand;
2967        match protobuf.run_type {
2968            Some(RunType::Command(cmd)) => {
2969                let run_command_action: RunCommandAction = cmd.try_into()?;
2970                let run_command: RunCommand = run_command_action.into();
2971                Ok(Run::Command(run_command))
2972            },
2973            Some(RunType::Plugin(plugin)) => Ok(Run::Plugin(plugin.try_into()?)),
2974            Some(RunType::EditFile(edit)) => edit.try_into(),
2975            Some(RunType::Cwd(cwd)) => Ok(Run::Cwd(PathBuf::from(cwd))),
2976            None => Err("PaneRun must have run_type"),
2977        }
2978    }
2979}
2980
2981impl TryFrom<Run> for ProtobufPaneRun {
2982    type Error = &'static str;
2983    fn try_from(internal: Run) -> Result<Self, Self::Error> {
2984        use super::generated_api::api::action::pane_run::RunType;
2985        let run_type = match internal {
2986            Run::Command(cmd) => {
2987                let run_command_action: RunCommandAction = cmd.into();
2988                Some(RunType::Command(run_command_action.try_into()?))
2989            },
2990            Run::Plugin(plugin) => Some(RunType::Plugin(plugin.try_into()?)),
2991            Run::EditFile(file_path, line_number, cwd) => {
2992                Some(RunType::EditFile(ProtobufRunEditFileAction {
2993                    file_path: file_path.display().to_string(),
2994                    line_number: line_number.map(|n| n as u32),
2995                    cwd: cwd.map(|p| p.display().to_string()),
2996                }))
2997            },
2998            Run::Cwd(cwd) => Some(RunType::Cwd(cwd.display().to_string())),
2999        };
3000        Ok(ProtobufPaneRun { run_type })
3001    }
3002}
3003
3004impl TryFrom<ProtobufCommandOrPlugin> for CommandOrPlugin {
3005    type Error = &'static str;
3006    fn try_from(protobuf: ProtobufCommandOrPlugin) -> Result<Self, Self::Error> {
3007        use super::generated_api::api::action::command_or_plugin::CommandOrPluginType;
3008        match protobuf.command_or_plugin_type {
3009            Some(CommandOrPluginType::Command(cmd)) => {
3010                Ok(CommandOrPlugin::Command(cmd.try_into()?))
3011            },
3012            Some(CommandOrPluginType::Plugin(plugin)) => {
3013                Ok(CommandOrPlugin::Plugin(plugin.try_into()?))
3014            },
3015            Some(CommandOrPluginType::File(f)) => {
3016                Ok(CommandOrPlugin::File(crate::data::FileToOpen {
3017                    path: std::path::PathBuf::from(&f.path),
3018                    line_number: f.line_number.map(|n| n as usize),
3019                    cwd: f.cwd.map(std::path::PathBuf::from),
3020                }))
3021            },
3022            None => Err("CommandOrPlugin must have command_or_plugin_type"),
3023        }
3024    }
3025}
3026
3027impl TryFrom<CommandOrPlugin> for ProtobufCommandOrPlugin {
3028    type Error = &'static str;
3029    fn try_from(internal: CommandOrPlugin) -> Result<Self, Self::Error> {
3030        use super::generated_api::api::action::command_or_plugin::CommandOrPluginType;
3031        use super::generated_api::api::action::CommandOrPluginFile;
3032        let command_or_plugin_type = match internal {
3033            CommandOrPlugin::Command(cmd) => Some(CommandOrPluginType::Command(cmd.try_into()?)),
3034            CommandOrPlugin::Plugin(plugin) => {
3035                Some(CommandOrPluginType::Plugin(plugin.try_into()?))
3036            },
3037            CommandOrPlugin::File(f) => Some(CommandOrPluginType::File(CommandOrPluginFile {
3038                path: f.path.display().to_string(),
3039                line_number: f.line_number.map(|n| n as i32),
3040                cwd: f.cwd.map(|c| c.display().to_string()),
3041            })),
3042        };
3043        Ok(ProtobufCommandOrPlugin {
3044            command_or_plugin_type,
3045        })
3046    }
3047}
3048
3049impl TryFrom<ProtobufTabLayoutInfo> for TabLayoutInfo {
3050    type Error = &'static str;
3051
3052    fn try_from(protobuf_tab: ProtobufTabLayoutInfo) -> Result<Self, Self::Error> {
3053        Ok(TabLayoutInfo {
3054            tab_index: protobuf_tab.tab_index as usize,
3055            tab_name: protobuf_tab.tab_name.filter(|s| !s.is_empty()),
3056            tiled_layout: protobuf_tab
3057                .tiled_layout
3058                .ok_or("missing tiled_layout")?
3059                .try_into()?,
3060            floating_layouts: protobuf_tab
3061                .floating_layouts
3062                .into_iter()
3063                .map(|l| l.try_into())
3064                .collect::<Result<Vec<_>, _>>()?,
3065            swap_tiled_layouts: if protobuf_tab.swap_tiled_layouts.is_empty() {
3066                None
3067            } else {
3068                Some(
3069                    protobuf_tab
3070                        .swap_tiled_layouts
3071                        .into_iter()
3072                        .map(|l| l.try_into())
3073                        .collect::<Result<Vec<_>, _>>()?,
3074                )
3075            },
3076            swap_floating_layouts: if protobuf_tab.swap_floating_layouts.is_empty() {
3077                None
3078            } else {
3079                Some(
3080                    protobuf_tab
3081                        .swap_floating_layouts
3082                        .into_iter()
3083                        .map(|l| l.try_into())
3084                        .collect::<Result<Vec<_>, _>>()?,
3085                )
3086            },
3087        })
3088    }
3089}
3090
3091impl TryFrom<TabLayoutInfo> for ProtobufTabLayoutInfo {
3092    type Error = &'static str;
3093
3094    fn try_from(tab_info: TabLayoutInfo) -> Result<Self, Self::Error> {
3095        Ok(ProtobufTabLayoutInfo {
3096            tab_index: tab_info.tab_index as u32,
3097            tab_name: tab_info.tab_name,
3098            tiled_layout: Some(tab_info.tiled_layout.try_into()?),
3099            floating_layouts: tab_info
3100                .floating_layouts
3101                .into_iter()
3102                .map(|l| l.try_into())
3103                .collect::<Result<Vec<_>, _>>()?,
3104            swap_tiled_layouts: tab_info
3105                .swap_tiled_layouts
3106                .unwrap_or_default()
3107                .into_iter()
3108                .map(|l| l.try_into())
3109                .collect::<Result<Vec<_>, _>>()?,
3110            swap_floating_layouts: tab_info
3111                .swap_floating_layouts
3112                .unwrap_or_default()
3113                .into_iter()
3114                .map(|l| l.try_into())
3115                .collect::<Result<Vec<_>, _>>()?,
3116        })
3117    }
3118}
3119
3120impl TryFrom<ProtobufTiledPaneLayout> for TiledPaneLayout {
3121    type Error = &'static str;
3122    fn try_from(protobuf: ProtobufTiledPaneLayout) -> Result<Self, Self::Error> {
3123        let children_split_direction =
3124            ProtobufSplitDirection::try_from(protobuf.children_split_direction)
3125                .ok()
3126                .ok_or("Invalid split direction")?
3127                .try_into()?;
3128        let children = protobuf
3129            .children
3130            .into_iter()
3131            .map(|c| c.try_into())
3132            .collect::<Result<Vec<_>, _>>()?;
3133        let split_size = protobuf.split_size.map(|s| s.try_into()).transpose()?;
3134        let run = protobuf.run.map(|r| r.try_into()).transpose()?;
3135        let focus = protobuf.focus.and_then(|f| {
3136            if f == "true" {
3137                Some(true)
3138            } else if f == "false" {
3139                Some(false)
3140            } else {
3141                None
3142            }
3143        });
3144        let run_instructions_to_ignore = vec![]; // Not serialized in protobuf
3145        Ok(TiledPaneLayout {
3146            children_split_direction,
3147            name: protobuf.name,
3148            children,
3149            split_size,
3150            run,
3151            borderless: Some(protobuf.borderless),
3152            focus,
3153            external_children_index: protobuf.external_children_index.map(|i| i as usize),
3154            children_are_stacked: protobuf.children_are_stacked,
3155            is_expanded_in_stack: protobuf.is_expanded_in_stack,
3156            exclude_from_sync: protobuf.exclude_from_sync,
3157            run_instructions_to_ignore,
3158            hide_floating_panes: protobuf.hide_floating_panes,
3159            pane_initial_contents: protobuf.pane_initial_contents,
3160            default_fg: None,
3161            default_bg: None,
3162        })
3163    }
3164}
3165
3166impl TryFrom<TiledPaneLayout> for ProtobufTiledPaneLayout {
3167    type Error = &'static str;
3168    fn try_from(internal: TiledPaneLayout) -> Result<Self, Self::Error> {
3169        let children_split_direction: ProtobufSplitDirection =
3170            internal.children_split_direction.try_into()?;
3171        let children = internal
3172            .children
3173            .into_iter()
3174            .map(|c| c.try_into())
3175            .collect::<Result<Vec<_>, _>>()?;
3176        let split_size = internal.split_size.map(|s| s.try_into()).transpose()?;
3177        let run = internal.run.map(|r| r.try_into()).transpose()?;
3178        let focus = internal.focus.map(|f| f.to_string());
3179        Ok(ProtobufTiledPaneLayout {
3180            children_split_direction: children_split_direction as i32,
3181            name: internal.name,
3182            children,
3183            split_size,
3184            run,
3185            borderless: internal.borderless.unwrap_or(false),
3186            focus,
3187            external_children_index: internal.external_children_index.map(|i| i as u32),
3188            children_are_stacked: internal.children_are_stacked,
3189            is_expanded_in_stack: internal.is_expanded_in_stack,
3190            exclude_from_sync: internal.exclude_from_sync,
3191            hide_floating_panes: internal.hide_floating_panes,
3192            pane_initial_contents: internal.pane_initial_contents,
3193        })
3194    }
3195}
3196
3197impl TryFrom<ProtobufFloatingPaneLayout> for FloatingPaneLayout {
3198    type Error = &'static str;
3199    fn try_from(protobuf: ProtobufFloatingPaneLayout) -> Result<Self, Self::Error> {
3200        let height = protobuf.height.map(|h| h.try_into()).transpose()?;
3201        let width = protobuf.width.map(|w| w.try_into()).transpose()?;
3202        let x = protobuf.x.map(|x| x.try_into()).transpose()?;
3203        let y = protobuf.y.map(|y| y.try_into()).transpose()?;
3204        let run = protobuf.run.map(|r| r.try_into()).transpose()?;
3205        Ok(FloatingPaneLayout {
3206            name: protobuf.name,
3207            height,
3208            width,
3209            x,
3210            y,
3211            pinned: protobuf.pinned,
3212            run,
3213            focus: protobuf.focus,
3214            already_running: protobuf.already_running,
3215            pane_initial_contents: protobuf.pane_initial_contents,
3216            logical_position: protobuf.logical_position.map(|p| p as usize),
3217            borderless: protobuf.borderless,
3218            default_fg: None,
3219            default_bg: None,
3220        })
3221    }
3222}
3223
3224impl TryFrom<FloatingPaneLayout> for ProtobufFloatingPaneLayout {
3225    type Error = &'static str;
3226    fn try_from(internal: FloatingPaneLayout) -> Result<Self, Self::Error> {
3227        let height = internal.height.map(|h| h.try_into()).transpose()?;
3228        let width = internal.width.map(|w| w.try_into()).transpose()?;
3229        let x = internal.x.map(|x| x.try_into()).transpose()?;
3230        let y = internal.y.map(|y| y.try_into()).transpose()?;
3231        let run = internal.run.map(|r| r.try_into()).transpose()?;
3232        Ok(ProtobufFloatingPaneLayout {
3233            name: internal.name,
3234            height,
3235            width,
3236            x,
3237            y,
3238            pinned: internal.pinned,
3239            run,
3240            focus: internal.focus,
3241            already_running: internal.already_running,
3242            pane_initial_contents: internal.pane_initial_contents,
3243            logical_position: internal.logical_position.map(|p| p as u32),
3244            borderless: internal.borderless,
3245        })
3246    }
3247}
3248
3249impl TryFrom<ProtobufLayoutConstraintTiledPair> for (LayoutConstraint, TiledPaneLayout) {
3250    type Error = &'static str;
3251    fn try_from(protobuf: ProtobufLayoutConstraintTiledPair) -> Result<Self, Self::Error> {
3252        let constraint = protobuf
3253            .constraint
3254            .ok_or("LayoutConstraintTiledPair must have constraint")?
3255            .try_into()?;
3256        let layout = protobuf
3257            .layout
3258            .ok_or("LayoutConstraintTiledPair must have layout")?
3259            .try_into()?;
3260        Ok((constraint, layout))
3261    }
3262}
3263
3264impl TryFrom<(LayoutConstraint, TiledPaneLayout)> for ProtobufLayoutConstraintTiledPair {
3265    type Error = &'static str;
3266    fn try_from(internal: (LayoutConstraint, TiledPaneLayout)) -> Result<Self, Self::Error> {
3267        Ok(ProtobufLayoutConstraintTiledPair {
3268            constraint: Some(internal.0.try_into()?),
3269            layout: Some(internal.1.try_into()?),
3270        })
3271    }
3272}
3273
3274impl TryFrom<ProtobufLayoutConstraintFloatingPair> for (LayoutConstraint, Vec<FloatingPaneLayout>) {
3275    type Error = &'static str;
3276    fn try_from(protobuf: ProtobufLayoutConstraintFloatingPair) -> Result<Self, Self::Error> {
3277        let constraint = protobuf
3278            .constraint
3279            .ok_or("LayoutConstraintFloatingPair must have constraint")?
3280            .try_into()?;
3281        let layouts = protobuf
3282            .layouts
3283            .into_iter()
3284            .map(|l| l.try_into())
3285            .collect::<Result<Vec<_>, _>>()?;
3286        Ok((constraint, layouts))
3287    }
3288}
3289
3290impl TryFrom<(LayoutConstraint, Vec<FloatingPaneLayout>)> for ProtobufLayoutConstraintFloatingPair {
3291    type Error = &'static str;
3292    fn try_from(
3293        internal: (LayoutConstraint, Vec<FloatingPaneLayout>),
3294    ) -> Result<Self, Self::Error> {
3295        Ok(ProtobufLayoutConstraintFloatingPair {
3296            constraint: Some(internal.0.try_into()?),
3297            layouts: internal
3298                .1
3299                .into_iter()
3300                .map(|l| l.try_into())
3301                .collect::<Result<Vec<_>, _>>()?,
3302        })
3303    }
3304}
3305
3306impl TryFrom<ProtobufSwapTiledLayout> for SwapTiledLayout {
3307    type Error = &'static str;
3308    fn try_from(protobuf: ProtobufSwapTiledLayout) -> Result<Self, Self::Error> {
3309        let constraint_map: BTreeMap<LayoutConstraint, TiledPaneLayout> = protobuf
3310            .constraint_map
3311            .into_iter()
3312            .map(|pair| pair.try_into())
3313            .collect::<Result<BTreeMap<_, _>, _>>()?;
3314        Ok((constraint_map, protobuf.name))
3315    }
3316}
3317
3318impl TryFrom<SwapTiledLayout> for ProtobufSwapTiledLayout {
3319    type Error = &'static str;
3320    fn try_from(internal: SwapTiledLayout) -> Result<Self, Self::Error> {
3321        let constraint_map = internal
3322            .0
3323            .into_iter()
3324            .map(|(constraint, layout)| (constraint, layout).try_into())
3325            .collect::<Result<Vec<_>, _>>()?;
3326        Ok(ProtobufSwapTiledLayout {
3327            constraint_map,
3328            name: internal.1,
3329        })
3330    }
3331}
3332
3333impl TryFrom<ProtobufSwapFloatingLayout> for SwapFloatingLayout {
3334    type Error = &'static str;
3335    fn try_from(protobuf: ProtobufSwapFloatingLayout) -> Result<Self, Self::Error> {
3336        let constraint_map: BTreeMap<LayoutConstraint, Vec<FloatingPaneLayout>> = protobuf
3337            .constraint_map
3338            .into_iter()
3339            .map(|pair| pair.try_into())
3340            .collect::<Result<BTreeMap<_, _>, _>>()?;
3341        Ok((constraint_map, protobuf.name))
3342    }
3343}
3344
3345impl TryFrom<SwapFloatingLayout> for ProtobufSwapFloatingLayout {
3346    type Error = &'static str;
3347    fn try_from(internal: SwapFloatingLayout) -> Result<Self, Self::Error> {
3348        let constraint_map = internal
3349            .0
3350            .into_iter()
3351            .map(|(constraint, layouts)| (constraint, layouts).try_into())
3352            .collect::<Result<Vec<_>, _>>()?;
3353        Ok(ProtobufSwapFloatingLayout {
3354            constraint_map,
3355            name: internal.1,
3356        })
3357    }
3358}
3359
3360#[cfg(test)]
3361mod tests {
3362    use super::*;
3363    use crate::input::options::PaneFrameStyle;
3364
3365    #[test]
3366    fn set_pane_frame_style_action_protobuf_round_trip() {
3367        for style in [
3368            PaneFrameStyle::Full,
3369            PaneFrameStyle::Titles,
3370            PaneFrameStyle::None,
3371        ] {
3372            let original = Action::SetPaneFrameStyle(style);
3373            let protobuf: ProtobufAction = original.clone().try_into().expect("encode");
3374            let decoded: Action = protobuf.try_into().expect("decode");
3375            assert_eq!(original, decoded);
3376        }
3377    }
3378
3379    #[test]
3380    fn osc133_actions_protobuf_round_trip() {
3381        for original in [
3382            Action::ScrollToPreviousPrompt,
3383            Action::ScrollToNextPrompt,
3384            Action::SelectCommandAtScrollPosition,
3385            Action::CopyLastCommandOutput,
3386        ] {
3387            let protobuf: ProtobufAction = original.clone().try_into().expect("encode");
3388            let decoded: Action = protobuf.try_into().expect("decode");
3389            assert_eq!(original, decoded);
3390        }
3391    }
3392}