Feature: Add pane names (#928)

* Read pane name from layout

* Update pane name at runtime

* Fix tests

* prefer and render pane name over pane title

* fix clippy errors

* fix after rebase
This commit is contained in:
Kunal Mohan 2021-12-09 23:30:40 +05:30 committed by GitHub
parent 368c852e57
commit c75bcbd937
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 233 additions and 22 deletions

View file

@ -279,7 +279,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart {
colored_elements, colored_elements,
separator, separator,
), ),
InputMode::Pane => key_indicators( InputMode::Pane | InputMode::RenamePane => key_indicators(
max_len, max_len,
&[ &[
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Lock), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Lock),

View file

@ -131,7 +131,8 @@ fn read_from_channel(
let last_snapshot = last_snapshot.clone(); let last_snapshot = last_snapshot.clone();
let cursor_coordinates = cursor_coordinates.clone(); let cursor_coordinates = cursor_coordinates.clone();
let mut vte_parser = vte::Parser::new(); let mut vte_parser = vte::Parser::new();
let mut terminal_output = TerminalPane::new(0, *pane_geom, Palette::default(), 0); // 0 is the pane index let mut terminal_output =
TerminalPane::new(0, *pane_geom, Palette::default(), 0, String::new()); // 0 is the pane index
let mut retries_left = 3; let mut retries_left = 3;
move || { move || {
let mut should_sleep = false; let mut should_sleep = false;

View file

@ -160,7 +160,7 @@ pub fn start_client(
client_attributes, client_attributes,
Box::new(opts), Box::new(opts),
Box::new(config_options.clone()), Box::new(config_options.clone()),
layout.unwrap(), Box::new(layout.unwrap()),
Some(config.plugins.clone()), Some(config.plugins.clone()),
) )
} }

View file

@ -58,7 +58,7 @@ pub enum ServerInstruction {
ClientAttributes, ClientAttributes,
Box<CliArgs>, Box<CliArgs>,
Box<Options>, Box<Options>,
LayoutFromYaml, Box<LayoutFromYaml>,
ClientId, ClientId,
Option<PluginsConfig>, Option<PluginsConfig>,
), ),
@ -531,7 +531,7 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
pub struct SessionOptions { pub struct SessionOptions {
pub opts: Box<CliArgs>, pub opts: Box<CliArgs>,
pub config_options: Box<Options>, pub config_options: Box<Options>,
pub layout: LayoutFromYaml, pub layout: Box<LayoutFromYaml>,
pub plugins: Option<PluginsConfig>, pub plugins: Option<PluginsConfig>,
} }

View file

@ -12,7 +12,7 @@ use crate::ClientId;
use zellij_utils::pane_size::Offset; use zellij_utils::pane_size::Offset;
use zellij_utils::position::Position; use zellij_utils::position::Position;
use zellij_utils::shared::ansi_len; use zellij_utils::shared::ansi_len;
use zellij_utils::zellij_tile::prelude::{Event, Mouse, PaletteColor}; use zellij_utils::zellij_tile::prelude::{Event, InputMode, Mouse, PaletteColor};
use zellij_utils::{ use zellij_utils::{
channels::SenderWithContext, channels::SenderWithContext,
pane_size::{Dimension, PaneGeom}, pane_size::{Dimension, PaneGeom},
@ -28,6 +28,7 @@ pub(crate) struct PluginPane {
pub send_plugin_instructions: SenderWithContext<PluginInstruction>, pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
pub active_at: Instant, pub active_at: Instant,
pub pane_title: String, pub pane_title: String,
pub pane_name: String,
frame: bool, frame: bool,
borderless: bool, borderless: bool,
} }
@ -38,6 +39,7 @@ impl PluginPane {
position_and_size: PaneGeom, position_and_size: PaneGeom,
send_plugin_instructions: SenderWithContext<PluginInstruction>, send_plugin_instructions: SenderWithContext<PluginInstruction>,
title: String, title: String,
pane_name: String,
) -> Self { ) -> Self {
Self { Self {
pid, pid,
@ -51,6 +53,7 @@ impl PluginPane {
content_offset: Offset::default(), content_offset: Offset::default(),
pane_title: title, pane_title: title,
borderless: false, borderless: false,
pane_name,
} }
} }
} }
@ -209,14 +212,29 @@ impl Pane for PluginPane {
None None
} }
} }
fn render_frame(&mut self, _client_id: ClientId, frame_params: FrameParams) -> Option<String> { fn render_frame(
&mut self,
_client_id: ClientId,
frame_params: FrameParams,
input_mode: InputMode,
) -> Option<String> {
// FIXME: This is a hack that assumes all fixed-size panes are borderless. This // FIXME: This is a hack that assumes all fixed-size panes are borderless. This
// will eventually need fixing! // will eventually need fixing!
if self.frame && !(self.geom.rows.is_fixed() || self.geom.cols.is_fixed()) { if self.frame && !(self.geom.rows.is_fixed() || self.geom.cols.is_fixed()) {
let pane_title = if self.pane_name.is_empty()
&& input_mode == InputMode::RenamePane
&& frame_params.is_main_client
{
String::from("Enter name...")
} else if self.pane_name.is_empty() {
self.pane_title.clone()
} else {
self.pane_name.clone()
};
let frame = PaneFrame::new( let frame = PaneFrame::new(
self.current_geom().into(), self.current_geom().into(),
(0, 0), // scroll position (0, 0), // scroll position
self.pane_title.clone(), pane_title,
frame_params, frame_params,
); );
Some(frame.render()) Some(frame.render())
@ -231,6 +249,20 @@ impl Pane for PluginPane {
) -> Option<String> { ) -> Option<String> {
None None
} }
fn update_name(&mut self, name: &str) {
match name {
"\0" => {
self.pane_name = String::new();
}
"\u{007F}" | "\u{0008}" => {
//delete and backspace keys
self.pane_name.pop();
}
c => {
self.pane_name.push_str(c);
}
}
}
fn pid(&self) -> PaneId { fn pid(&self) -> PaneId {
PaneId::Plugin(self.pid) PaneId::Plugin(self.pid)
} }

View file

@ -17,7 +17,7 @@ use zellij_utils::{
pane_size::{Dimension, PaneGeom}, pane_size::{Dimension, PaneGeom},
position::Position, position::Position,
vte, vte,
zellij_tile::data::{Palette, PaletteColor}, zellij_tile::data::{InputMode, Palette, PaletteColor},
}; };
pub const SELECTION_SCROLL_INTERVAL_MS: u64 = 10; pub const SELECTION_SCROLL_INTERVAL_MS: u64 = 10;
@ -44,6 +44,7 @@ pub struct TerminalPane {
selection_scrolled_at: time::Instant, selection_scrolled_at: time::Instant,
content_offset: Offset, content_offset: Offset,
pane_title: String, pane_title: String,
pane_name: String,
frame: HashMap<ClientId, PaneFrame>, frame: HashMap<ClientId, PaneFrame>,
borderless: bool, borderless: bool,
fake_cursor_locations: HashSet<(usize, usize)>, // (x, y) - these hold a record of previous fake cursors which we need to clear on render fake_cursor_locations: HashSet<(usize, usize)>, // (x, y) - these hold a record of previous fake cursors which we need to clear on render
@ -278,16 +279,31 @@ impl Pane for TerminalPane {
None None
} }
} }
fn render_frame(&mut self, client_id: ClientId, frame_params: FrameParams) -> Option<String> { fn render_frame(
&mut self,
client_id: ClientId,
frame_params: FrameParams,
input_mode: InputMode,
) -> Option<String> {
// TODO: remove the cursor stuff from here // TODO: remove the cursor stuff from here
let mut vte_output = None; let mut vte_output = None;
let frame = PaneFrame::new( let pane_title = if self.pane_name.is_empty()
self.current_geom().into(), && input_mode == InputMode::RenamePane
self.grid.scrollback_position_and_length(), && frame_params.is_main_client
{
String::from("Enter name...")
} else if self.pane_name.is_empty() {
self.grid self.grid
.title .title
.clone() .clone()
.unwrap_or_else(|| self.pane_title.clone()), .unwrap_or_else(|| self.pane_title.clone())
} else {
self.pane_name.clone()
};
let frame = PaneFrame::new(
self.current_geom().into(),
self.grid.scrollback_position_and_length(),
pane_title,
frame_params, frame_params,
); );
match self.frame.get(&client_id) { match self.frame.get(&client_id) {
@ -336,6 +352,20 @@ impl Pane for TerminalPane {
} }
vte_output vte_output
} }
fn update_name(&mut self, name: &str) {
match name {
"\0" => {
self.pane_name = String::new();
}
"\u{007F}" | "\u{0008}" => {
//delete and backspace keys
self.pane_name.pop();
}
c => {
self.pane_name.push_str(c);
}
}
}
fn pid(&self) -> PaneId { fn pid(&self) -> PaneId {
PaneId::Terminal(self.pid) PaneId::Terminal(self.pid)
} }
@ -475,6 +505,7 @@ impl TerminalPane {
position_and_size: PaneGeom, position_and_size: PaneGeom,
palette: Palette, palette: Palette,
pane_index: usize, pane_index: usize,
pane_name: String,
) -> TerminalPane { ) -> TerminalPane {
let initial_pane_title = format!("Pane #{}", pane_index); let initial_pane_title = format!("Pane #{}", pane_index);
let grid = Grid::new( let grid = Grid::new(
@ -495,6 +526,7 @@ impl TerminalPane {
colors: palette, colors: palette,
selection_scrolled_at: time::Instant::now(), selection_scrolled_at: time::Instant::now(),
pane_title: initial_pane_title, pane_title: initial_pane_title,
pane_name,
borderless: false, borderless: false,
fake_cursor_locations: HashSet::new(), fake_cursor_locations: HashSet::new(),
} }

View file

@ -14,7 +14,7 @@ pub fn scrolling_inside_a_pane() {
let pid = 1; let pid = 1;
let palette = Palette::default(); let palette = Palette::default();
let mut terminal_pane = TerminalPane::new(pid, fake_win_size, palette, 0); // 0 is the pane index let mut terminal_pane = TerminalPane::new(pid, fake_win_size, palette, 0, String::new()); // 0 is the pane index
let mut text_to_fill_pane = String::new(); let mut text_to_fill_pane = String::new();
for i in 0..30 { for i in 0..30 {
writeln!(&mut text_to_fill_pane, "\rline {}", i + 1).unwrap(); writeln!(&mut text_to_fill_pane, "\rline {}", i + 1).unwrap();

View file

@ -74,7 +74,7 @@ pub(crate) struct Pty {
use std::convert::TryFrom; use std::convert::TryFrom;
pub(crate) fn pty_thread_main(mut pty: Pty, layout: LayoutFromYaml) { pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<LayoutFromYaml>) {
loop { loop {
let (event, mut err_ctx) = pty.bus.recv().expect("failed to receive event on channel"); let (event, mut err_ctx) = pty.bus.recv().expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Pty((&event).into())); err_ctx.add_call(ContextType::Pty((&event).into()));

View file

@ -226,6 +226,12 @@ fn route_action(
}; };
session.senders.send_to_pty(pty_instr).unwrap(); session.senders.send_to_pty(pty_instr).unwrap();
} }
Action::PaneNameInput(c) => {
session
.senders
.send_to_screen(ScreenInstruction::UpdatePaneName(c, client_id))
.unwrap();
}
Action::Run(command) => { Action::Run(command) => {
let run_cmd = Some(TerminalAction::RunCommand(command.clone().into())); let run_cmd = Some(TerminalAction::RunCommand(command.clone().into()));
let pty_instr = match command.direction { let pty_instr = match command.direction {

View file

@ -68,6 +68,7 @@ pub enum ScreenInstruction {
TogglePaneFrames, TogglePaneFrames,
SetSelectable(PaneId, bool, usize), SetSelectable(PaneId, bool, usize),
ClosePane(PaneId, Option<ClientId>), ClosePane(PaneId, Option<ClientId>),
UpdatePaneName(Vec<u8>, ClientId),
NewTab(Layout, Vec<RawFd>, ClientId), NewTab(Layout, Vec<RawFd>, ClientId),
SwitchTabNext(ClientId), SwitchTabNext(ClientId),
SwitchTabPrev(ClientId), SwitchTabPrev(ClientId),
@ -140,6 +141,7 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::TogglePaneFrames => ScreenContext::TogglePaneFrames, ScreenInstruction::TogglePaneFrames => ScreenContext::TogglePaneFrames,
ScreenInstruction::SetSelectable(..) => ScreenContext::SetSelectable, ScreenInstruction::SetSelectable(..) => ScreenContext::SetSelectable,
ScreenInstruction::ClosePane(..) => ScreenContext::ClosePane, ScreenInstruction::ClosePane(..) => ScreenContext::ClosePane,
ScreenInstruction::UpdatePaneName(..) => ScreenContext::UpdatePaneName,
ScreenInstruction::NewTab(..) => ScreenContext::NewTab, ScreenInstruction::NewTab(..) => ScreenContext::NewTab,
ScreenInstruction::SwitchTabNext(..) => ScreenContext::SwitchTabNext, ScreenInstruction::SwitchTabNext(..) => ScreenContext::SwitchTabNext,
ScreenInstruction::SwitchTabPrev(..) => ScreenContext::SwitchTabPrev, ScreenInstruction::SwitchTabPrev(..) => ScreenContext::SwitchTabPrev,
@ -975,6 +977,14 @@ pub(crate) fn screen_thread_main(
} }
screen.update_tabs(); screen.update_tabs();
} }
ScreenInstruction::UpdatePaneName(c, client_id) => {
screen
.get_active_tab_mut(client_id)
.unwrap()
.update_active_pane_name(c, client_id);
screen.render();
}
ScreenInstruction::ToggleActiveTerminalFullscreen(client_id) => { ScreenInstruction::ToggleActiveTerminalFullscreen(client_id) => {
screen screen
.get_active_tab_mut(client_id) .get_active_tab_mut(client_id)

View file

@ -23,8 +23,9 @@ use std::time::Instant;
use std::{ use std::{
cmp::Reverse, cmp::Reverse,
collections::{BTreeMap, HashMap, HashSet}, collections::{BTreeMap, HashMap, HashSet},
str,
}; };
use zellij_tile::data::{Event, ModeInfo, Palette, PaletteColor}; use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, PaletteColor};
use zellij_utils::{ use zellij_utils::{
input::{ input::{
layout::{Direction, Layout, Run}, layout::{Direction, Layout, Run},
@ -181,12 +182,18 @@ pub trait Pane {
fn selectable(&self) -> bool; fn selectable(&self) -> bool;
fn set_selectable(&mut self, selectable: bool); fn set_selectable(&mut self, selectable: bool);
fn render(&mut self, client_id: Option<ClientId>) -> Option<String>; fn render(&mut self, client_id: Option<ClientId>) -> Option<String>;
fn render_frame(&mut self, client_id: ClientId, frame_params: FrameParams) -> Option<String>; fn render_frame(
&mut self,
client_id: ClientId,
frame_params: FrameParams,
input_mode: InputMode,
) -> Option<String>;
fn render_fake_cursor( fn render_fake_cursor(
&mut self, &mut self,
cursor_color: PaletteColor, cursor_color: PaletteColor,
text_color: PaletteColor, text_color: PaletteColor,
) -> Option<String>; ) -> Option<String>;
fn update_name(&mut self, name: &str);
fn pid(&self) -> PaneId; fn pid(&self) -> PaneId;
fn reduce_height(&mut self, percent: f64); fn reduce_height(&mut self, percent: f64);
fn increase_height(&mut self, percent: f64); fn increase_height(&mut self, percent: f64);
@ -400,6 +407,7 @@ impl Tab {
*position_and_size, *position_and_size,
self.senders.to_plugin.as_ref().unwrap().clone(), self.senders.to_plugin.as_ref().unwrap().clone(),
pane_title, pane_title,
layout.pane_name.clone().unwrap_or_default(),
); );
new_plugin.set_borderless(layout.borderless); new_plugin.set_borderless(layout.borderless);
self.panes.insert(PaneId::Plugin(pid), Box::new(new_plugin)); self.panes.insert(PaneId::Plugin(pid), Box::new(new_plugin));
@ -412,6 +420,7 @@ impl Tab {
*position_and_size, *position_and_size,
self.colors, self.colors,
next_terminal_position, next_terminal_position,
layout.pane_name.clone().unwrap_or_default(),
); );
new_pane.set_borderless(layout.borderless); new_pane.set_borderless(layout.borderless);
self.panes self.panes
@ -588,6 +597,7 @@ impl Tab {
bottom_winsize, bottom_winsize,
self.colors, self.colors,
next_terminal_position, next_terminal_position,
String::new(),
); );
terminal_to_split.set_geom(top_winsize); terminal_to_split.set_geom(top_winsize);
self.panes.insert(pid, Box::new(new_terminal)); self.panes.insert(pid, Box::new(new_terminal));
@ -604,6 +614,7 @@ impl Tab {
right_winsize, right_winsize,
self.colors, self.colors,
next_terminal_position, next_terminal_position,
String::new(),
); );
terminal_to_split.set_geom(left_winsize); terminal_to_split.set_geom(left_winsize);
self.panes.insert(pid, Box::new(new_terminal)); self.panes.insert(pid, Box::new(new_terminal));
@ -647,6 +658,7 @@ impl Tab {
bottom_winsize, bottom_winsize,
self.colors, self.colors,
next_terminal_position, next_terminal_position,
String::new(),
); );
active_pane.set_geom(top_winsize); active_pane.set_geom(top_winsize);
self.panes.insert(pid, Box::new(new_terminal)); self.panes.insert(pid, Box::new(new_terminal));
@ -684,8 +696,13 @@ impl Tab {
} }
let terminal_ws = active_pane.position_and_size(); let terminal_ws = active_pane.position_and_size();
if let Some((left_winsize, right_winsize)) = split(Direction::Vertical, &terminal_ws) { if let Some((left_winsize, right_winsize)) = split(Direction::Vertical, &terminal_ws) {
let new_terminal = let new_terminal = TerminalPane::new(
TerminalPane::new(term_pid, right_winsize, self.colors, next_terminal_position); term_pid,
right_winsize,
self.colors,
next_terminal_position,
String::new(),
);
active_pane.set_geom(left_winsize); active_pane.set_geom(left_winsize);
self.panes.insert(pid, Box::new(new_terminal)); self.panes.insert(pid, Box::new(new_terminal));
} }
@ -3451,6 +3468,17 @@ impl Tab {
.unwrap(); .unwrap();
} }
} }
pub fn update_active_pane_name(&mut self, buf: Vec<u8>, client_id: ClientId) {
if let Some(active_terminal_id) = self.get_active_terminal_id(client_id) {
let s = str::from_utf8(&buf).unwrap();
let active_terminal = self
.panes
.get_mut(&PaneId::Terminal(active_terminal_id))
.unwrap();
active_terminal.update_name(s);
}
}
} }
#[allow(clippy::borrowed_box)] #[allow(clippy::borrowed_box)]

View file

@ -132,7 +132,7 @@ impl<'a> PaneContentsAndUi<'a> {
other_cursors_exist_in_session: self.multiple_users_exist_in_session, other_cursors_exist_in_session: self.multiple_users_exist_in_session,
} }
}; };
if let Some(vte_output) = self.pane.render_frame(client_id, frame_params) { if let Some(vte_output) = self.pane.render_frame(client_id, frame_params, client_mode) {
// FIXME: Use Termion for cursor and style clearing? // FIXME: Use Termion for cursor and style clearing?
self.output.push_to_client( self.output.push_to_client(
client_id, client_id,

View file

@ -77,8 +77,12 @@ pub enum InputMode {
/// `Scroll` mode allows scrolling up and down within a pane. /// `Scroll` mode allows scrolling up and down within a pane.
#[serde(alias = "scroll")] #[serde(alias = "scroll")]
Scroll, Scroll,
/// `RenameTab` mode allows assigning a new name to a tab.
#[serde(alias = "renametab")] #[serde(alias = "renametab")]
RenameTab, RenameTab,
/// `RenamePane` mode allows assigning a new name to a pane.
#[serde(alias = "renamepane")]
RenamePane,
/// `Session` mode allows detaching sessions /// `Session` mode allows detaching sessions
#[serde(alias = "session")] #[serde(alias = "session")]
Session, Session,
@ -133,6 +137,7 @@ impl FromStr for InputMode {
"session" => Ok(InputMode::Session), "session" => Ok(InputMode::Session),
"move" => Ok(InputMode::Move), "move" => Ok(InputMode::Move),
"prompt" => Ok(InputMode::Prompt), "prompt" => Ok(InputMode::Prompt),
"renamepane" => Ok(InputMode::RenamePane),
e => Err(e.to_string().into()), e => Err(e.to_string().into()),
} }
} }

View file

@ -135,6 +135,8 @@ keybinds:
key: [ Alt: '[',] key: [ Alt: '[',]
- action: [FocusNextPane,] - action: [FocusNextPane,]
key: [ Alt: ']',] key: [ Alt: ']',]
- action: [SwitchToMode: RenamePane, PaneNameInput: [0],]
key: [Char: 'c']
move: move:
- action: [SwitchToMode: Locked,] - action: [SwitchToMode: Locked,]
key: [Ctrl: 'g'] key: [Ctrl: 'g']
@ -304,6 +306,27 @@ keybinds:
key: [ Alt: '[',] key: [ Alt: '[',]
- action: [FocusNextPane,] - action: [FocusNextPane,]
key: [ Alt: ']',] key: [ Alt: ']',]
renamepane:
- action: [SwitchToMode: Normal,]
key: [Ctrl: 'c', Ctrl: 's', Char: ' ',]
- action: [SwitchToMode: Pane,]
key: [Char: "\n",]
- action: [PaneNameInput: [27] , SwitchToMode: Pane,]
key: [Esc,]
- action: [NewPane: ,]
key: [ Alt: 'n',]
- action: [MoveFocus: Left,]
key: [ Alt: 'h',]
- action: [MoveFocus: Right,]
key: [ Alt: 'l',]
- action: [MoveFocus: Down,]
key: [ Alt: 'j',]
- action: [MoveFocus: Up,]
key: [ Alt: 'k',]
- action: [FocusPreviousPane,]
key: [ Alt: '[',]
- action: [FocusNextPane,]
key: [ Alt: ']',]
session: session:
- action: [SwitchToMode: Locked,] - action: [SwitchToMode: Locked,]
key: [Ctrl: 'g'] key: [Ctrl: 'g']

View file

@ -251,6 +251,7 @@ pub enum ScreenContext {
SetFixedHeight, SetFixedHeight,
SetFixedWidth, SetFixedWidth,
ClosePane, ClosePane,
UpdatePaneName,
NewTab, NewTab,
SwitchTabNext, SwitchTabNext,
SwitchTabPrev, SwitchTabPrev,

View file

@ -83,6 +83,7 @@ pub enum Action {
NewPane(Option<Direction>), NewPane(Option<Direction>),
/// Close the focus pane. /// Close the focus pane.
CloseFocus, CloseFocus,
PaneNameInput(Vec<u8>),
/// Create a new tab, optionally with a specified tab layout. /// Create a new tab, optionally with a specified tab layout.
NewTab(Option<TabLayout>), NewTab(Option<TabLayout>),
/// Do nothing. /// Do nothing.

View file

@ -209,6 +209,7 @@ impl Keybinds {
mode_keybind_or_action(Action::Write(raw_bytes)) mode_keybind_or_action(Action::Write(raw_bytes))
} }
InputMode::RenameTab => mode_keybind_or_action(Action::TabNameInput(raw_bytes)), InputMode::RenameTab => mode_keybind_or_action(Action::TabNameInput(raw_bytes)),
InputMode::RenamePane => mode_keybind_or_action(Action::PaneNameInput(raw_bytes)),
_ => mode_keybind_or_action(Action::NoOp), _ => mode_keybind_or_action(Action::NoOp),
} }
} }

View file

@ -133,6 +133,8 @@ impl fmt::Display for RunPluginLocation {
pub struct Layout { pub struct Layout {
pub direction: Direction, pub direction: Direction,
#[serde(default)] #[serde(default)]
pub pane_name: Option<String>,
#[serde(default)]
pub parts: Vec<Layout>, pub parts: Vec<Layout>,
pub split_size: Option<SplitSize>, pub split_size: Option<SplitSize>,
pub run: Option<Run>, pub run: Option<Run>,
@ -411,6 +413,8 @@ fn default_as_some_true() -> Option<bool> {
pub struct LayoutTemplate { pub struct LayoutTemplate {
pub direction: Direction, pub direction: Direction,
#[serde(default)] #[serde(default)]
pub pane_name: Option<String>,
#[serde(default)]
pub borderless: bool, pub borderless: bool,
#[serde(default)] #[serde(default)]
pub parts: Vec<LayoutTemplate>, pub parts: Vec<LayoutTemplate>,
@ -454,6 +458,7 @@ impl LayoutTemplate {
pub struct TabLayout { pub struct TabLayout {
#[serde(default)] #[serde(default)]
pub direction: Direction, pub direction: Direction,
pub pane_name: Option<String>,
#[serde(default)] #[serde(default)]
pub borderless: bool, pub borderless: bool,
#[serde(default)] #[serde(default)]
@ -703,6 +708,7 @@ impl TryFrom<TabLayout> for Layout {
fn try_from(tab: TabLayout) -> Result<Self, Self::Error> { fn try_from(tab: TabLayout) -> Result<Self, Self::Error> {
Ok(Layout { Ok(Layout {
direction: tab.direction, direction: tab.direction,
pane_name: tab.pane_name,
borderless: tab.borderless, borderless: tab.borderless,
parts: Self::from_vec_tab_layout(tab.parts)?, parts: Self::from_vec_tab_layout(tab.parts)?,
split_size: tab.split_size, split_size: tab.split_size,
@ -715,6 +721,7 @@ impl From<TabLayout> for LayoutTemplate {
fn from(tab: TabLayout) -> Self { fn from(tab: TabLayout) -> Self {
Self { Self {
direction: tab.direction, direction: tab.direction,
pane_name: tab.pane_name,
borderless: tab.borderless, borderless: tab.borderless,
parts: Self::from_vec_tab_layout(tab.parts), parts: Self::from_vec_tab_layout(tab.parts),
body: false, body: false,
@ -730,6 +737,7 @@ impl TryFrom<LayoutTemplate> for Layout {
fn try_from(template: LayoutTemplate) -> Result<Self, Self::Error> { fn try_from(template: LayoutTemplate) -> Result<Self, Self::Error> {
Ok(Layout { Ok(Layout {
direction: template.direction, direction: template.direction,
pane_name: template.pane_name,
borderless: template.borderless, borderless: template.borderless,
parts: Self::from_vec_template_layout(template.parts)?, parts: Self::from_vec_template_layout(template.parts)?,
split_size: template.split_size, split_size: template.split_size,
@ -752,6 +760,7 @@ impl Default for TabLayout {
split_size: None, split_size: None,
run: None, run: None,
name: String::new(), name: String::new(),
pane_name: None,
} }
} }
} }
@ -760,10 +769,12 @@ impl Default for LayoutTemplate {
fn default() -> Self { fn default() -> Self {
Self { Self {
direction: Direction::Horizontal, direction: Direction::Horizontal,
pane_name: None,
body: false, body: false,
borderless: false, borderless: false,
parts: vec![LayoutTemplate { parts: vec![LayoutTemplate {
direction: Direction::Horizontal, direction: Direction::Horizontal,
pane_name: None,
body: true, body: true,
borderless: false, borderless: false,
split_size: None, split_size: None,

View file

@ -40,6 +40,7 @@ pub fn get_mode_info(
("x".to_string(), "Close".to_string()), ("x".to_string(), "Close".to_string()),
("f".to_string(), "Fullscreen".to_string()), ("f".to_string(), "Fullscreen".to_string()),
("z".to_string(), "Frames".to_string()), ("z".to_string(), "Frames".to_string()),
("c".to_string(), "Rename".to_string()),
], ],
InputMode::Tab => vec![ InputMode::Tab => vec![
("←↓↑→".to_string(), "Move focus".to_string()), ("←↓↑→".to_string(), "Move focus".to_string()),
@ -55,6 +56,7 @@ pub fn get_mode_info(
("u/d".to_string(), "Scroll Half Page".to_string()), ("u/d".to_string(), "Scroll Half Page".to_string()),
], ],
InputMode::RenameTab => vec![("Enter".to_string(), "when done".to_string())], InputMode::RenameTab => vec![("Enter".to_string(), "when done".to_string())],
InputMode::RenamePane => vec![("Enter".to_string(), "when done".to_string())],
InputMode::Session => vec![("d".to_string(), "Detach".to_string())], InputMode::Session => vec![("d".to_string(), "Detach".to_string())],
}; };

View file

@ -40,10 +40,12 @@ fn default_layout_merged_correctly() {
let merged_layout = Layout { let merged_layout = Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![ parts: vec![
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: true, borderless: true,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Fixed(1)), split_size: Some(SplitSize::Fixed(1)),
run: Some(Run::Plugin(RunPlugin { run: Some(Run::Plugin(RunPlugin {
@ -54,6 +56,7 @@ fn default_layout_merged_correctly() {
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: None, split_size: None,
run: None, run: None,
@ -61,6 +64,7 @@ fn default_layout_merged_correctly() {
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: true, borderless: true,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Fixed(2)), split_size: Some(SplitSize::Fixed(2)),
run: Some(Run::Plugin(RunPlugin { run: Some(Run::Plugin(RunPlugin {
@ -84,10 +88,12 @@ fn default_layout_new_tab_correct() {
let merged_layout = Layout { let merged_layout = Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![ parts: vec![
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: true, borderless: true,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Fixed(1)), split_size: Some(SplitSize::Fixed(1)),
run: Some(Run::Plugin(RunPlugin { run: Some(Run::Plugin(RunPlugin {
@ -98,6 +104,7 @@ fn default_layout_new_tab_correct() {
Layout { Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: None, split_size: None,
run: None, run: None,
@ -105,6 +112,7 @@ fn default_layout_new_tab_correct() {
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: true, borderless: true,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Fixed(2)), split_size: Some(SplitSize::Fixed(2)),
run: Some(Run::Plugin(RunPlugin { run: Some(Run::Plugin(RunPlugin {
@ -168,13 +176,16 @@ fn three_panes_with_tab_merged_correctly() {
let merged_layout = Layout { let merged_layout = Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![Layout { parts: vec![Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: false, borderless: false,
pane_name: None,
parts: vec![ parts: vec![
Layout { Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Percent(50.0)), split_size: Some(SplitSize::Percent(50.0)),
run: None, run: None,
@ -182,10 +193,12 @@ fn three_panes_with_tab_merged_correctly() {
Layout { Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![ parts: vec![
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Percent(50.0)), split_size: Some(SplitSize::Percent(50.0)),
run: None, run: None,
@ -193,6 +206,7 @@ fn three_panes_with_tab_merged_correctly() {
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Percent(50.0)), split_size: Some(SplitSize::Percent(50.0)),
run: None, run: None,
@ -220,9 +234,11 @@ fn three_panes_with_tab_new_tab_is_correct() {
let merged_layout = Layout { let merged_layout = Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![Layout { parts: vec![Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: None, split_size: None,
run: None, run: None,
@ -260,10 +276,12 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() {
let merged_layout = Layout { let merged_layout = Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![ parts: vec![
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Fixed(1)), split_size: Some(SplitSize::Fixed(1)),
run: Some(Run::Plugin(RunPlugin { run: Some(Run::Plugin(RunPlugin {
@ -274,10 +292,12 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() {
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: false, borderless: false,
pane_name: None,
parts: vec![ parts: vec![
Layout { Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Percent(50.0)), split_size: Some(SplitSize::Percent(50.0)),
run: None, run: None,
@ -285,10 +305,12 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() {
Layout { Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![ parts: vec![
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Percent(50.0)), split_size: Some(SplitSize::Percent(50.0)),
run: None, run: None,
@ -296,6 +318,7 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() {
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Percent(50.0)), split_size: Some(SplitSize::Percent(50.0)),
run: None, run: None,
@ -311,6 +334,7 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() {
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Fixed(2)), split_size: Some(SplitSize::Fixed(2)),
run: Some(Run::Plugin(RunPlugin { run: Some(Run::Plugin(RunPlugin {
@ -334,10 +358,12 @@ fn three_panes_with_tab_and_default_plugins_new_tab_is_correct() {
let merged_layout = Layout { let merged_layout = Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![ parts: vec![
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Fixed(1)), split_size: Some(SplitSize::Fixed(1)),
run: Some(Run::Plugin(RunPlugin { run: Some(Run::Plugin(RunPlugin {
@ -348,6 +374,7 @@ fn three_panes_with_tab_and_default_plugins_new_tab_is_correct() {
Layout { Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: None, split_size: None,
run: None, run: None,
@ -355,6 +382,7 @@ fn three_panes_with_tab_and_default_plugins_new_tab_is_correct() {
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Fixed(2)), split_size: Some(SplitSize::Fixed(2)),
run: Some(Run::Plugin(RunPlugin { run: Some(Run::Plugin(RunPlugin {
@ -396,14 +424,17 @@ fn deeply_nested_tab_merged_correctly() {
let merged_layout = Layout { let merged_layout = Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![ parts: vec![
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: false, borderless: false,
pane_name: None,
parts: vec![ parts: vec![
Layout { Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Percent(21.0)), split_size: Some(SplitSize::Percent(21.0)),
run: None, run: None,
@ -411,10 +442,12 @@ fn deeply_nested_tab_merged_correctly() {
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: false, borderless: false,
pane_name: None,
parts: vec![ parts: vec![
Layout { Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Percent(22.0)), split_size: Some(SplitSize::Percent(22.0)),
run: None, run: None,
@ -422,10 +455,12 @@ fn deeply_nested_tab_merged_correctly() {
Layout { Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![ parts: vec![
Layout { Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Percent(23.0)), split_size: Some(SplitSize::Percent(23.0)),
run: None, run: None,
@ -433,6 +468,7 @@ fn deeply_nested_tab_merged_correctly() {
Layout { Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Percent(24.0)), split_size: Some(SplitSize::Percent(24.0)),
run: None, run: None,
@ -452,6 +488,7 @@ fn deeply_nested_tab_merged_correctly() {
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Percent(15.0)), split_size: Some(SplitSize::Percent(15.0)),
run: None, run: None,
@ -459,6 +496,7 @@ fn deeply_nested_tab_merged_correctly() {
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Percent(15.0)), split_size: Some(SplitSize::Percent(15.0)),
run: None, run: None,
@ -466,6 +504,7 @@ fn deeply_nested_tab_merged_correctly() {
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Percent(15.0)), split_size: Some(SplitSize::Percent(15.0)),
run: None, run: None,
@ -504,10 +543,12 @@ fn three_tabs_tab_one_merged_correctly() {
let merged_layout = Layout { let merged_layout = Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: false, borderless: false,
pane_name: None,
parts: vec![ parts: vec![
Layout { Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Percent(50.0)), split_size: Some(SplitSize::Percent(50.0)),
run: None, run: None,
@ -515,6 +556,7 @@ fn three_tabs_tab_one_merged_correctly() {
Layout { Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: None, split_size: None,
run: None, run: None,
@ -539,14 +581,17 @@ fn three_tabs_tab_two_merged_correctly() {
let merged_layout = Layout { let merged_layout = Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: false, borderless: false,
pane_name: None,
parts: vec![ parts: vec![
Layout { Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![ parts: vec![
Layout { Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Percent(50.0)), split_size: Some(SplitSize::Percent(50.0)),
run: None, run: None,
@ -554,6 +599,7 @@ fn three_tabs_tab_two_merged_correctly() {
Layout { Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: None, split_size: None,
run: None, run: None,
@ -565,6 +611,7 @@ fn three_tabs_tab_two_merged_correctly() {
Layout { Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: None, split_size: None,
run: None, run: None,
@ -589,14 +636,17 @@ fn three_tabs_tab_three_merged_correctly() {
let merged_layout = Layout { let merged_layout = Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: false, borderless: false,
pane_name: None,
parts: vec![ parts: vec![
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: false, borderless: false,
pane_name: None,
parts: vec![ parts: vec![
Layout { Layout {
direction: Direction::Vertical, direction: Direction::Vertical,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: Some(SplitSize::Percent(50.0)), split_size: Some(SplitSize::Percent(50.0)),
run: None, run: None,
@ -604,6 +654,7 @@ fn three_tabs_tab_three_merged_correctly() {
Layout { Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: None, split_size: None,
run: None, run: None,
@ -615,6 +666,7 @@ fn three_tabs_tab_three_merged_correctly() {
Layout { Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: None, split_size: None,
run: None, run: None,
@ -650,9 +702,11 @@ fn no_tabs_merged_correctly() {
let merged_layout = Layout { let merged_layout = Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![Layout { parts: vec![Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
borderless: false, borderless: false,
pane_name: None,
parts: vec![], parts: vec![],
split_size: None, split_size: None,
run: None, run: None,
@ -699,6 +753,7 @@ fn no_layout_template_merged_correctly() {
split_size: None, split_size: None,
run: None, run: None,
borderless: false, borderless: false,
pane_name: None,
}, },
Layout { Layout {
direction: Direction::Horizontal, direction: Direction::Horizontal,
@ -706,15 +761,18 @@ fn no_layout_template_merged_correctly() {
split_size: None, split_size: None,
run: None, run: None,
borderless: false, borderless: false,
pane_name: None,
}, },
], ],
split_size: None, split_size: None,
run: None, run: None,
borderless: false, borderless: false,
pane_name: None,
}], }],
split_size: None, split_size: None,
run: None, run: None,
borderless: false, borderless: false,
pane_name: None,
}; };
assert_eq!(merged_layout, tab_layout.try_into().unwrap()); assert_eq!(merged_layout, tab_layout.try_into().unwrap());

View file

@ -62,7 +62,7 @@ pub enum ClientToServerMsg {
ClientAttributes, ClientAttributes,
Box<CliArgs>, Box<CliArgs>,
Box<Options>, Box<Options>,
LayoutFromYaml, Box<LayoutFromYaml>,
Option<PluginsConfig>, Option<PluginsConfig>,
), ),
AttachClient(ClientAttributes, Options), AttachClient(ClientAttributes, Options),