mirror of https://github.com/helix-editor/helix
Implement view swapping
* add Tree::swap_split_in_direction() * add swap_view_{left,down,up,right} commands, bound to H,J,K,L respectively in the Window menu(s) * add test for view swappingpull/2530/head
parent
e8e252648f
commit
3f10473d30
|
@ -360,6 +360,10 @@ impl MappableCommand {
|
||||||
jump_view_left, "Jump to the split to the left",
|
jump_view_left, "Jump to the split to the left",
|
||||||
jump_view_up, "Jump to the split above",
|
jump_view_up, "Jump to the split above",
|
||||||
jump_view_down, "Jump to the split below",
|
jump_view_down, "Jump to the split below",
|
||||||
|
swap_view_right, "Swap with the split to the right",
|
||||||
|
swap_view_left, "Swap with the split to the left",
|
||||||
|
swap_view_up, "Swap with the split above",
|
||||||
|
swap_view_down, "Swap with the split below",
|
||||||
transpose_view, "Transpose splits",
|
transpose_view, "Transpose splits",
|
||||||
rotate_view, "Goto next window",
|
rotate_view, "Goto next window",
|
||||||
hsplit, "Horizontal bottom split",
|
hsplit, "Horizontal bottom split",
|
||||||
|
@ -3864,6 +3868,22 @@ fn jump_view_down(cx: &mut Context) {
|
||||||
cx.editor.focus_down()
|
cx.editor.focus_down()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn swap_view_right(cx: &mut Context) {
|
||||||
|
cx.editor.swap_right()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn swap_view_left(cx: &mut Context) {
|
||||||
|
cx.editor.swap_left()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn swap_view_up(cx: &mut Context) {
|
||||||
|
cx.editor.swap_up()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn swap_view_down(cx: &mut Context) {
|
||||||
|
cx.editor.swap_down()
|
||||||
|
}
|
||||||
|
|
||||||
fn transpose_view(cx: &mut Context) {
|
fn transpose_view(cx: &mut Context) {
|
||||||
cx.editor.transpose_view()
|
cx.editor.transpose_view()
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,6 +180,10 @@ pub fn default() -> HashMap<Mode, Keymap> {
|
||||||
"C-j" | "j" | "down" => jump_view_down,
|
"C-j" | "j" | "down" => jump_view_down,
|
||||||
"C-k" | "k" | "up" => jump_view_up,
|
"C-k" | "k" | "up" => jump_view_up,
|
||||||
"C-l" | "l" | "right" => jump_view_right,
|
"C-l" | "l" | "right" => jump_view_right,
|
||||||
|
"L" => swap_view_right,
|
||||||
|
"K" => swap_view_up,
|
||||||
|
"H" => swap_view_left,
|
||||||
|
"J" => swap_view_down,
|
||||||
"n" => { "New split scratch buffer"
|
"n" => { "New split scratch buffer"
|
||||||
"C-s" | "s" => hsplit_new,
|
"C-s" | "s" => hsplit_new,
|
||||||
"C-v" | "v" => vsplit_new,
|
"C-v" | "v" => vsplit_new,
|
||||||
|
@ -236,6 +240,10 @@ pub fn default() -> HashMap<Mode, Keymap> {
|
||||||
"C-j" | "j" | "down" => jump_view_down,
|
"C-j" | "j" | "down" => jump_view_down,
|
||||||
"C-k" | "k" | "up" => jump_view_up,
|
"C-k" | "k" | "up" => jump_view_up,
|
||||||
"C-l" | "l" | "right" => jump_view_right,
|
"C-l" | "l" | "right" => jump_view_right,
|
||||||
|
"H" => swap_view_left,
|
||||||
|
"J" => swap_view_down,
|
||||||
|
"K" => swap_view_up,
|
||||||
|
"L" => swap_view_right,
|
||||||
"n" => { "New split scratch buffer"
|
"n" => { "New split scratch buffer"
|
||||||
"C-s" | "s" => hsplit_new,
|
"C-s" | "s" => hsplit_new,
|
||||||
"C-v" | "v" => vsplit_new,
|
"C-v" | "v" => vsplit_new,
|
||||||
|
|
|
@ -885,6 +885,22 @@ impl Editor {
|
||||||
self.tree.focus_direction(tree::Direction::Down);
|
self.tree.focus_direction(tree::Direction::Down);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn swap_right(&mut self) {
|
||||||
|
self.tree.swap_split_in_direction(tree::Direction::Right);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn swap_left(&mut self) {
|
||||||
|
self.tree.swap_split_in_direction(tree::Direction::Left);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn swap_up(&mut self) {
|
||||||
|
self.tree.swap_split_in_direction(tree::Direction::Up);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn swap_down(&mut self) {
|
||||||
|
self.tree.swap_split_in_direction(tree::Direction::Down);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn transpose_view(&mut self) {
|
pub fn transpose_view(&mut self) {
|
||||||
self.tree.transpose();
|
self.tree.transpose();
|
||||||
}
|
}
|
||||||
|
|
|
@ -538,6 +538,24 @@ impl Tree {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn swap_split_in_direction(&mut self, direction: Direction) {
|
||||||
|
if let Some(id) = self.find_split_in_direction(self.focus, direction) {
|
||||||
|
if let Some([focused, target]) = self.nodes.get_disjoint_mut([self.focus, id]) {
|
||||||
|
match (&mut focused.content, &mut target.content) {
|
||||||
|
(Content::View(focused), Content::View(target)) => {
|
||||||
|
std::mem::swap(&mut focused.doc, &mut target.doc);
|
||||||
|
std::mem::swap(&mut focused.id, &mut target.id);
|
||||||
|
self.focus = id;
|
||||||
|
}
|
||||||
|
// self.focus always points to a view which has a content of Content::View
|
||||||
|
// and find_split_in_direction() only returns a view which has content of
|
||||||
|
// Content::View.
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn area(&self) -> Rect {
|
pub fn area(&self) -> Rect {
|
||||||
self.area
|
self.area
|
||||||
}
|
}
|
||||||
|
@ -649,4 +667,133 @@ mod test {
|
||||||
assert_eq!(None, tree.find_split_in_direction(r0, Direction::Right));
|
assert_eq!(None, tree.find_split_in_direction(r0, Direction::Right));
|
||||||
assert_eq!(None, tree.find_split_in_direction(r0, Direction::Up));
|
assert_eq!(None, tree.find_split_in_direction(r0, Direction::Up));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn swap_split_in_direction() {
|
||||||
|
let mut tree = Tree::new(Rect {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 180,
|
||||||
|
height: 80,
|
||||||
|
});
|
||||||
|
|
||||||
|
let doc_l0 = DocumentId::default();
|
||||||
|
let mut view = View::new(
|
||||||
|
doc_l0,
|
||||||
|
vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
||||||
|
);
|
||||||
|
view.area = Rect::new(0, 0, 180, 80);
|
||||||
|
tree.insert(view);
|
||||||
|
|
||||||
|
let l0 = tree.focus;
|
||||||
|
|
||||||
|
let doc_r0 = DocumentId::default();
|
||||||
|
let view = View::new(
|
||||||
|
doc_r0,
|
||||||
|
vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
||||||
|
);
|
||||||
|
tree.split(view, Layout::Vertical);
|
||||||
|
let r0 = tree.focus;
|
||||||
|
|
||||||
|
tree.focus = l0;
|
||||||
|
|
||||||
|
let doc_l1 = DocumentId::default();
|
||||||
|
let view = View::new(
|
||||||
|
doc_l1,
|
||||||
|
vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
||||||
|
);
|
||||||
|
tree.split(view, Layout::Horizontal);
|
||||||
|
let l1 = tree.focus;
|
||||||
|
|
||||||
|
tree.focus = l0;
|
||||||
|
|
||||||
|
let doc_l2 = DocumentId::default();
|
||||||
|
let view = View::new(
|
||||||
|
doc_l2,
|
||||||
|
vec![GutterType::Diagnostics, GutterType::LineNumbers],
|
||||||
|
);
|
||||||
|
tree.split(view, Layout::Vertical);
|
||||||
|
let l2 = tree.focus;
|
||||||
|
|
||||||
|
// Views in test
|
||||||
|
// | L0 | L2 | |
|
||||||
|
// | L1 | R0 |
|
||||||
|
|
||||||
|
// Document IDs in test
|
||||||
|
// | l0 | l2 | |
|
||||||
|
// | l1 | r0 |
|
||||||
|
|
||||||
|
fn doc_id(tree: &Tree, view_id: ViewId) -> Option<DocumentId> {
|
||||||
|
if let Content::View(view) = &tree.nodes[view_id].content {
|
||||||
|
Some(view.doc)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.focus = l0;
|
||||||
|
// `*` marks the view in focus from view table (here L0)
|
||||||
|
// | l0* | l2 | |
|
||||||
|
// | l1 | r0 |
|
||||||
|
tree.swap_split_in_direction(Direction::Down);
|
||||||
|
// | l1 | l2 | |
|
||||||
|
// | l0* | r0 |
|
||||||
|
assert_eq!(tree.focus, l1);
|
||||||
|
assert_eq!(doc_id(&tree, l0), Some(doc_l1));
|
||||||
|
assert_eq!(doc_id(&tree, l1), Some(doc_l0));
|
||||||
|
assert_eq!(doc_id(&tree, l2), Some(doc_l2));
|
||||||
|
assert_eq!(doc_id(&tree, r0), Some(doc_r0));
|
||||||
|
|
||||||
|
tree.swap_split_in_direction(Direction::Right);
|
||||||
|
|
||||||
|
// | l1 | l2 | |
|
||||||
|
// | r0 | l0* |
|
||||||
|
assert_eq!(tree.focus, r0);
|
||||||
|
assert_eq!(doc_id(&tree, l0), Some(doc_l1));
|
||||||
|
assert_eq!(doc_id(&tree, l1), Some(doc_r0));
|
||||||
|
assert_eq!(doc_id(&tree, l2), Some(doc_l2));
|
||||||
|
assert_eq!(doc_id(&tree, r0), Some(doc_l0));
|
||||||
|
|
||||||
|
// cannot swap, nothing changes
|
||||||
|
tree.swap_split_in_direction(Direction::Up);
|
||||||
|
// | l1 | l2 | |
|
||||||
|
// | r0 | l0* |
|
||||||
|
assert_eq!(tree.focus, r0);
|
||||||
|
assert_eq!(doc_id(&tree, l0), Some(doc_l1));
|
||||||
|
assert_eq!(doc_id(&tree, l1), Some(doc_r0));
|
||||||
|
assert_eq!(doc_id(&tree, l2), Some(doc_l2));
|
||||||
|
assert_eq!(doc_id(&tree, r0), Some(doc_l0));
|
||||||
|
|
||||||
|
// cannot swap, nothing changes
|
||||||
|
tree.swap_split_in_direction(Direction::Down);
|
||||||
|
// | l1 | l2 | |
|
||||||
|
// | r0 | l0* |
|
||||||
|
assert_eq!(tree.focus, r0);
|
||||||
|
assert_eq!(doc_id(&tree, l0), Some(doc_l1));
|
||||||
|
assert_eq!(doc_id(&tree, l1), Some(doc_r0));
|
||||||
|
assert_eq!(doc_id(&tree, l2), Some(doc_l2));
|
||||||
|
assert_eq!(doc_id(&tree, r0), Some(doc_l0));
|
||||||
|
|
||||||
|
tree.focus = l2;
|
||||||
|
// | l1 | l2* | |
|
||||||
|
// | r0 | l0 |
|
||||||
|
|
||||||
|
tree.swap_split_in_direction(Direction::Down);
|
||||||
|
// | l1 | r0 | |
|
||||||
|
// | l2* | l0 |
|
||||||
|
assert_eq!(tree.focus, l1);
|
||||||
|
assert_eq!(doc_id(&tree, l0), Some(doc_l1));
|
||||||
|
assert_eq!(doc_id(&tree, l1), Some(doc_l2));
|
||||||
|
assert_eq!(doc_id(&tree, l2), Some(doc_r0));
|
||||||
|
assert_eq!(doc_id(&tree, r0), Some(doc_l0));
|
||||||
|
|
||||||
|
tree.swap_split_in_direction(Direction::Up);
|
||||||
|
// | l2* | r0 | |
|
||||||
|
// | l1 | l0 |
|
||||||
|
assert_eq!(tree.focus, l0);
|
||||||
|
assert_eq!(doc_id(&tree, l0), Some(doc_l2));
|
||||||
|
assert_eq!(doc_id(&tree, l1), Some(doc_l1));
|
||||||
|
assert_eq!(doc_id(&tree, l2), Some(doc_r0));
|
||||||
|
assert_eq!(doc_id(&tree, r0), Some(doc_l0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue