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