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