mirror of
https://github.com/wez/wezterm.git
synced 2024-12-22 21:01:36 +03:00
Add SplitPane assignment
This, along with the plumbing included here, allows specifying the destination of the split (now you can specify top/left, whereas previously it was limited to right/bottom), as well as the size of the split, and also whether the split targets the node at the top level of the tab rather than the active pane--that is referred to as full-width in tmux terminology. https://github.com/wez/wezterm/issues/578
This commit is contained in:
parent
d2d4257f79
commit
ecd05547d5
@ -189,6 +189,14 @@ impl<L, N> Tree<L, N> {
|
||||
path: Box::new(Path::Top),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn num_leaves(&self) -> usize {
|
||||
match self {
|
||||
Self::Empty => 0,
|
||||
Self::Leaf(_) => 1,
|
||||
Self::Node { left, right, .. } => left.num_leaves() + right.num_leaves(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, N> Cursor<L, N> {
|
||||
@ -313,6 +321,34 @@ impl<L, N> Cursor<L, N> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn split_node_and_insert_left(self, to_insert: L) -> Result<Self, Self> {
|
||||
match *self.it {
|
||||
Tree::Node { left, right, data } => Ok(Self {
|
||||
it: Box::new(Tree::Node {
|
||||
data: None,
|
||||
right: Box::new(Tree::Node { left, right, data }),
|
||||
left: Box::new(Tree::Leaf(to_insert)),
|
||||
}),
|
||||
path: self.path,
|
||||
}),
|
||||
_ => Err(self),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn split_node_and_insert_right(self, to_insert: L) -> Result<Self, Self> {
|
||||
match *self.it {
|
||||
Tree::Node { left, right, data } => Ok(Self {
|
||||
it: Box::new(Tree::Node {
|
||||
data: None,
|
||||
left: Box::new(Tree::Node { left, right, data }),
|
||||
right: Box::new(Tree::Leaf(to_insert)),
|
||||
}),
|
||||
path: self.path,
|
||||
}),
|
||||
_ => Err(self),
|
||||
}
|
||||
}
|
||||
|
||||
/// If the current position is a leaf, split it into a Node where
|
||||
/// the left side holds the current leaf value and the right side
|
||||
/// holds the provided `right` value.
|
||||
|
@ -15,7 +15,7 @@ use anyhow::{bail, Context as _, Error};
|
||||
use mux::client::{ClientId, ClientInfo};
|
||||
use mux::pane::PaneId;
|
||||
use mux::renderable::{RenderableDimensions, StableCursorPosition};
|
||||
use mux::tab::{PaneNode, SerdeUrl, SplitDirection, TabId};
|
||||
use mux::tab::{PaneNode, SerdeUrl, SplitRequest, TabId};
|
||||
use mux::window::WindowId;
|
||||
use portable_pty::{CommandBuilder, PtySize};
|
||||
use rangeset::*;
|
||||
@ -416,7 +416,7 @@ macro_rules! pdu {
|
||||
/// The overall version of the codec.
|
||||
/// This must be bumped when backwards incompatible changes
|
||||
/// are made to the types and protocol.
|
||||
pub const CODEC_VERSION: usize = 22;
|
||||
pub const CODEC_VERSION: usize = 23;
|
||||
|
||||
// Defines the Pdu enum.
|
||||
// Each struct has an explicit identifying number.
|
||||
@ -592,7 +592,7 @@ pub struct ListPanesResponse {
|
||||
#[derive(Deserialize, Serialize, PartialEq, Debug)]
|
||||
pub struct SplitPane {
|
||||
pub pane_id: PaneId,
|
||||
pub direction: SplitDirection,
|
||||
pub split_request: SplitRequest,
|
||||
pub command: Option<CommandBuilder>,
|
||||
pub command_dir: Option<String>,
|
||||
pub domain: config::keyassignment::SpawnTabDomain,
|
||||
|
@ -371,9 +371,33 @@ pub enum KeyAssignment {
|
||||
|
||||
CopyMode(CopyModeAssignment),
|
||||
RotatePanes(RotationDirection),
|
||||
SplitPane(SplitPane),
|
||||
}
|
||||
impl_lua_conversion_dynamic!(KeyAssignment);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, FromDynamic, ToDynamic)]
|
||||
pub struct SplitPane {
|
||||
pub direction: PaneDirection,
|
||||
#[dynamic(default)]
|
||||
pub size: SplitSize,
|
||||
#[dynamic(default)]
|
||||
pub command: SpawnCommand,
|
||||
#[dynamic(default)]
|
||||
pub top_level: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, FromDynamic, ToDynamic)]
|
||||
pub enum SplitSize {
|
||||
Cells(usize),
|
||||
Percent(u8),
|
||||
}
|
||||
|
||||
impl Default for SplitSize {
|
||||
fn default() -> Self {
|
||||
Self::Percent(50)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, FromDynamic, ToDynamic)]
|
||||
pub enum RotationDirection {
|
||||
Clockwise,
|
||||
|
@ -35,6 +35,7 @@ As features stabilize some brief notes about them will accumulate here.
|
||||
* [cell_width](config/lua/config/cell_width.md) option to adjust the horizontal spacing when the availble font stretches are insufficient. [#1979](https://github.com/wez/wezterm/issues/1979)
|
||||
* [min_scroll_bar_height](config/lua/config/min_scroll_bar_height.md) to control the minimum size of the scroll bar thumb [#1936](https://github.com/wez/wezterm/issues/1936)
|
||||
* [RotatePanes](config/lua/keyassignment/RotatePanes.md) key assignment for re-arranging the panes in a tab
|
||||
* [SplitPane](config/lua/keyassignment/SplitPane.md) key assignment that allows specifying the size and location of the split, as well as top-level (full width/height) splits. `wezterm cli split-pane --help` shows equivalent options you can use from the cli. [#578](https://github.com/wez/wezterm/issues/578)
|
||||
|
||||
|
||||
#### Updated
|
||||
|
@ -33,3 +33,4 @@ return {
|
||||
}
|
||||
```
|
||||
|
||||
See also: [SplitPane](SplitPane.md).
|
||||
|
29
docs/config/lua/keyassignment/SplitPane.md
Normal file
29
docs/config/lua/keyassignment/SplitPane.md
Normal file
@ -0,0 +1,29 @@
|
||||
# SplitPane
|
||||
|
||||
*Since: nightly builds only*
|
||||
|
||||
Splits the active pane in a particular direction, spawning a new command into the newly created pane.
|
||||
|
||||
This assignment has a number of fields that control the overall action:
|
||||
|
||||
* `direction` - can be one of `"Up"`, `"Down"`, `"Left"`, `"Right"`. Specifies where the new pane will end up. This field is required.
|
||||
* `size` - controls the size of the new pane. Can be `{Cells=10}` to specify eg: 10 cells or `{Percent=50}` to specify 50% of the available space. If omitted, `{Percent=50}` is the default
|
||||
* `command` - the [SpawnCommand](../SpawnCommand.md) that specifies what program to launch into the new pane. If omitted, the [default_prog](../config/default_prog.md) is used
|
||||
* `top_level` - if set to `true`, rather than splitting the active pane, the split will be made at the root of the tab and effectively split the entire tab across the full extent possible. The default is `false`.
|
||||
|
||||
```lua
|
||||
local wezterm = require 'wezterm';
|
||||
|
||||
return {
|
||||
keys = {
|
||||
-- This will create a new split and run the `top` program inside it
|
||||
{key="%", mods="CTRL|SHIFT|ALT", action=wezterm.action{SplitPane={
|
||||
direction="Left",
|
||||
command={args={"top"}},
|
||||
size={Percent=50},
|
||||
}}},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See also: [SplitHorizontal](SplitHorizontal.md), [SplitVertical](SplitVertical.md) and `wezterm cli split-pane --help`.
|
@ -33,3 +33,4 @@ return {
|
||||
}
|
||||
```
|
||||
|
||||
See also: [SplitPane](SplitPane.md).
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
use crate::localpane::LocalPane;
|
||||
use crate::pane::{alloc_pane_id, Pane, PaneId};
|
||||
use crate::tab::{SplitDirection, Tab, TabId};
|
||||
use crate::tab::{SplitRequest, Tab, TabId};
|
||||
use crate::window::WindowId;
|
||||
use crate::Mux;
|
||||
use anyhow::{bail, Error};
|
||||
@ -59,7 +59,7 @@ pub trait Domain: Downcast {
|
||||
command_dir: Option<String>,
|
||||
tab: TabId,
|
||||
pane_id: PaneId,
|
||||
direction: SplitDirection,
|
||||
split_request: SplitRequest,
|
||||
) -> anyhow::Result<Rc<dyn Pane>> {
|
||||
let mux = Mux::get().unwrap();
|
||||
let tab = match mux.get_tab(tab) {
|
||||
@ -76,7 +76,7 @@ pub trait Domain: Downcast {
|
||||
None => anyhow::bail!("invalid pane id {}", pane_id),
|
||||
};
|
||||
|
||||
let split_size = match tab.compute_split_size(pane_index, direction) {
|
||||
let split_size = match tab.compute_split_size(pane_index, split_request) {
|
||||
Some(s) => s,
|
||||
None => anyhow::bail!("invalid pane index {}", pane_index),
|
||||
};
|
||||
@ -85,7 +85,7 @@ pub trait Domain: Downcast {
|
||||
.spawn_pane(split_size.second, command, command_dir)
|
||||
.await?;
|
||||
|
||||
tab.split_and_insert(pane_index, direction, Rc::clone(&pane))?;
|
||||
tab.split_and_insert(pane_index, split_request, Rc::clone(&pane))?;
|
||||
Ok(pane)
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::client::{ClientId, ClientInfo};
|
||||
use crate::pane::{Pane, PaneId};
|
||||
use crate::tab::{SplitDirection, Tab, TabId};
|
||||
use crate::tab::{SplitRequest, Tab, TabId};
|
||||
use crate::window::{Window, WindowId};
|
||||
use anyhow::{anyhow, Context, Error};
|
||||
use config::keyassignment::SpawnTabDomain;
|
||||
@ -941,7 +941,7 @@ impl Mux {
|
||||
&self,
|
||||
// TODO: disambiguate with TabId
|
||||
pane_id: PaneId,
|
||||
direction: SplitDirection,
|
||||
request: SplitRequest,
|
||||
command: Option<CommandBuilder>,
|
||||
command_dir: Option<String>,
|
||||
domain: config::keyassignment::SpawnTabDomain,
|
||||
@ -966,7 +966,7 @@ impl Mux {
|
||||
let cwd = self.resolve_cwd(command_dir, Some(Rc::clone(¤t_pane)));
|
||||
|
||||
let pane = domain
|
||||
.split_pane(command, cwd, tab_id, pane_id, direction)
|
||||
.split_pane(command, cwd, tab_id, pane_id, request)
|
||||
.await?;
|
||||
if let Some(config) = term_config {
|
||||
pane.set_config(config);
|
||||
|
290
mux/src/tab.rs
290
mux/src/tab.rs
@ -79,6 +79,41 @@ pub struct SplitDirectionAndSize {
|
||||
pub second: PtySize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum SplitSize {
|
||||
Cells(usize),
|
||||
Percent(u8),
|
||||
}
|
||||
|
||||
impl Default for SplitSize {
|
||||
fn default() -> Self {
|
||||
Self::Percent(50)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SplitRequest {
|
||||
pub direction: SplitDirection,
|
||||
/// Whether the newly created item will be in the second part
|
||||
/// of the split (right/bottom)
|
||||
pub target_is_second: bool,
|
||||
/// Split across the top of the tab rather than the active pane
|
||||
pub top_level: bool,
|
||||
/// The size of the new item
|
||||
pub size: SplitSize,
|
||||
}
|
||||
|
||||
impl Default for SplitRequest {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
direction: SplitDirection::Horizontal,
|
||||
target_is_second: true,
|
||||
top_level: false,
|
||||
size: SplitSize::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SplitDirectionAndSize {
|
||||
fn top_of_second(&self) -> usize {
|
||||
match self.direction {
|
||||
@ -1443,36 +1478,74 @@ impl Tab {
|
||||
pub fn compute_split_size(
|
||||
&self,
|
||||
pane_index: usize,
|
||||
direction: SplitDirection,
|
||||
request: SplitRequest,
|
||||
) -> Option<SplitDirectionAndSize> {
|
||||
let cell_dims = self.cell_dimensions();
|
||||
|
||||
fn split_dimension(dim: usize, request: SplitRequest) -> (usize, usize) {
|
||||
let target_size = match request.size {
|
||||
SplitSize::Cells(n) => n,
|
||||
SplitSize::Percent(n) => (dim * (n as usize)) / 100,
|
||||
}
|
||||
.max(1);
|
||||
|
||||
let remain = dim.saturating_sub(target_size + 1);
|
||||
|
||||
if request.target_is_second {
|
||||
(remain, target_size)
|
||||
} else {
|
||||
(target_size, remain)
|
||||
}
|
||||
}
|
||||
|
||||
if request.top_level {
|
||||
let size = self.size.borrow().clone();
|
||||
|
||||
let ((width1, width2), (height1, height2)) = match request.direction {
|
||||
SplitDirection::Horizontal => (
|
||||
split_dimension(size.cols as usize, request),
|
||||
(size.rows as usize, size.rows as usize),
|
||||
),
|
||||
SplitDirection::Vertical => (
|
||||
(size.cols as usize, size.cols as usize),
|
||||
split_dimension(size.rows as usize, request),
|
||||
),
|
||||
};
|
||||
|
||||
return Some(SplitDirectionAndSize {
|
||||
direction: request.direction,
|
||||
first: PtySize {
|
||||
rows: height1 as _,
|
||||
cols: width1 as _,
|
||||
pixel_height: cell_dims.pixel_height * height1 as u16,
|
||||
pixel_width: cell_dims.pixel_width * width1 as u16,
|
||||
},
|
||||
second: PtySize {
|
||||
rows: height2 as _,
|
||||
cols: width2 as _,
|
||||
pixel_height: cell_dims.pixel_height * height2 as u16,
|
||||
pixel_width: cell_dims.pixel_width * width2 as u16,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Ensure that we're not zoomed, otherwise we'll end up in
|
||||
// a bogus split state (https://github.com/wez/wezterm/issues/723)
|
||||
self.set_zoomed(false);
|
||||
|
||||
self.iter_panes().iter().nth(pane_index).map(|pos| {
|
||||
fn split_dimension(dim: usize) -> (usize, usize) {
|
||||
let halved = dim / 2;
|
||||
if halved * 2 == dim {
|
||||
// Was an even size; we need to allow 1 cell to render
|
||||
// the split UI, so make the newly created leaf slightly
|
||||
// smaller
|
||||
(halved, halved.saturating_sub(1))
|
||||
} else {
|
||||
(halved, halved)
|
||||
let ((width1, width2), (height1, height2)) = match request.direction {
|
||||
SplitDirection::Horizontal => (
|
||||
split_dimension(pos.width, request),
|
||||
(pos.height, pos.height),
|
||||
),
|
||||
SplitDirection::Vertical => {
|
||||
((pos.width, pos.width), split_dimension(pos.height, request))
|
||||
}
|
||||
}
|
||||
|
||||
let ((width1, width2), (height1, height2)) = match direction {
|
||||
SplitDirection::Horizontal => {
|
||||
(split_dimension(pos.width), (pos.height, pos.height))
|
||||
}
|
||||
SplitDirection::Vertical => ((pos.width, pos.width), split_dimension(pos.height)),
|
||||
};
|
||||
|
||||
SplitDirectionAndSize {
|
||||
direction,
|
||||
direction: request.direction,
|
||||
first: PtySize {
|
||||
rows: height1 as _,
|
||||
cols: width1 as _,
|
||||
@ -1496,7 +1569,7 @@ impl Tab {
|
||||
pub fn split_and_insert(
|
||||
&self,
|
||||
pane_index: usize,
|
||||
direction: SplitDirection,
|
||||
request: SplitRequest,
|
||||
pane: Rc<dyn Pane>,
|
||||
) -> anyhow::Result<usize> {
|
||||
if self.zoomed.borrow().is_some() {
|
||||
@ -1505,10 +1578,11 @@ impl Tab {
|
||||
|
||||
{
|
||||
let split_info = self
|
||||
.compute_split_size(pane_index, direction)
|
||||
.compute_split_size(pane_index, request)
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("invalid pane_index {}; cannot split!", pane_index)
|
||||
})?;
|
||||
|
||||
let tab_size = *self.size.borrow();
|
||||
if split_info.first.rows == 0
|
||||
|| split_info.first.cols == 0
|
||||
@ -1529,9 +1603,53 @@ impl Tab {
|
||||
anyhow::bail!("No space for split!");
|
||||
}
|
||||
|
||||
let needs_resize = if request.top_level {
|
||||
self.pane.borrow().as_ref().unwrap().num_leaves() > 1
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if needs_resize {
|
||||
// Pre-emptively resize the tab contents down to
|
||||
// match the target size; it's easier to reuse
|
||||
// existing resize logic that way
|
||||
if request.target_is_second {
|
||||
self.resize(split_info.first.clone());
|
||||
} else {
|
||||
self.resize(split_info.second.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut root = self.pane.borrow_mut();
|
||||
let mut cursor = root.take().unwrap().cursor();
|
||||
|
||||
if request.top_level && !cursor.is_leaf() {
|
||||
let result = if request.target_is_second {
|
||||
cursor.split_node_and_insert_right(Rc::clone(&pane))
|
||||
} else {
|
||||
cursor.split_node_and_insert_left(Rc::clone(&pane))
|
||||
};
|
||||
cursor = match result {
|
||||
Ok(c) => {
|
||||
cursor = match c.assign_node(Some(split_info)) {
|
||||
Err(c) | Ok(c) => c,
|
||||
};
|
||||
|
||||
root.replace(cursor.tree());
|
||||
|
||||
let pane_index = if request.target_is_second {
|
||||
root.as_ref().unwrap().num_leaves().saturating_sub(1)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
*self.active.borrow_mut() = pane_index;
|
||||
return Ok(pane_index);
|
||||
}
|
||||
Err(cursor) => cursor,
|
||||
};
|
||||
}
|
||||
|
||||
match cursor.go_to_nth_leaf(pane_index) {
|
||||
Ok(c) => cursor = c,
|
||||
Err(c) => {
|
||||
@ -1542,10 +1660,18 @@ impl Tab {
|
||||
|
||||
let existing_pane = Rc::clone(cursor.leaf_mut().unwrap());
|
||||
|
||||
existing_pane.resize(split_info.first)?;
|
||||
pane.resize(split_info.second.clone())?;
|
||||
let (pane1, pane2) = if request.target_is_second {
|
||||
(existing_pane, pane)
|
||||
} else {
|
||||
(pane, existing_pane)
|
||||
};
|
||||
|
||||
match cursor.split_leaf_and_insert_right(pane) {
|
||||
pane1.resize(split_info.first)?;
|
||||
pane2.resize(split_info.second.clone())?;
|
||||
|
||||
*cursor.leaf_mut().unwrap() = pane1;
|
||||
|
||||
match cursor.split_leaf_and_insert_right(pane2) {
|
||||
Ok(c) => cursor = c,
|
||||
Err(c) => {
|
||||
root.replace(c.tree());
|
||||
@ -1559,13 +1685,19 @@ impl Tab {
|
||||
Err(c) | Ok(c) => root.replace(c.tree()),
|
||||
};
|
||||
|
||||
if request.target_is_second {
|
||||
*self.active.borrow_mut() = pane_index + 1;
|
||||
}
|
||||
}
|
||||
|
||||
log::debug!("split info after split: {:#?}", self.iter_splits());
|
||||
log::debug!("pane info after split: {:#?}", self.iter_panes());
|
||||
|
||||
Ok(pane_index + 1)
|
||||
Ok(if request.target_is_second {
|
||||
pane_index + 1
|
||||
} else {
|
||||
pane_index
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1784,23 +1916,35 @@ mod test {
|
||||
assert_eq!(24, panes[0].height);
|
||||
|
||||
assert!(tab
|
||||
.compute_split_size(1, SplitDirection::Horizontal)
|
||||
.compute_split_size(
|
||||
1,
|
||||
SplitRequest {
|
||||
direction: SplitDirection::Horizontal,
|
||||
..Default::default()
|
||||
}
|
||||
)
|
||||
.is_none());
|
||||
|
||||
let horz_size = tab
|
||||
.compute_split_size(0, SplitDirection::Horizontal)
|
||||
.compute_split_size(
|
||||
0,
|
||||
SplitRequest {
|
||||
direction: SplitDirection::Horizontal,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
horz_size,
|
||||
SplitDirectionAndSize {
|
||||
direction: SplitDirection::Horizontal,
|
||||
first: PtySize {
|
||||
second: PtySize {
|
||||
rows: 24,
|
||||
cols: 40,
|
||||
pixel_width: 400,
|
||||
pixel_height: 600
|
||||
},
|
||||
second: PtySize {
|
||||
first: PtySize {
|
||||
rows: 24,
|
||||
cols: 39,
|
||||
pixel_width: 390,
|
||||
@ -1809,18 +1953,26 @@ mod test {
|
||||
}
|
||||
);
|
||||
|
||||
let vert_size = tab.compute_split_size(0, SplitDirection::Vertical).unwrap();
|
||||
let vert_size = tab
|
||||
.compute_split_size(
|
||||
0,
|
||||
SplitRequest {
|
||||
direction: SplitDirection::Vertical,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
vert_size,
|
||||
SplitDirectionAndSize {
|
||||
direction: SplitDirection::Vertical,
|
||||
first: PtySize {
|
||||
second: PtySize {
|
||||
rows: 12,
|
||||
cols: 80,
|
||||
pixel_width: 800,
|
||||
pixel_height: 300
|
||||
},
|
||||
second: PtySize {
|
||||
first: PtySize {
|
||||
rows: 11,
|
||||
cols: 80,
|
||||
pixel_width: 800,
|
||||
@ -1832,7 +1984,10 @@ mod test {
|
||||
let new_index = tab
|
||||
.split_and_insert(
|
||||
0,
|
||||
SplitDirection::Horizontal,
|
||||
SplitRequest {
|
||||
direction: SplitDirection::Horizontal,
|
||||
..Default::default()
|
||||
},
|
||||
FakePane::new(2, horz_size.second),
|
||||
)
|
||||
.unwrap();
|
||||
@ -1845,27 +2000,40 @@ mod test {
|
||||
assert_eq!(false, panes[0].is_active);
|
||||
assert_eq!(0, panes[0].left);
|
||||
assert_eq!(0, panes[0].top);
|
||||
assert_eq!(40, panes[0].width);
|
||||
assert_eq!(39, panes[0].width);
|
||||
assert_eq!(24, panes[0].height);
|
||||
assert_eq!(400, panes[0].pixel_width);
|
||||
assert_eq!(390, panes[0].pixel_width);
|
||||
assert_eq!(600, panes[0].pixel_height);
|
||||
assert_eq!(1, panes[0].pane.pane_id());
|
||||
|
||||
assert_eq!(1, panes[1].index);
|
||||
assert_eq!(true, panes[1].is_active);
|
||||
assert_eq!(41, panes[1].left);
|
||||
assert_eq!(40, panes[1].left);
|
||||
assert_eq!(0, panes[1].top);
|
||||
assert_eq!(39, panes[1].width);
|
||||
assert_eq!(40, panes[1].width);
|
||||
assert_eq!(24, panes[1].height);
|
||||
assert_eq!(390, panes[1].pixel_width);
|
||||
assert_eq!(400, panes[1].pixel_width);
|
||||
assert_eq!(600, panes[1].pixel_height);
|
||||
assert_eq!(2, panes[1].pane.pane_id());
|
||||
|
||||
let vert_size = tab.compute_split_size(0, SplitDirection::Vertical).unwrap();
|
||||
let vert_size = tab
|
||||
.compute_split_size(
|
||||
0,
|
||||
SplitRequest {
|
||||
direction: SplitDirection::Vertical,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let new_index = tab
|
||||
.split_and_insert(
|
||||
0,
|
||||
SplitDirection::Vertical,
|
||||
SplitRequest {
|
||||
direction: SplitDirection::Vertical,
|
||||
top_level: false,
|
||||
target_is_second: true,
|
||||
size: Default::default(),
|
||||
},
|
||||
FakePane::new(3, vert_size.second),
|
||||
)
|
||||
.unwrap();
|
||||
@ -1878,47 +2046,47 @@ mod test {
|
||||
assert_eq!(false, panes[0].is_active);
|
||||
assert_eq!(0, panes[0].left);
|
||||
assert_eq!(0, panes[0].top);
|
||||
assert_eq!(40, panes[0].width);
|
||||
assert_eq!(12, panes[0].height);
|
||||
assert_eq!(400, panes[0].pixel_width);
|
||||
assert_eq!(300, panes[0].pixel_height);
|
||||
assert_eq!(39, panes[0].width);
|
||||
assert_eq!(11, panes[0].height);
|
||||
assert_eq!(390, panes[0].pixel_width);
|
||||
assert_eq!(275, panes[0].pixel_height);
|
||||
assert_eq!(1, panes[0].pane.pane_id());
|
||||
|
||||
assert_eq!(1, panes[1].index);
|
||||
assert_eq!(true, panes[1].is_active);
|
||||
assert_eq!(0, panes[1].left);
|
||||
assert_eq!(13, panes[1].top);
|
||||
assert_eq!(40, panes[1].width);
|
||||
assert_eq!(11, panes[1].height);
|
||||
assert_eq!(400, panes[1].pixel_width);
|
||||
assert_eq!(275, panes[1].pixel_height);
|
||||
assert_eq!(12, panes[1].top);
|
||||
assert_eq!(39, panes[1].width);
|
||||
assert_eq!(12, panes[1].height);
|
||||
assert_eq!(390, panes[1].pixel_width);
|
||||
assert_eq!(300, panes[1].pixel_height);
|
||||
assert_eq!(3, panes[1].pane.pane_id());
|
||||
|
||||
assert_eq!(2, panes[2].index);
|
||||
assert_eq!(false, panes[2].is_active);
|
||||
assert_eq!(41, panes[2].left);
|
||||
assert_eq!(40, panes[2].left);
|
||||
assert_eq!(0, panes[2].top);
|
||||
assert_eq!(39, panes[2].width);
|
||||
assert_eq!(40, panes[2].width);
|
||||
assert_eq!(24, panes[2].height);
|
||||
assert_eq!(390, panes[2].pixel_width);
|
||||
assert_eq!(400, panes[2].pixel_width);
|
||||
assert_eq!(600, panes[2].pixel_height);
|
||||
assert_eq!(2, panes[2].pane.pane_id());
|
||||
|
||||
tab.resize_split_by(1, 1);
|
||||
let panes = tab.iter_panes();
|
||||
assert_eq!(40, panes[0].width);
|
||||
assert_eq!(13, panes[0].height);
|
||||
assert_eq!(400, panes[0].pixel_width);
|
||||
assert_eq!(325, panes[0].pixel_height);
|
||||
assert_eq!(39, panes[0].width);
|
||||
assert_eq!(12, panes[0].height);
|
||||
assert_eq!(390, panes[0].pixel_width);
|
||||
assert_eq!(300, panes[0].pixel_height);
|
||||
|
||||
assert_eq!(40, panes[1].width);
|
||||
assert_eq!(10, panes[1].height);
|
||||
assert_eq!(400, panes[1].pixel_width);
|
||||
assert_eq!(250, panes[1].pixel_height);
|
||||
assert_eq!(39, panes[1].width);
|
||||
assert_eq!(11, panes[1].height);
|
||||
assert_eq!(390, panes[1].pixel_width);
|
||||
assert_eq!(275, panes[1].pixel_height);
|
||||
|
||||
assert_eq!(39, panes[2].width);
|
||||
assert_eq!(40, panes[2].width);
|
||||
assert_eq!(24, panes[2].height);
|
||||
assert_eq!(390, panes[2].pixel_width);
|
||||
assert_eq!(400, panes[2].pixel_width);
|
||||
assert_eq!(600, panes[2].pixel_height);
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ use config::{SshDomain, TlsDomainClient, UnixDomain};
|
||||
use mux::connui::{ConnectionUI, ConnectionUIParams};
|
||||
use mux::domain::{alloc_domain_id, Domain, DomainId, DomainState};
|
||||
use mux::pane::{Pane, PaneId};
|
||||
use mux::tab::{SplitDirection, Tab, TabId};
|
||||
use mux::tab::{SplitRequest, Tab, TabId};
|
||||
use mux::window::WindowId;
|
||||
use mux::{Mux, MuxNotification};
|
||||
use portable_pty::{CommandBuilder, PtySize};
|
||||
@ -541,7 +541,7 @@ impl Domain for ClientDomain {
|
||||
command_dir: Option<String>,
|
||||
tab_id: TabId,
|
||||
pane_id: PaneId,
|
||||
direction: SplitDirection,
|
||||
split_request: SplitRequest,
|
||||
) -> anyhow::Result<Rc<dyn Pane>> {
|
||||
let inner = self
|
||||
.inner()
|
||||
@ -564,7 +564,7 @@ impl Domain for ClientDomain {
|
||||
.split_pane(SplitPane {
|
||||
domain: SpawnTabDomain::CurrentPaneDomain,
|
||||
pane_id: pane.remote_pane_id,
|
||||
direction,
|
||||
split_request,
|
||||
command,
|
||||
command_dir,
|
||||
})
|
||||
@ -587,7 +587,7 @@ impl Domain for ClientDomain {
|
||||
None => anyhow::bail!("invalid pane id {}", pane_id),
|
||||
};
|
||||
|
||||
tab.split_and_insert(pane_index, direction, Rc::clone(&pane))
|
||||
tab.split_and_insert(pane_index, split_request, Rc::clone(&pane))
|
||||
.ok();
|
||||
|
||||
mux.add_pane(&pane)?;
|
||||
|
@ -22,8 +22,8 @@ use ::wezterm_term::input::{ClickPosition, MouseButton as TMB};
|
||||
use ::window::*;
|
||||
use anyhow::{anyhow, ensure, Context};
|
||||
use config::keyassignment::{
|
||||
ClipboardCopyDestination, ClipboardPasteSource, KeyAssignment, Pattern, QuickSelectArguments,
|
||||
RotationDirection, SpawnCommand,
|
||||
ClipboardCopyDestination, ClipboardPasteSource, KeyAssignment, PaneDirection, Pattern,
|
||||
QuickSelectArguments, RotationDirection, SpawnCommand, SplitSize,
|
||||
};
|
||||
use config::{
|
||||
configuration, AudibleBell, ConfigHandle, Dimension, DimensionContext, GradientOrientation,
|
||||
@ -32,7 +32,10 @@ use config::{
|
||||
use mlua::{FromLua, UserData, UserDataFields};
|
||||
use mux::pane::{CloseReason, Pane, PaneId, Pattern as MuxPattern};
|
||||
use mux::renderable::RenderableDimensions;
|
||||
use mux::tab::{PositionedPane, PositionedSplit, SplitDirection, Tab, TabId};
|
||||
use mux::tab::{
|
||||
PositionedPane, PositionedSplit, SplitDirection, SplitRequest, SplitSize as MuxSplitSize, Tab,
|
||||
TabId,
|
||||
};
|
||||
use mux::window::WindowId as MuxWindowId;
|
||||
use mux::{Mux, MuxNotification};
|
||||
use portable_pty::PtySize;
|
||||
@ -2093,11 +2096,27 @@ impl TermWindow {
|
||||
}
|
||||
SplitHorizontal(spawn) => {
|
||||
log::trace!("SplitHorizontal {:?}", spawn);
|
||||
self.spawn_command(spawn, SpawnWhere::SplitPane(SplitDirection::Horizontal));
|
||||
self.spawn_command(
|
||||
spawn,
|
||||
SpawnWhere::SplitPane(SplitRequest {
|
||||
direction: SplitDirection::Horizontal,
|
||||
target_is_second: true,
|
||||
size: MuxSplitSize::Percent(50),
|
||||
top_level: false,
|
||||
}),
|
||||
);
|
||||
}
|
||||
SplitVertical(spawn) => {
|
||||
log::trace!("SplitVertical {:?}", spawn);
|
||||
self.spawn_command(spawn, SpawnWhere::SplitPane(SplitDirection::Vertical));
|
||||
self.spawn_command(
|
||||
spawn,
|
||||
SpawnWhere::SplitPane(SplitRequest {
|
||||
direction: SplitDirection::Vertical,
|
||||
target_is_second: true,
|
||||
size: MuxSplitSize::Percent(50),
|
||||
top_level: false,
|
||||
}),
|
||||
);
|
||||
}
|
||||
ToggleFullScreen => {
|
||||
self.window.as_ref().unwrap().toggle_fullscreen();
|
||||
@ -2488,6 +2507,37 @@ impl TermWindow {
|
||||
RotationDirection::CounterClockwise => tab.rotate_counter_clockwise(),
|
||||
}
|
||||
}
|
||||
SplitPane(split) => {
|
||||
log::trace!("SplitPane {:?}", split);
|
||||
self.spawn_command(
|
||||
&split.command,
|
||||
SpawnWhere::SplitPane(SplitRequest {
|
||||
direction: match split.direction {
|
||||
PaneDirection::Down | PaneDirection::Up => SplitDirection::Vertical,
|
||||
PaneDirection::Left | PaneDirection::Right => {
|
||||
SplitDirection::Horizontal
|
||||
}
|
||||
PaneDirection::Next | PaneDirection::Prev => {
|
||||
log::error!(
|
||||
"Invalid direction {:?} for SplitPane",
|
||||
split.direction
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
},
|
||||
target_is_second: match split.direction {
|
||||
PaneDirection::Down | PaneDirection::Right => true,
|
||||
PaneDirection::Up | PaneDirection::Left => false,
|
||||
PaneDirection::Next | PaneDirection::Prev => unreachable!(),
|
||||
},
|
||||
size: match split.size {
|
||||
SplitSize::Percent(n) => MuxSplitSize::Percent(n),
|
||||
SplitSize::Cells(n) => MuxSplitSize::Cells(n),
|
||||
},
|
||||
top_level: split.top_level,
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use anyhow::{anyhow, bail, Context};
|
||||
use config::keyassignment::{SpawnCommand, SpawnTabDomain};
|
||||
use config::TermConfig;
|
||||
use mux::activity::Activity;
|
||||
use mux::tab::SplitDirection;
|
||||
use mux::tab::SplitRequest;
|
||||
use mux::Mux;
|
||||
use portable_pty::{CommandBuilder, PtySize};
|
||||
use std::sync::Arc;
|
||||
@ -12,7 +12,7 @@ use std::sync::Arc;
|
||||
pub enum SpawnWhere {
|
||||
NewWindow,
|
||||
NewTab,
|
||||
SplitPane(SplitDirection),
|
||||
SplitPane(SplitRequest),
|
||||
}
|
||||
|
||||
impl super::TermWindow {
|
||||
|
@ -722,7 +722,7 @@ async fn split_pane(split: SplitPane, client_id: Option<Arc<ClientId>>) -> anyho
|
||||
let (pane, size) = mux
|
||||
.split_pane(
|
||||
split.pane_id,
|
||||
split.direction,
|
||||
split.split_request,
|
||||
split.command,
|
||||
split.command_dir,
|
||||
split.domain,
|
||||
|
@ -4,7 +4,7 @@ use config::keyassignment::SpawnTabDomain;
|
||||
use config::wezterm_version;
|
||||
use mux::activity::Activity;
|
||||
use mux::pane::PaneId;
|
||||
use mux::tab::SplitDirection;
|
||||
use mux::tab::{SplitDirection, SplitRequest, SplitSize};
|
||||
use mux::window::WindowId;
|
||||
use mux::Mux;
|
||||
use portable_pty::cmdbuilder::CommandBuilder;
|
||||
@ -160,6 +160,7 @@ enum CliSubCommand {
|
||||
|
||||
#[structopt(
|
||||
name = "split-pane",
|
||||
rename_all = "kebab",
|
||||
about = "split the current pane.
|
||||
Outputs the pane-id for the newly created pane on success"
|
||||
)]
|
||||
@ -167,16 +168,47 @@ Outputs the pane-id for the newly created pane on success"
|
||||
/// Specify the pane that should be split.
|
||||
/// The default is to use the current pane based on the
|
||||
/// environment variable WEZTERM_PANE.
|
||||
#[structopt(long = "pane-id")]
|
||||
#[structopt(long)]
|
||||
pane_id: Option<PaneId>,
|
||||
|
||||
/// Split horizontally rather than vertically
|
||||
#[structopt(long = "horizontal")]
|
||||
#[structopt(long, conflicts_with_all=&["left", "right", "top", "bottom"])]
|
||||
horizontal: bool,
|
||||
|
||||
/// Split horizontally, with the new pane on the left
|
||||
#[structopt(long, conflicts_with_all=&["right", "top", "bottom"])]
|
||||
left: bool,
|
||||
|
||||
/// Split horizontally, with the new pane on the right
|
||||
#[structopt(long, conflicts_with_all=&["left", "top", "bottom"])]
|
||||
right: bool,
|
||||
|
||||
/// Split vertically, with the new pane on the top
|
||||
#[structopt(long, conflicts_with_all=&["left", "right", "bottom"])]
|
||||
top: bool,
|
||||
|
||||
/// Split vertically, with the new pane on the bottom
|
||||
#[structopt(long, conflicts_with_all=&["left", "right", "top"])]
|
||||
bottom: bool,
|
||||
|
||||
/// Rather than splitting the active pane, split the entire
|
||||
/// window.
|
||||
#[structopt(long)]
|
||||
top_level: bool,
|
||||
|
||||
/// The number of cells that the new split should have.
|
||||
/// If omitted, 50% of the available space is used.
|
||||
#[structopt(long)]
|
||||
cells: Option<usize>,
|
||||
|
||||
/// Specify the number of cells that the new split should
|
||||
/// have, expressed as a percentage of the available space.
|
||||
#[structopt(long, conflicts_with = "cells")]
|
||||
percent: Option<u8>,
|
||||
|
||||
/// Specify the current working directory for the initially
|
||||
/// spawned program
|
||||
#[structopt(long = "cwd", parse(from_os_str))]
|
||||
#[structopt(long, parse(from_os_str))]
|
||||
cwd: Option<OsString>,
|
||||
|
||||
/// Instead of executing your shell, run PROG.
|
||||
@ -743,17 +775,41 @@ async fn run_cli_async(config: config::ConfigHandle, cli: CliCommand) -> anyhow:
|
||||
cwd,
|
||||
prog,
|
||||
horizontal,
|
||||
left,
|
||||
right,
|
||||
top,
|
||||
bottom,
|
||||
top_level,
|
||||
cells,
|
||||
percent,
|
||||
} => {
|
||||
let pane_id = resolve_pane_id(&client, pane_id).await?;
|
||||
|
||||
let direction = if left || right || horizontal {
|
||||
SplitDirection::Horizontal
|
||||
} else if top || bottom {
|
||||
SplitDirection::Vertical
|
||||
} else {
|
||||
anyhow::bail!("impossible combination of args");
|
||||
};
|
||||
let target_is_second = right || bottom;
|
||||
let size = match (cells, percent) {
|
||||
(Some(c), _) => SplitSize::Cells(c),
|
||||
(_, Some(p)) => SplitSize::Percent(p),
|
||||
(None, None) => SplitSize::Percent(50),
|
||||
};
|
||||
|
||||
let split_request = SplitRequest {
|
||||
direction,
|
||||
target_is_second,
|
||||
size,
|
||||
top_level,
|
||||
};
|
||||
|
||||
let spawned = client
|
||||
.split_pane(codec::SplitPane {
|
||||
pane_id,
|
||||
direction: if horizontal {
|
||||
SplitDirection::Horizontal
|
||||
} else {
|
||||
SplitDirection::Vertical
|
||||
},
|
||||
split_request,
|
||||
domain: config::keyassignment::SpawnTabDomain::CurrentPaneDomain,
|
||||
command: if prog.is_empty() {
|
||||
None
|
||||
|
Loading…
Reference in New Issue
Block a user