Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add flex resize, focus mode, hard-coded limits for now (clone of #2704) #8546

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
26 changes: 25 additions & 1 deletion helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ use helix_view::{
input::KeyEvent,
keyboard::KeyCode,
theme::Style,
tree,
tree::{self, Dimension, Resize},
view::View,
Document, DocumentId, Editor, ViewId,
};
Expand Down Expand Up @@ -382,6 +382,11 @@ impl MappableCommand {
goto_prev_change, "Goto previous change",
goto_first_change, "Goto first change",
goto_last_change, "Goto last change",
grow_buffer_width, "Grow focused container width",
shrink_buffer_width, "Shrink focused container width",
grow_buffer_height, "Grow focused container height",
shrink_buffer_height, "Shrink focused container height",
toggle_focus_window, "Toggle focus mode on buffer",
goto_line_start, "Goto line start",
goto_line_end, "Goto line end",
goto_next_buffer, "Goto next buffer",
Expand Down Expand Up @@ -807,6 +812,25 @@ fn goto_line_start(cx: &mut Context) {
)
}

fn grow_buffer_width(cx: &mut Context) {
cx.editor.resize_buffer(Resize::Grow, Dimension::Width);
}

fn shrink_buffer_width(cx: &mut Context) {
cx.editor.resize_buffer(Resize::Shrink, Dimension::Width);
}
fn grow_buffer_height(cx: &mut Context) {
cx.editor.resize_buffer(Resize::Grow, Dimension::Height);
}

fn shrink_buffer_height(cx: &mut Context) {
cx.editor.resize_buffer(Resize::Shrink, Dimension::Height);
}
Comment on lines +815 to +828
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This strikes me as not the right API for resizing. I would expect that commands would hardcode the amount/ratio or take the cx.count() into account to decide how much to resize. With this API it's hard to introduce resizing windows by mouse dragging the separator for example, and we can take the terminals cells into account to give an exact amount. I think @pascalkuthe has similar thoughts here if he'd like to weigh in

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For an example of another pane resizing thing in the wild: in sway/i3 you can create a mode to resize like I have here: https://github.com/the-mikedavis/dotfiles/blob/51d85cb1f258300b6fc90427fe7c1069ff924a64/defaults/sway/default.nix#L255-L271

That's what I'm thinking for the sticky mode and the hardcoding of amounts to resize by

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I think using terminal cells/pixels as aboslute positions instead of these relative slots makes more sense to me


fn toggle_focus_window(cx: &mut Context) {
cx.editor.toggle_focus_window();
}

fn goto_next_buffer(cx: &mut Context) {
goto_buffer(cx.editor, Direction::Forward, cx.count());
}
Expand Down
16 changes: 16 additions & 0 deletions helix-term/src/keymap/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,22 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
"home" => goto_line_start,
"end" => goto_line_end,

"A-w" => { "Alter Window"
"A-h"|"A-left" |"h"|"left" => shrink_buffer_width,
"A-l"|"A-right"|"l"|"right" => grow_buffer_width,
"A-j"|"A-down" |"j"|"down" => shrink_buffer_height,
"A-k"|"A-up" |"k"|"up" => grow_buffer_height,
"A-f"|"f" => toggle_focus_window,
},
Comment on lines +29 to +35
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we need the non-sticky version of this at all? I don't imagine that you would want to make a single edit to the sizes.

I'm also not sure about the keybinding. Instead I think this might fit better as a minor mode under C-w/<space>w specifically for resizing and move the f binding under C-w/<space>w instead


"A-W" => { "Alter Window" sticky=true
"h"|"left" => shrink_buffer_width,
"l"|"right" => grow_buffer_width,
"j"|"down" => shrink_buffer_height,
"k"|"up" => grow_buffer_height,
"f" => toggle_focus_window,
},

"w" => move_next_word_start,
"b" => move_prev_word_start,
"e" => move_next_word_end,
Expand Down
10 changes: 9 additions & 1 deletion helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
input::KeyEvent,
register::Registers,
theme::{self, Theme},
tree::{self, Tree},
tree::{self, Dimension, Resize, Tree},
Document, DocumentId, View, ViewId,
};
use dap::StackFrame;
Expand Down Expand Up @@ -1897,6 +1897,14 @@ impl Editor {
self.tree.transpose();
}

pub fn resize_buffer(&mut self, resize_type: Resize, dimension: Dimension) {
self.tree.resize_buffer(resize_type, dimension);
}

pub fn toggle_focus_window(&mut self) {
Comment on lines +1900 to +1904
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's some inconsistency here between "buffer" and "window". I would call both of these "view"s to match the internal wording

self.tree.toggle_focus_window();
}

pub fn should_close(&self) -> bool {
self.tree.is_empty()
}
Expand Down
187 changes: 178 additions & 9 deletions helix-view/src/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ pub enum Content {
Container(Box<Container>),
}

pub enum Resize {
Shrink,
Grow,
}

pub enum Dimension {
Width,
Height,
}

impl Node {
pub fn container(layout: Layout) -> Self {
Self {
Expand Down Expand Up @@ -65,6 +75,14 @@ pub struct Container {
layout: Layout,
children: Vec<ViewId>,
area: Rect,
node_bounds: Vec<ContainerBounds>,
}

#[derive(Debug, Clone, Copy)]
pub struct ContainerBounds {
width: usize,
height: usize,
expand: bool,
}

impl Container {
Expand All @@ -73,8 +91,69 @@ impl Container {
layout,
children: Vec::new(),
area: Rect::default(),
node_bounds: Vec::new(),
}
}

fn get_child_by_view_id(&mut self, node: ViewId) -> Option<&mut ContainerBounds> {
self.children
.iter()
.position(|child| child == &node)
.and_then(|index| self.node_bounds.get_mut(index))
}

fn push_child(&mut self, node: ViewId) -> &mut Self {
self.children.push(node);
self.add_child_bounds();
self
}

fn insert_child(&mut self, index: usize, node: ViewId) -> &mut Self {
self.children.insert(index, node);
self.insert_child_bounds(index);
self
}

fn add_child_bounds(&mut self) -> &mut Self {
self.node_bounds.push(ContainerBounds {
width: 10,
height: 10,
expand: false,
});
self
}

fn insert_child_bounds(&mut self, index: usize) -> &mut Self {
self.node_bounds.insert(
index,
ContainerBounds {
width: 10,
height: 10,
expand: false,
},
);
self
}

fn calculate_slots_width(&self) -> usize {
self.node_bounds
.iter()
.map(|bounds| match bounds.expand {
true => 40,
false => bounds.width,
})
.sum()
}

fn calculate_slots_height(&self) -> usize {
self.node_bounds
.iter()
.map(|bounds| match bounds.expand {
true => 40,
false => bounds.height,
})
.sum()
}
}

impl Default for Container {
Expand Down Expand Up @@ -131,7 +210,7 @@ impl Tree {
pos + 1
};

container.children.insert(pos, node);
container.insert_child(pos, node);
// focus the new node
self.focus = node;

Expand Down Expand Up @@ -168,7 +247,7 @@ impl Tree {
.unwrap();
pos + 1
};
container.children.insert(pos, node);
container.insert_child(pos, node);
self.nodes[node].parent = parent;
} else {
let mut split = Node::container(layout);
Expand All @@ -182,8 +261,8 @@ impl Tree {
} => container,
_ => unreachable!(),
};
container.children.push(focus);
container.children.push(node);
container.push_child(focus);
container.push_child(node);
self.nodes[focus].parent = split;
self.nodes[node].parent = split;

Expand Down Expand Up @@ -382,12 +461,17 @@ impl Tree {
match container.layout {
Layout::Horizontal => {
let len = container.children.len();

let height = area.height / len as u16;

let slots = container.calculate_slots_height();
let slot_height = area.height as f32 / slots as f32;
let mut child_y = area.y;

for (i, child) in container.children.iter().enumerate() {
let bounds = container.node_bounds[i];
let height = match bounds.expand {
true => (40.0 * slot_height) as u16,
false => (slot_height * bounds.height as f32).floor() as u16,
};

let mut area = Rect::new(
container.area.x,
child_y,
Expand All @@ -396,7 +480,7 @@ impl Tree {
);
child_y += height;

// last child takes the remaining width because we can get uneven
// last child takes the remaining height because we can get uneven
// space from rounding
if i == len - 1 {
area.height = container.area.y + container.area.height - area.y;
Expand All @@ -413,11 +497,19 @@ impl Tree {
let total_gap = inner_gap * len_u16.saturating_sub(2);

let used_area = area.width.saturating_sub(total_gap);
let width = used_area / len_u16;

let slots = container.calculate_slots_width();
let slot_width: f32 = used_area as f32 / slots as f32;

let mut child_x = area.x;

for (i, child) in container.children.iter().enumerate() {
let bounds = container.node_bounds[i];
let width = match bounds.expand {
true => (40.0 * slot_width) as u16,
false => (slot_width * bounds.width as f32).floor() as u16,
};

let mut area = Rect::new(
child_x,
container.area.y,
Expand Down Expand Up @@ -596,6 +688,83 @@ impl Tree {
}
}

fn get_active_node_bounds_mut(
&mut self,
expect_layout: Layout,
) -> Option<&mut ContainerBounds> {
let mut focus = self.focus;
let mut parent = self.nodes[focus].parent;

// Parent expected to be container
if let Some(focused_layout) = match &self.nodes[parent].content {
Content::View(_) => unreachable!(),
Content::Container(node) => Some(node.layout),
} {
// if we want to make a width change and we have a `Horizontal` layout focused,
// alter the parent `Vertical` layout instead and vice versa
if focused_layout != expect_layout {
focus = parent;
parent = self.nodes[parent].parent;
}

if let Content::Container(node) = &mut self.nodes[parent].content {
return node.as_mut().get_child_by_view_id(focus);
};
}
None
}

pub fn resize_buffer(&mut self, resize_type: Resize, dimension: Dimension) {
match dimension {
Dimension::Width => {
if let Some(bounds) = self.get_active_node_bounds_mut(Layout::Vertical) {
match resize_type {
Resize::Shrink => {
if bounds.width > 1 {
bounds.width -= 1;
}
}
Resize::Grow => {
if bounds.width < 20 {
bounds.width += 1;
}
}
};
self.recalculate();
}
}
Dimension::Height => {
if let Some(bounds) = self.get_active_node_bounds_mut(Layout::Horizontal) {
match resize_type {
Resize::Shrink => {
if bounds.height > 1 {
bounds.height -= 1;
}
}
Resize::Grow => {
if bounds.height < 20 {
bounds.height += 1;
}
}
};
self.recalculate();
}
}
}
}

pub fn toggle_focus_window(&mut self) {
if let Some(bounds) = self.get_active_node_bounds_mut(Layout::Horizontal) {
bounds.expand = !bounds.expand;
}

if let Some(bounds) = self.get_active_node_bounds_mut(Layout::Vertical) {
bounds.expand = !bounds.expand;
}

self.recalculate();
}

pub fn swap_split_in_direction(&mut self, direction: Direction) -> Option<()> {
let focus = self.focus;
let target = self.find_split_in_direction(focus, direction)?;
Expand Down