diff --git a/book/src/configuration.md b/book/src/configuration.md index 7514a3d0fcc3..0846414492f8 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -57,6 +57,7 @@ on unix operating systems. | `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file. | `[]` | | `bufferline` | Renders a line at the top of the editor displaying open buffers. Can be `always`, `never` or `multiple` (only shown if more than one buffer is in use) | `never` | | `color-modes` | Whether to color the mode indicator with different colors depending on the mode itself | `false` | +| `centered-views` | Wether to center views by default | `false` | ### `[editor.statusline]` Section diff --git a/book/src/keymap.md b/book/src/keymap.md index 0550e57f3f3b..9f20f04b2617 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -244,23 +244,24 @@ Accessed by typing `Ctrl-w` in [normal mode](#normal-mode). This layer is similar to Vim keybindings as Kakoune does not support window. -| Key | Description | Command | -| ----- | ------------- | ------- | -| `w`, `Ctrl-w` | Switch to next window | `rotate_view` | -| `v`, `Ctrl-v` | Vertical right split | `vsplit` | -| `s`, `Ctrl-s` | Horizontal bottom split | `hsplit` | -| `f` | Go to files in the selection in horizontal splits | `goto_file` | -| `F` | Go to files in the selection in vertical splits | `goto_file` | -| `h`, `Ctrl-h`, `Left` | Move to left split | `jump_view_left` | -| `j`, `Ctrl-j`, `Down` | Move to split below | `jump_view_down` | -| `k`, `Ctrl-k`, `Up` | Move to split above | `jump_view_up` | -| `l`, `Ctrl-l`, `Right` | Move to right split | `jump_view_right` | -| `q`, `Ctrl-q` | Close current window | `wclose` | -| `o`, `Ctrl-o` | Only keep the current window, closing all the others | `wonly` | -| `H` | Swap window to the left | `swap_view_left` | -| `J` | Swap window downwards | `swap_view_down` | -| `K` | Swap window upwards | `swap_view_up` | -| `L` | Swap window to the right | `swap_view_right` | +| Key | Description | Command | +| ----- | ------------- | ------- | +| `c` | Toggle centered view | `toggle_centered_view` | +| `w`, `Ctrl-w` | Switch to next window | `rotate_view` | +| `v`, `Ctrl-v` | Vertical right split | `vsplit` | +| `s`, `Ctrl-s` | Horizontal bottom split | `hsplit` | +| `f` | Go to files in the selection in horizontal splits | `goto_file` | +| `F` | Go to files in the selection in vertical splits | `goto_file` | +| `h`, `Ctrl-h`, `Left` | Move to left split | `jump_view_left` | +| `j`, `Ctrl-j`, `Down` | Move to split below | `jump_view_down` | +| `k`, `Ctrl-k`, `Up` | Move to split above | `jump_view_up` | +| `l`, `Ctrl-l`, `Right` | Move to right split | `jump_view_right` | +| `q`, `Ctrl-q` | Close current window | `wclose` | +| `o`, `Ctrl-o` | Only keep the current window, closing all the others | `wonly` | +| `H` | Swap window to the left | `swap_view_left` | +| `J` | Swap window downwards | `swap_view_down` | +| `K` | Swap window upwards | `swap_view_up` | +| `L` | Swap window to the right | `swap_view_right` | #### Space mode diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index f8a96074df8b..fa2dd7ddfdc8 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -405,6 +405,7 @@ impl MappableCommand { wonly, "Close windows except current", select_register, "Select register", insert_register, "Insert register", + toggle_centered_view, "Toggle centered view", align_view_middle, "Align view middle", align_view_top, "Align view top", align_view_center, "Align view center", @@ -4571,6 +4572,13 @@ fn insert_register(cx: &mut Context) { }) } +fn toggle_centered_view(cx: &mut Context) { + let mut view = view_mut!(cx.editor); + view.is_centered = !view.is_centered; + + cx.editor.tree.recalculate(); +} + fn align_view_top(cx: &mut Context) { let (view, doc) = current!(cx.editor); align_view(doc, view, Align::Top); diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 01184f80edcd..1b2e0083fa7c 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -179,6 +179,7 @@ pub fn default() -> HashMap { "C-d" => half_page_down, "C-w" => { "Window" + "c" => toggle_centered_view, "C-w" | "w" => rotate_view, "C-s" | "s" => hsplit, "C-v" | "v" => vsplit, @@ -242,6 +243,7 @@ pub fn default() -> HashMap { "E" => dap_disable_exceptions, }, "w" => { "Window" + "c" => toggle_centered_view, "C-w" | "w" => rotate_view, "C-s" | "s" => hsplit, "C-v" | "v" => vsplit, diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 50da3ddeac2d..350517a28246 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -274,6 +274,8 @@ pub struct Config { /// Whether to color modes with different colors. Defaults to `false`. pub color_modes: bool, pub soft_wrap: SoftWrap, + /// Whether to center views by default, Defaults to `false`. + pub centered_views: bool, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -772,6 +774,7 @@ impl Default for Config { indent_guides: IndentGuidesConfig::default(), color_modes: false, soft_wrap: SoftWrap::default(), + centered_views: false, } } } @@ -1241,7 +1244,13 @@ impl Editor { .try_get(self.tree.focus) .filter(|v| id == v.doc) // Different Document .cloned() - .unwrap_or_else(|| View::new(id, self.config().gutters.clone())); + .unwrap_or_else(|| { + View::new( + id, + self.config().gutters.clone(), + self.config().centered_views, + ) + }); let view_id = self.tree.split( view, match action { @@ -1398,7 +1407,11 @@ impl Editor { .map(|(&doc_id, _)| doc_id) .next() .unwrap_or_else(|| self.new_document(Document::default(self.config.clone()))); - let view = View::new(doc_id, self.config().gutters.clone()); + let view = View::new( + doc_id, + self.config().gutters.clone(), + self.config().centered_views, + ); let view_id = self.tree.insert(view); let doc = doc_mut!(self, &doc_id); doc.ensure_view_init(view_id); diff --git a/helix-view/src/tree.rs b/helix-view/src/tree.rs index 5ec2773d9465..7c90c138760f 100644 --- a/helix-view/src/tree.rs +++ b/helix-view/src/tree.rs @@ -342,16 +342,35 @@ impl Tree { // take the area // fetch the node - // a) node is view, give it whole area + // a) node is view + // 1) view is centered and the parent container is not a vertical layout or + // with only 1 child (the current view), center the view + // 2) give the whole area // b) node is container, calculate areas for each child and push them on the stack while let Some((key, area)) = self.stack.pop() { - let node = &mut self.nodes[key]; + let parent = self.nodes[key].parent; - match &mut node.content { + // Parent must always be a container + let (parent_child_count, parent_layout) = match &self.nodes[parent].content { + Content::Container(container) => (container.children.len(), container.layout), + Content::View(_) => unreachable!(), + }; + + match &mut self.nodes[key].content { Content::View(view) => { // debug!!("setting view area {:?}", area); - view.area = area; + if view.is_centered + && (parent_child_count <= 1 || parent_layout != Layout::Vertical) + { + let width = std::cmp::min(std::cmp::max(area.width / 2, 120), area.width); + let area = + Rect::new(area.width / 2 - width / 2, area.y, width, area.height); + + view.area = area; + } else { + view.area = area; + } } // TODO: call f() Content::Container(container) => { // debug!!("setting container area {:?}", area); @@ -712,22 +731,22 @@ mod test { width: 180, height: 80, }); - let mut view = View::new(DocumentId::default(), GutterConfig::default()); + let mut view = View::new(DocumentId::default(), GutterConfig::default(), false); view.area = Rect::new(0, 0, 180, 80); tree.insert(view); let l0 = tree.focus; - let view = View::new(DocumentId::default(), GutterConfig::default()); + let view = View::new(DocumentId::default(), GutterConfig::default(), false); tree.split(view, Layout::Vertical); let r0 = tree.focus; tree.focus = l0; - let view = View::new(DocumentId::default(), GutterConfig::default()); + let view = View::new(DocumentId::default(), GutterConfig::default(), false); tree.split(view, Layout::Horizontal); let l1 = tree.focus; tree.focus = l0; - let view = View::new(DocumentId::default(), GutterConfig::default()); + let view = View::new(DocumentId::default(), GutterConfig::default(), false); tree.split(view, Layout::Vertical); let l2 = tree.focus; @@ -769,28 +788,28 @@ mod test { }); let doc_l0 = DocumentId::default(); - let mut view = View::new(doc_l0, GutterConfig::default()); + let mut view = View::new(doc_l0, GutterConfig::default(), false); 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, GutterConfig::default()); + let view = View::new(doc_r0, GutterConfig::default(), false); tree.split(view, Layout::Vertical); let r0 = tree.focus; tree.focus = l0; let doc_l1 = DocumentId::default(); - let view = View::new(doc_l1, GutterConfig::default()); + let view = View::new(doc_l1, GutterConfig::default(), false); tree.split(view, Layout::Horizontal); let l1 = tree.focus; tree.focus = l0; let doc_l2 = DocumentId::default(); - let view = View::new(doc_l2, GutterConfig::default()); + let view = View::new(doc_l2, GutterConfig::default(), false); tree.split(view, Layout::Vertical); let l2 = tree.focus; diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index f793cbe364f5..651652d80da5 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -107,6 +107,7 @@ pub struct View { pub id: ViewId, pub offset: ViewPosition, pub area: Rect, + pub is_centered: bool, pub doc: DocumentId, pub jumps: JumpList, // documents accessed from this view from the oldest one to last viewed one @@ -138,7 +139,7 @@ impl fmt::Debug for View { } impl View { - pub fn new(doc: DocumentId, gutters: GutterConfig) -> Self { + pub fn new(doc: DocumentId, gutters: GutterConfig, is_centered: bool) -> Self { Self { id: ViewId::default(), doc, @@ -148,6 +149,7 @@ impl View { vertical_offset: 0, }, area: Rect::default(), // will get calculated upon inserting into tree + is_centered, jumps: JumpList::new((doc, Selection::point(0))), // TODO: use actual sel docs_access_history: Vec::new(), last_modified_docs: [None, None], @@ -597,7 +599,7 @@ mod tests { #[test] fn test_text_pos_at_screen_coords() { - let mut view = View::new(DocumentId::default(), GutterConfig::default()); + let mut view = View::new(DocumentId::default(), GutterConfig::default(), false); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("abc\n\tdef"); let doc = Document::from( @@ -771,6 +773,7 @@ mod tests { layout: vec![GutterType::Diagnostics], line_numbers: GutterLineNumbersConfig::default(), }, + false, ); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("abc\n\tdef"); @@ -800,6 +803,7 @@ mod tests { layout: vec![], line_numbers: GutterLineNumbersConfig::default(), }, + false, ); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("abc\n\tdef"); @@ -823,7 +827,7 @@ mod tests { #[test] fn test_text_pos_at_screen_coords_cjk() { - let mut view = View::new(DocumentId::default(), GutterConfig::default()); + let mut view = View::new(DocumentId::default(), GutterConfig::default(), false); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("Hi! こんにちは皆さん"); let doc = Document::from( @@ -906,7 +910,7 @@ mod tests { #[test] fn test_text_pos_at_screen_coords_graphemes() { - let mut view = View::new(DocumentId::default(), GutterConfig::default()); + let mut view = View::new(DocumentId::default(), GutterConfig::default(), false); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("Hèl̀l̀ò world!"); let doc = Document::from(