Skip to main content

zellij_utils/plugin_api/
action.rs

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