Skip to content

Commit 71b18cd

Browse files
committed
Added zen-view mode
1 parent a2ee2e6 commit 71b18cd

File tree

9 files changed

+184
-7
lines changed

9 files changed

+184
-7
lines changed

book/src/configuration.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ The `[editor.statusline]` key takes the following sub-keys:
9494

9595
| Key | Description | Default |
9696
| --- | --- | --- |
97-
| `left` | A list of elements aligned to the left of the statusline | `["mode", "spinner", "file-name", "read-only-indicator", "file-modification-indicator"]` |
97+
| `left` | A list of elements aligned to the left of the statusline | `["mode", "spinner", "file-name", "read-only-indicator", "zoom", "file-modification-indicator"]` |
9898
| `center` | A list of elements aligned to the middle of the statusline | `[]` |
9999
| `right` | A list of elements aligned to the right of the statusline | `["diagnostics", "selections", "register", "position", "file-encoding"]` |
100100
| `separator` | The character used to separate elements in the statusline | `"│"` |
@@ -127,6 +127,7 @@ The following statusline elements can be configured:
127127
| `spacer` | Inserts a space between elements (multiple/contiguous spacers may be specified) |
128128
| `version-control` | The current branch name or detached commit hash of the opened workspace |
129129
| `register` | The current selected register |
130+
| `zoom` | The current window zoom/zen state |
130131

131132
### `[editor.lsp]` Section
132133

@@ -402,3 +403,20 @@ S-tab = "move_parent_node_start"
402403
tab = "extend_parent_node_end"
403404
S-tab = "extend_parent_node_start"
404405
```
406+
407+
### `[editor.zen-view]` Section
408+
409+
Options for the zen-view mode.
410+
411+
Zen-view will center the currently-focused editor on the screen and hide all the others, useful for when you want to focus on editing
412+
a single file with either a wider monitor or if you have a lot of splits at once.
413+
414+
| Key | Description | Default |
415+
|--------------|-------------|---------|
416+
| `max-width` | The maximum width that the zen-view will take up. | `120` |
417+
| `auto-enter` | Whether zen-view should be automatically entered on startup. Can be one of `"off"` (never auto-enter), `"single-file"` (only auto-enter if Helix is opened with a single file), `"multi-file"` (only auto-enter if opened with >1 files), `"always"` (always start in zen-view). | `"off"` |
418+
419+
#### `[editor.zen-view.gutters]` Section
420+
421+
Same as the [normal gutters](#editorgutters-section) option, but specifically for zen-view.
422+
It only supports the `layout` option currently (including setting `gutters = [...]` directly).

book/src/keymap.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ useful when you're simply looking over text and not actively editing it.
195195
| `Ctrl-b`, `PageUp` | Move page up | `page_up` |
196196
| `Ctrl-u` | Move cursor and page half page up | `page_cursor_half_up` |
197197
| `Ctrl-d` | Move cursor and page half page down | `page_cursor_half_down` |
198+
| `Z` | Toggle 'zen-mode' for the focused view | `toggle_zen_view` |
198199

199200
#### Goto mode
200201

helix-term/src/application.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,12 @@ impl Application {
204204
nr_of_files,
205205
if nr_of_files == 1 { "" } else { "s" } // avoid "Loaded 1 files." grammo
206206
));
207+
editor.tree.zoom = editor
208+
.config()
209+
.zen_view
210+
.auto_enter
211+
.should_enter(nr_of_files);
212+
editor.tree.recalculate();
207213
// align the view to center after all files are loaded,
208214
// does not affect views without pos since it is at the top
209215
let (view, doc) = current!(editor);

helix-term/src/commands.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,8 @@ impl MappableCommand {
457457
vsplit_new, "Vertical right split scratch buffer",
458458
wclose, "Close window",
459459
wonly, "Close windows except current",
460+
toggle_zoom, "Toggle zoom for current window",
461+
toggle_zen_view, "Toggle zen view for current window",
460462
select_register, "Select register",
461463
insert_register, "Insert register",
462464
align_view_middle, "Align view middle",
@@ -5119,6 +5121,22 @@ fn wonly(cx: &mut Context) {
51195121
}
51205122
}
51215123

5124+
fn toggle_zoom(cx: &mut Context) {
5125+
cx.editor.tree.zoom = match cx.editor.tree.zoom {
5126+
Some(tree::ZoomMode::Normal) => None,
5127+
_ => Some(tree::ZoomMode::Normal),
5128+
};
5129+
cx.editor.tree.recalculate();
5130+
}
5131+
5132+
fn toggle_zen_view(cx: &mut Context) {
5133+
cx.editor.tree.zoom = match cx.editor.tree.zoom {
5134+
Some(tree::ZoomMode::Zen) => None,
5135+
_ => Some(tree::ZoomMode::Zen),
5136+
};
5137+
cx.editor.tree.recalculate();
5138+
}
5139+
51225140
fn select_register(cx: &mut Context) {
51235141
cx.editor.autoinfo = Some(Info::from_registers(&cx.editor.registers));
51245142
cx.on_next_key(move |cx, event| {

helix-term/src/keymap/default.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
205205
"C-s" | "s" => hsplit_new,
206206
"C-v" | "v" => vsplit_new,
207207
},
208+
"z" => toggle_zoom,
209+
"Z" => toggle_zen_view,
208210
},
209211

210212
// move under <space>c
@@ -270,6 +272,8 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
270272
"C-s" | "s" => hsplit_new,
271273
"C-v" | "v" => vsplit_new,
272274
},
275+
"z" => toggle_zoom,
276+
"Z" => toggle_zen_view,
273277
},
274278
"y" => yank_to_clipboard,
275279
"Y" => yank_main_selection_to_clipboard,

helix-term/src/ui/editor.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,14 @@ impl EditorView {
666666
let gutter_style_virtual = theme.get("ui.gutter.virtual");
667667
let gutter_selected_style_virtual = theme.get("ui.gutter.selected.virtual");
668668

669-
for gutter_type in view.gutters() {
669+
let config = editor.config();
670+
let gutters = if matches!(editor.tree.zoom, Some(helix_view::tree::ZoomMode::Zen)) {
671+
&config.zen_view.gutters.layout
672+
} else {
673+
view.gutters()
674+
};
675+
676+
for gutter_type in gutters {
670677
let mut gutter = gutter_type.style(editor, doc, view, theme, is_focused);
671678
let width = gutter_type.width(view, doc);
672679
// avoid lots of small allocations by reusing a text buffer for each line
@@ -1499,8 +1506,10 @@ impl Component for EditorView {
14991506
}
15001507

15011508
for (view, is_focused) in cx.editor.tree.views() {
1502-
let doc = cx.editor.document(view.doc).unwrap();
1503-
self.render_view(cx.editor, doc, view, area, surface, is_focused);
1509+
if cx.editor.tree.zoom.is_none() || is_focused {
1510+
let doc = cx.editor.document(view.doc).unwrap();
1511+
self.render_view(cx.editor, doc, view, area, surface, is_focused);
1512+
}
15041513
}
15051514

15061515
if config.auto_info {

helix-term/src/ui/statusline.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ fn get_render_function<'a>(
155155
helix_view::editor::StatusLineElement::Spacer => render_spacer,
156156
helix_view::editor::StatusLineElement::VersionControl => render_version_control,
157157
helix_view::editor::StatusLineElement::Register => render_register,
158+
helix_view::editor::StatusLineElement::Zoom => render_zoom,
158159
}
159160
}
160161

@@ -459,3 +460,13 @@ fn render_register<'a>(context: &RenderContext) -> Spans<'a> {
459460
Spans::default()
460461
}
461462
}
463+
464+
fn render_zoom<'a>(context: &RenderContext) -> Spans<'a> {
465+
let Some(zoom) = context.editor.tree.zoom else { return Spans::default() };
466+
467+
use helix_view::tree::ZoomMode as E;
468+
match zoom {
469+
E::Normal => "[zoom]".into(),
470+
E::Zen => "[zen]".into(),
471+
}
472+
}

helix-view/src/editor.rs

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{
77
input::KeyEvent,
88
register::Registers,
99
theme::{self, Theme},
10-
tree::{self, Tree},
10+
tree::{self, Tree, ZoomMode},
1111
view::ViewPosition,
1212
Align, Document, DocumentId, View, ViewId,
1313
};
@@ -336,6 +336,9 @@ pub struct Config {
336336
deserialize_with = "deserialize_alphabet"
337337
)]
338338
pub jump_label_alphabet: Vec<char>,
339+
/// Zen-view config.
340+
#[serde(default)]
341+
pub zen_view: ZenViewConfig,
339342
}
340343

341344
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq, PartialOrd, Ord)]
@@ -469,6 +472,7 @@ impl Default for StatusLineConfig {
469472
E::Spinner,
470473
E::FileName,
471474
E::ReadOnlyIndicator,
475+
E::Zoom,
472476
E::FileModificationIndicator,
473477
],
474478
center: vec![],
@@ -568,6 +572,9 @@ pub enum StatusLineElement {
568572

569573
/// Indicator for selected register
570574
Register,
575+
576+
/// Current zoom/zen state
577+
Zoom,
571578
}
572579

573580
// Cursor shape is read and used on every rendered frame and so needs
@@ -862,6 +869,69 @@ pub enum PopupBorderConfig {
862869
Menu,
863870
}
864871

872+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
873+
#[serde(rename_all = "kebab-case")]
874+
pub struct ZenViewConfig {
875+
#[serde(default = "ZenViewConfig::default_max_width")]
876+
pub max_width: u16,
877+
#[serde(default)]
878+
pub auto_enter: ZenViewAutoEnter,
879+
// Currently the line-numbers option isn't used, but it will
880+
// be once the updated config system is implemented in #9318
881+
// TODO(lizclipse): Once that PR is in implement the per-mode line_numbers config
882+
#[serde(default = "ZenViewConfig::default_gutters")]
883+
pub gutters: GutterConfig,
884+
}
885+
886+
impl ZenViewConfig {
887+
const fn default_max_width() -> u16 {
888+
120
889+
}
890+
891+
fn default_gutters() -> GutterConfig {
892+
GutterConfig {
893+
layout: vec![],
894+
..Default::default()
895+
}
896+
}
897+
}
898+
899+
impl Default for ZenViewConfig {
900+
fn default() -> Self {
901+
Self {
902+
max_width: Self::default_max_width(),
903+
auto_enter: ZenViewAutoEnter::default(),
904+
gutters: Self::default_gutters(),
905+
}
906+
}
907+
}
908+
909+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
910+
#[serde(rename_all = "kebab-case")]
911+
pub enum ZenViewAutoEnter {
912+
Off,
913+
SingleFile,
914+
MultiFile,
915+
Always,
916+
}
917+
918+
impl ZenViewAutoEnter {
919+
pub fn should_enter(&self, files: i32) -> Option<ZoomMode> {
920+
match self {
921+
Self::Always => Some(ZoomMode::Zen),
922+
Self::SingleFile if files == 1 => Some(ZoomMode::Zen),
923+
Self::MultiFile if files > 1 => Some(ZoomMode::Zen),
924+
_ => None,
925+
}
926+
}
927+
}
928+
929+
impl Default for ZenViewAutoEnter {
930+
fn default() -> Self {
931+
Self::Off
932+
}
933+
}
934+
865935
impl Default for Config {
866936
fn default() -> Self {
867937
Self {
@@ -913,6 +983,7 @@ impl Default for Config {
913983
popup_border: PopupBorderConfig::None,
914984
indent_heuristic: IndentationHeuristic::default(),
915985
jump_label_alphabet: ('a'..='z').collect(),
986+
zen_view: ZenViewConfig::default(),
916987
}
917988
}
918989
}
@@ -1087,13 +1158,19 @@ impl Editor {
10871158
let language_servers = helix_lsp::Registry::new(syn_loader.clone());
10881159
let conf = config.load();
10891160
let auto_pairs = (&conf.auto_pairs).into();
1161+
let mut tree = Tree::new(area);
1162+
tree.zen_max_width = conf.zen_view.max_width;
1163+
tree.zoom = match conf.zen_view.auto_enter {
1164+
ZenViewAutoEnter::Always => Some(ZoomMode::Zen),
1165+
_ => None,
1166+
};
10901167

10911168
// HAXX: offset the render area height by 1 to account for prompt/commandline
10921169
area.height -= 1;
10931170

10941171
Self {
10951172
mode: Mode::Normal,
1096-
tree: Tree::new(area),
1173+
tree,
10971174
next_document_id: DocumentId::default(),
10981175
documents: BTreeMap::new(),
10991176
saves: HashMap::new(),
@@ -1169,6 +1246,8 @@ impl Editor {
11691246
pub fn refresh_config(&mut self) {
11701247
let config = self.config();
11711248
self.auto_pairs = (&config.auto_pairs).into();
1249+
self.tree.zen_max_width = config.zen_view.max_width;
1250+
self.tree.recalculate();
11721251
self.reset_idle_timer();
11731252
self._refresh();
11741253
}

helix-view/src/tree.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ pub struct Tree {
88
root: ViewId,
99
// (container, index inside the container)
1010
pub focus: ViewId,
11-
// fullscreen: bool,
1211
area: Rect,
12+
// Maximum width to aim for when in zen-view.
13+
pub zen_max_width: u16,
14+
// The current zoom state.
15+
pub zoom: Option<ZoomMode>,
1316

1417
nodes: HopSlotMap<ViewId, Node>,
1518

@@ -29,6 +32,12 @@ pub enum Content {
2932
Container(Box<Container>),
3033
}
3134

35+
#[derive(Debug, Clone, Copy)]
36+
pub enum ZoomMode {
37+
Normal,
38+
Zen,
39+
}
40+
3241
impl Node {
3342
pub fn container(layout: Layout) -> Self {
3443
Self {
@@ -98,6 +107,8 @@ impl Tree {
98107
focus: root,
99108
// fullscreen: false,
100109
area,
110+
zen_max_width: 150,
111+
zoom: None,
101112
nodes,
102113
stack: Vec::new(),
103114
}
@@ -360,6 +371,26 @@ impl Tree {
360371
return;
361372
}
362373

374+
if let Some(zoom) = self.zoom {
375+
let width = match (zoom, self.zen_max_width) {
376+
(ZoomMode::Normal, _) => self.area.width,
377+
(ZoomMode::Zen, max_width) if max_width == 0 => self.area.width,
378+
(ZoomMode::Zen, max_width) => std::cmp::min(max_width, self.area.width),
379+
};
380+
let area = Rect::new(
381+
self.area.width / 2 - width / 2,
382+
self.area.y,
383+
width,
384+
self.area.height,
385+
);
386+
387+
for (view, _focused) in self.views_mut() {
388+
view.area = area;
389+
}
390+
391+
return;
392+
}
393+
363394
self.stack.push((self.root, self.area));
364395

365396
// take the area

0 commit comments

Comments
 (0)