Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions book/src/generated/static-cmd.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@
| `goto_prev_change` | Goto previous change | normal: `` [g ``, select: `` [g `` |
| `goto_first_change` | Goto first change | normal: `` [G ``, select: `` [G `` |
| `goto_last_change` | Goto last change | normal: `` ]G ``, select: `` ]G `` |
| `grow_view_width` | Grow focused container width | normal: `` <C-w>rl ``, `` <space>wrl ``, `` <C-w>r<right> ``, `` <space>wr<right> ``, select: `` <C-w>rl ``, `` <space>wrl ``, `` <C-w>r<right> ``, `` <space>wr<right> `` |
| `shrink_view_width` | Shrink focused container width | normal: `` <C-w>rh ``, `` <space>wrh ``, `` <C-w>r<left> ``, `` <space>wr<left> ``, select: `` <C-w>rh ``, `` <space>wrh ``, `` <C-w>r<left> ``, `` <space>wr<left> `` |
| `grow_view_height` | Grow focused container height | normal: `` <C-w>rk ``, `` <C-w>r<up> ``, `` <space>wrk ``, `` <space>wr<up> ``, select: `` <C-w>rk ``, `` <C-w>r<up> ``, `` <space>wrk ``, `` <space>wr<up> `` |
| `shrink_view_height` | Shrink focused container height | normal: `` <C-w>rj ``, `` <space>wrj ``, `` <C-w>r<down> ``, `` <space>wr<down> ``, select: `` <C-w>rj ``, `` <space>wrj ``, `` <C-w>r<down> ``, `` <space>wr<down> `` |
| `goto_line_start` | Goto line start | normal: `` gh ``, `` <home> ``, select: `` gh ``, insert: `` <home> `` |
| `goto_line_end` | Goto line end | normal: `` gl ``, `` <end> ``, select: `` gl `` |
| `goto_column` | Goto column | normal: `` g\| `` |
Expand Down
26 changes: 25 additions & 1 deletion helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,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 @@ -458,6 +458,10 @@ impl MappableCommand {
goto_prev_change, "Goto previous change",
goto_first_change, "Goto first change",
goto_last_change, "Goto last change",
grow_view_width, "Grow focused container width",
shrink_view_width, "Shrink focused container width",
grow_view_height, "Grow focused container height",
shrink_view_height, "Shrink focused container height",
goto_line_start, "Goto line start",
goto_line_end, "Goto line end",
goto_column, "Goto column",
Expand Down Expand Up @@ -906,6 +910,26 @@ fn goto_line_start(cx: &mut Context) {
)
}

fn grow_view_width(cx: &mut Context) {
cx.editor
.resize_view(Resize::Grow, Dimension::Width, cx.count());
}

fn shrink_view_width(cx: &mut Context) {
cx.editor
.resize_view(Resize::Shrink, Dimension::Width, cx.count());
}

fn grow_view_height(cx: &mut Context) {
cx.editor
.resize_view(Resize::Grow, Dimension::Height, cx.count());
}

fn shrink_view_height(cx: &mut Context) {
cx.editor
.resize_view(Resize::Shrink, Dimension::Height, cx.count());
}

fn goto_next_buffer(cx: &mut Context) {
goto_buffer(cx.editor, Direction::Forward, cx.count());
}
Expand Down
12 changes: 12 additions & 0 deletions helix-term/src/keymap/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,12 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
"C-s" | "s" => hsplit,
"C-v" | "v" => vsplit,
"C-t" | "t" => transpose_view,
"r" => { "Window resize mode" sticky=true
"h" | "left" => shrink_view_width,
"l" | "right" => grow_view_width,
"j" | "down" => shrink_view_height,
"k" | "up" => grow_view_height,
},
"f" => goto_file_hsplit,
"F" => goto_file_vsplit,
"C-q" | "q" => wclose,
Expand Down Expand Up @@ -262,6 +268,12 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
"C-s" | "s" => hsplit,
"C-v" | "v" => vsplit,
"C-t" | "t" => transpose_view,
"r" => { "Window resize mode" sticky=true
"h" | "left" => shrink_view_width,
"l" | "right" => grow_view_width,
"j" | "down" => shrink_view_height,
"k" | "up" => grow_view_height,
},
"f" => goto_file_hsplit,
"F" => goto_file_vsplit,
"C-q" | "q" => wclose,
Expand Down
6 changes: 5 additions & 1 deletion helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
input::KeyEvent,
register::Registers,
theme::{self, Theme},
tree::{self, Tree},
tree::{self, Dimension, Resize, Tree},
Document, DocumentId, View, ViewId,
};
use helix_event::dispatch;
Expand Down Expand Up @@ -2236,6 +2236,10 @@ impl Editor {
self.tree.transpose();
}

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

pub fn should_close(&self) -> bool {
self.tree.is_empty()
}
Expand Down
210 changes: 203 additions & 7 deletions helix-view/src/tree.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{graphics::Rect, View, ViewId};
use slotmap::SlotMap;
use std::ops::{Index, IndexMut};

// the dimensions are recomputed on window resize/tree change.
//
Expand Down Expand Up @@ -29,6 +30,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 @@ -63,15 +74,118 @@ pub enum Direction {
#[derive(Debug)]
pub struct Container {
layout: Layout,
children: Vec<ViewId>,
children: Children,
area: Rect,
}

#[derive(Debug)]
struct Children {
pub views: Vec<ViewId>,
pub bounds: Vec<ContainerBounds>,
}

impl Children {
fn new() -> Self {
Children {
views: Vec::new(),
bounds: Vec::new(),
}
}

pub fn iter(&self) -> std::slice::Iter<'_, ViewId> {
self.views.iter()
}

pub fn len(&self) -> usize {
debug_assert_eq!(self.views.len(), self.bounds.len());
self.views.len()
}

pub fn is_empty(&self) -> bool {
self.len() == 0
}
}

impl Children {
fn push(&mut self, node: ViewId) {
self.views.push(node);

let portion = 1.0 / self.views.len() as f64;

let prev = 1.0 - portion;
for bound in &mut self.bounds {
bound.portion *= prev;
}

self.bounds.push(ContainerBounds { portion });
}

fn insert(&mut self, index: usize, node: ViewId) {
self.views.insert(index, node);

let portion = 1.0 / self.views.len() as f64;

let prev = 1.0 - portion;
for bound in &mut self.bounds {
bound.portion *= prev;
}

self.bounds.insert(index, ContainerBounds { portion });
}

fn pop(&mut self) -> Option<ViewId> {
let bound = self.bounds.pop()?;

let prev = 1.0 - bound.portion;
for bound in &mut self.bounds {
bound.portion /= prev;
}

self.views.pop()
}

fn remove(&mut self, index: usize) -> ViewId {
let bound = self.bounds.remove(index);

let prev = 1.0 - bound.portion;
for bound in &mut self.bounds {
bound.portion /= prev;
}

self.views.remove(index)
}
}

impl<Idx> Index<Idx> for Children
where
Idx: std::slice::SliceIndex<[ViewId]>,
{
type Output = Idx::Output;
fn index(&self, index: Idx) -> &Self::Output {
self.views.index(index)
}
}

impl<Idx> IndexMut<Idx> for Children
where
Idx: std::slice::SliceIndex<[ViewId]>,
{
#[inline(always)]
fn index_mut(&mut self, index: Idx) -> &mut Self::Output {
self.views.index_mut(index)
}
}

#[derive(Debug, Clone, Copy)]
pub struct ContainerBounds {
portion: f64,
}

impl Container {
pub fn new(layout: Layout) -> Self {
Self {
layout,
children: Vec::new(),
children: Children::new(),
area: Rect::default(),
}
}
Expand Down Expand Up @@ -383,11 +497,12 @@ impl Tree {
Layout::Horizontal => {
let len = container.children.len();

let height = area.height / len as u16;

let mut child_y = area.y;

for (i, child) in container.children.iter().enumerate() {
let bounds = container.children.bounds[i];
let height = (area.height as f64 * bounds.portion).floor() as u16;

let mut area = Rect::new(
container.area.x,
child_y,
Expand All @@ -396,7 +511,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 +528,13 @@ 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 mut child_x = area.x;

for (i, child) in container.children.iter().enumerate() {
let bounds = container.children.bounds[i];
let width = (used_area as f64 * bounds.portion).floor() as u16;

let mut area = Rect::new(
child_x,
container.area.y,
Expand Down Expand Up @@ -475,7 +592,7 @@ impl Tree {
// It's possible to move in the desired direction within
// the parent container so an attempt is made to find the
// correct child.
match self.find_child(id, &parent_container.children, direction) {
match self.find_child(id, &parent_container.children.views, direction) {
// Child is found, search is ended
Some(id) => Some(id),
// A child is not found. This could be because of either two scenarios
Expand Down Expand Up @@ -596,6 +713,85 @@ impl Tree {
}
}

fn find_container(&mut self, layout: Layout) -> Option<(&mut Container, usize)> {
let mut focus = self.focus;
let mut parent = self.nodes[focus].parent;

let mut found = false;
while parent != focus {
let Content::Container(container) = &self.nodes[parent].content else {
unreachable!("parent should be a container");
};

if container.layout == layout {
found = true;
break;
}

focus = parent;
parent = self.nodes[focus].parent;
}

if found {
let Content::Container(container) = &mut self.nodes[parent].content else {
unreachable!("parent should be a container");
};

let idx = container.children.iter().position(|node| *node == focus)?;
Some((container, idx))
} else {
None
}
}

pub fn resize_view(&mut self, resize_type: Resize, dimension: Dimension, count: usize) {
let layout = match dimension {
Dimension::Width => Layout::Vertical,
Dimension::Height => Layout::Horizontal,
};

if let Some((container, idx)) = self.find_container(layout) {
if container.children.len() == 1 {
return;
}

let diff = match dimension {
Dimension::Width => 1.0 / container.area.width as f64 * 2.0,
Dimension::Height => 1.0 / container.area.height as f64 * 2.0,
};
let diff = match resize_type {
Resize::Shrink => -diff,
Resize::Grow => diff,
};
let diff = diff * count as f64;

let min = 0.05;
let max = 1.0 - ((container.children.len() - 1) as f64 * min);

let bounds = &mut container.children.bounds;
if bounds[idx].portion <= min && bounds[idx].portion >= max {
return;
}

let portion = bounds[idx].portion;
bounds[idx].portion = f64::clamp(portion + diff, min, max);

let diff = bounds[idx].portion - portion;
if let Some(prev) = idx.checked_sub(1) {
if let Some(next) = bounds.get_mut(idx + 1) {
next.portion -= diff / 2.0;
bounds[prev].portion -= diff / 2.0;
} else {
bounds[prev].portion -= diff;
}
} else if let Some(next) = bounds.get_mut(idx + 1) {
next.portion -= diff;
}

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
Loading