From 0dbd5b61ef4b75ba141b0b52636f3ba7c61c70cb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= <blaz@mxxn.io>
Date: Thu, 1 Apr 2021 10:39:46 +0900
Subject: [PATCH] Simplify code by providin cx.current() = (view, doc).

---
 TODO.md                         |   2 +
 helix-term/src/commands.rs      | 441 ++++++++++++++------------------
 helix-term/src/ui/completion.rs |  18 +-
 helix-term/src/ui/editor.rs     |  13 +-
 helix-term/src/ui/mod.rs        |  17 +-
 helix-view/src/editor.rs        |   6 +
 6 files changed, 211 insertions(+), 286 deletions(-)

diff --git a/TODO.md b/TODO.md
index 94ed7c2c..fa19b7fa 100644
--- a/TODO.md
+++ b/TODO.md
@@ -23,6 +23,8 @@
 - [ ] repeat insert/command -> transaction
 - [ ] repeat selection
 
+- [] jump to alt buffer
+
 - [ ] load toml configs, themes, tabsize/identation
 
 - [ ] draw separator line between views
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 9da65813..235cb52d 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -55,6 +55,11 @@ impl<'a> Context<'a> {
         &mut self.editor.documents[id]
     }
 
+    #[inline]
+    pub fn current(&mut self) -> (&mut View, &mut Document) {
+        self.editor.current()
+    }
+
     /// Push a new component onto the compositor.
     pub fn push_layer(&mut self, mut component: Box<dyn Component>) {
         self.callback = Some(Box::new(
@@ -103,10 +108,9 @@ pub type Command = fn(cx: &mut Context);
 
 pub fn move_char_left(cx: &mut Context) {
     let count = cx.count;
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let text = doc.text().slice(..);
-    let selection = doc.selection(view_id).transform(|range| {
+    let selection = doc.selection(view.id).transform(|range| {
         movement::move_horizontally(
             text,
             range,
@@ -115,15 +119,14 @@ pub fn move_char_left(cx: &mut Context) {
             false, /* extend */
         )
     });
-    doc.set_selection(view_id, selection);
+    doc.set_selection(view.id, selection);
 }
 
 pub fn move_char_right(cx: &mut Context) {
     let count = cx.count;
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let text = doc.text().slice(..);
-    let selection = doc.selection(view_id).transform(|range| {
+    let selection = doc.selection(view.id).transform(|range| {
         movement::move_horizontally(
             text,
             range,
@@ -132,15 +135,14 @@ pub fn move_char_right(cx: &mut Context) {
             false, /* extend */
         )
     });
-    doc.set_selection(view_id, selection);
+    doc.set_selection(view.id, selection);
 }
 
 pub fn move_line_up(cx: &mut Context) {
     let count = cx.count;
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let text = doc.text().slice(..);
-    let selection = doc.selection(view_id).transform(|range| {
+    let selection = doc.selection(view.id).transform(|range| {
         movement::move_vertically(
             text,
             range,
@@ -149,15 +151,14 @@ pub fn move_line_up(cx: &mut Context) {
             false, /* extend */
         )
     });
-    doc.set_selection(view_id, selection);
+    doc.set_selection(view.id, selection);
 }
 
 pub fn move_line_down(cx: &mut Context) {
     let count = cx.count;
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let text = doc.text().slice(..);
-    let selection = doc.selection(view_id).transform(|range| {
+    let selection = doc.selection(view.id).transform(|range| {
         movement::move_vertically(
             text,
             range,
@@ -166,13 +167,12 @@ pub fn move_line_down(cx: &mut Context) {
             false, /* extend */
         )
     });
-    doc.set_selection(view_id, selection);
+    doc.set_selection(view.id, selection);
 }
 
 pub fn move_line_end(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
-    let lines = selection_lines(doc.text(), doc.selection(view_id));
+    let (view, doc) = cx.current();
+    let lines = selection_lines(doc.text(), doc.selection(view.id));
 
     let positions = lines
         .into_iter()
@@ -187,13 +187,12 @@ pub fn move_line_end(cx: &mut Context) {
 
     let selection = Selection::new(positions.collect(), 0);
 
-    doc.set_selection(view_id, selection);
+    doc.set_selection(view.id, selection);
 }
 
 pub fn move_line_start(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
-    let lines = selection_lines(doc.text(), doc.selection(view_id));
+    let (view, doc) = cx.current();
+    let lines = selection_lines(doc.text(), doc.selection(view.id));
 
     let positions = lines
         .into_iter()
@@ -205,7 +204,7 @@ pub fn move_line_start(cx: &mut Context) {
 
     let selection = Selection::new(positions.collect(), 0);
 
-    doc.set_selection(view_id, selection);
+    doc.set_selection(view.id, selection);
 }
 
 // TODO: move vs extend could take an extra type Extend/Move that would
@@ -214,101 +213,93 @@ pub fn move_line_start(cx: &mut Context) {
 
 pub fn move_next_word_start(cx: &mut Context) {
     let count = cx.count;
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let text = doc.text().slice(..);
 
-    let selection = doc.selection(view_id).transform(|range| {
+    let selection = doc.selection(view.id).transform(|range| {
         let pos = movement::move_next_word_start(text, range.head, count);
         Range::new(pos, pos)
     });
 
-    doc.set_selection(view_id, selection);
+    doc.set_selection(view.id, selection);
 }
 
 pub fn move_prev_word_start(cx: &mut Context) {
     let count = cx.count;
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let text = doc.text().slice(..);
 
-    let selection = doc.selection(view_id).transform(|range| {
+    let selection = doc.selection(view.id).transform(|range| {
         let pos = movement::move_prev_word_start(text, range.head, count);
         Range::new(pos, pos)
     });
 
-    doc.set_selection(view_id, selection);
+    doc.set_selection(view.id, selection);
 }
 
 pub fn move_next_word_end(cx: &mut Context) {
     let count = cx.count;
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let text = doc.text().slice(..);
 
-    let selection = doc.selection(view_id).transform(|range| {
+    let selection = doc.selection(view.id).transform(|range| {
         let pos = movement::move_next_word_end(text, range.head, count);
         Range::new(pos, pos)
     });
 
-    doc.set_selection(view_id, selection);
+    doc.set_selection(view.id, selection);
 }
 
 pub fn move_file_start(cx: &mut Context) {
     push_jump(cx);
-    let view_id = cx.view_id;
-    let doc = cx.doc();
-    doc.set_selection(view_id, Selection::point(0));
+    let (view, doc) = cx.current();
+    doc.set_selection(view.id, Selection::point(0));
 }
 
 pub fn move_file_end(cx: &mut Context) {
     push_jump(cx);
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let text = doc.text();
     let last_line = text.line_to_char(text.len_lines().saturating_sub(2));
-    doc.set_selection(view_id, Selection::point(last_line));
+    doc.set_selection(view.id, Selection::point(last_line));
 }
 
 pub fn extend_next_word_start(cx: &mut Context) {
     let count = cx.count;
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let text = doc.text().slice(..);
 
-    let selection = doc.selection(view_id).transform(|mut range| {
+    let selection = doc.selection(view.id).transform(|mut range| {
         let pos = movement::move_next_word_start(text, range.head, count);
         Range::new(range.anchor, pos)
     });
 
-    doc.set_selection(view_id, selection);
+    doc.set_selection(view.id, selection);
 }
 
 pub fn extend_prev_word_start(cx: &mut Context) {
     let count = cx.count;
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let text = doc.text().slice(..);
 
-    let selection = doc.selection(view_id).transform(|mut range| {
+    let selection = doc.selection(view.id).transform(|mut range| {
         let pos = movement::move_prev_word_start(text, range.head, count);
         Range::new(range.anchor, pos)
     });
-    doc.set_selection(view_id, selection);
+    doc.set_selection(view.id, selection);
 }
 
 pub fn extend_next_word_end(cx: &mut Context) {
     let count = cx.count;
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let text = doc.text().slice(..);
 
-    let selection = doc.selection(view_id).transform(|mut range| {
+    let selection = doc.selection(view.id).transform(|mut range| {
         let pos = movement::move_next_word_end(text, range.head, count);
         Range::new(range.anchor, pos)
     });
 
-    doc.set_selection(view_id, selection);
+    doc.set_selection(view.id, selection);
 }
 
 #[inline]
@@ -329,11 +320,10 @@ where
             ..
         } = event
         {
-            let view_id = cx.view_id;
-            let doc = cx.doc();
+            let (view, doc) = cx.current();
             let text = doc.text().slice(..);
 
-            let selection = doc.selection(view_id).transform(|mut range| {
+            let selection = doc.selection(view.id).transform(|mut range| {
                 search::find_nth_next(text, ch, range.head, count, inclusive).map_or(range, |pos| {
                     if extend {
                         Range::new(range.anchor, pos)
@@ -345,7 +335,7 @@ where
                 })
             });
 
-            doc.set_selection(view_id, selection);
+            doc.set_selection(view.id, selection);
         }
     })
 }
@@ -432,24 +422,22 @@ pub fn replace(cx: &mut Context) {
         {
             let text = Tendril::from_char(ch);
 
-            let view_id = cx.view_id;
-            let doc = cx.doc();
+            let (view, doc) = cx.current();
 
             let transaction =
-                Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
+                Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
                     (range.from(), range.to() + 1, Some(text.clone()))
                 });
 
-            doc.apply(&transaction, view_id);
-            doc.append_changes_to_history(view_id);
+            doc.apply(&transaction, view.id);
+            doc.append_changes_to_history(view.id);
         }
     })
 }
 
 fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
     use Direction::*;
-    let view = cx.editor.view();
-    let doc = cx.editor.document(view.doc).unwrap();
+    let (view, doc) = cx.current();
     let cursor = coords_at_pos(doc.text().slice(..), doc.selection(view.id).cursor());
     let doc_last_line = doc.text().len_lines() - 1;
 
@@ -466,9 +454,6 @@ fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
     // cursor visual offset
     let cursor_off = cursor.row - view.first_line;
 
-    // upgrade to mut reference
-    let view = cx.editor.view_mut();
-
     view.first_line = match direction {
         Forward => view.first_line + offset,
         Backward => view.first_line.saturating_sub(offset),
@@ -481,17 +466,10 @@ fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
         last_line.saturating_sub(scrolloff),
     );
 
-    let view_id = view.id;
-
-    // view drops here
-
-    // upgrade to mut reference
-    let doc = cx.doc();
-
     let text = doc.text().slice(..);
     let pos = pos_at_coords(text, Position::new(line, cursor.col)); // this func will properly truncate to line end
 
-    doc.set_selection(view_id, Selection::point(pos));
+    doc.set_selection(view.id, Selection::point(pos));
 }
 
 pub fn page_up(cx: &mut Context) {
@@ -520,10 +498,9 @@ pub fn half_page_down(cx: &mut Context) {
 
 pub fn extend_char_left(cx: &mut Context) {
     let count = cx.count;
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let text = doc.text().slice(..);
-    let selection = doc.selection(view_id).transform(|range| {
+    let selection = doc.selection(view.id).transform(|range| {
         movement::move_horizontally(
             text,
             range,
@@ -532,15 +509,14 @@ pub fn extend_char_left(cx: &mut Context) {
             true, /* extend */
         )
     });
-    doc.set_selection(view_id, selection);
+    doc.set_selection(view.id, selection);
 }
 
 pub fn extend_char_right(cx: &mut Context) {
     let count = cx.count;
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let text = doc.text().slice(..);
-    let selection = doc.selection(view_id).transform(|range| {
+    let selection = doc.selection(view.id).transform(|range| {
         movement::move_horizontally(
             text,
             range,
@@ -549,15 +525,14 @@ pub fn extend_char_right(cx: &mut Context) {
             true, /* extend */
         )
     });
-    doc.set_selection(view_id, selection);
+    doc.set_selection(view.id, selection);
 }
 
 pub fn extend_line_up(cx: &mut Context) {
     let count = cx.count;
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let text = doc.text().slice(..);
-    let selection = doc.selection(view_id).transform(|range| {
+    let selection = doc.selection(view.id).transform(|range| {
         movement::move_vertically(
             text,
             range,
@@ -566,15 +541,14 @@ pub fn extend_line_up(cx: &mut Context) {
             true, /* extend */
         )
     });
-    doc.set_selection(view_id, selection);
+    doc.set_selection(view.id, selection);
 }
 
 pub fn extend_line_down(cx: &mut Context) {
     let count = cx.count;
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let text = doc.text().slice(..);
-    let selection = doc.selection(view_id).transform(|range| {
+    let selection = doc.selection(view.id).transform(|range| {
         movement::move_vertically(
             text,
             range,
@@ -583,15 +557,14 @@ pub fn extend_line_down(cx: &mut Context) {
             true, /* extend */
         )
     });
-    doc.set_selection(view_id, selection);
+    doc.set_selection(view.id, selection);
 }
 
 pub fn select_all(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
 
     let end = doc.text().len_chars().saturating_sub(1);
-    doc.set_selection(view_id, Selection::single(0, end))
+    doc.set_selection(view.id, Selection::single(0, end))
 }
 
 pub fn select_regex(cx: &mut Context) {
@@ -619,14 +592,13 @@ pub fn split_selection(cx: &mut Context) {
 }
 
 pub fn split_selection_on_newline(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let text = doc.text().slice(..);
     // only compile the regex once
     #[allow(clippy::trivial_regex)]
     static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\n").unwrap());
-    let selection = selection::split_on_matches(text, doc.selection(view_id), &REGEX);
-    doc.set_selection(view_id, selection);
+    let selection = selection::split_on_matches(text, doc.selection(view.id), &REGEX);
+    doc.set_selection(view.id, selection);
 }
 
 // search: searches for the first occurence in file, provides a prompt
@@ -685,10 +657,9 @@ pub fn search_next(cx: &mut Context) {
 }
 
 pub fn search_selection(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let contents = doc.text().slice(..);
-    let query = doc.selection(view_id).primary().fragment(contents);
+    let query = doc.selection(view.id).primary().fragment(contents);
     let regex = regex::escape(&query);
     register::set('\\', vec![regex]);
     search_next(cx);
@@ -702,24 +673,22 @@ pub fn search_selection(cx: &mut Context) {
 
 pub fn select_line(cx: &mut Context) {
     let count = cx.count;
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
 
-    let pos = doc.selection(view_id).primary();
+    let pos = doc.selection(view.id).primary();
     let text = doc.text();
 
     let line = text.char_to_line(pos.head);
     let start = text.line_to_char(line);
     let end = text.line_to_char(line + count).saturating_sub(1);
 
-    doc.set_selection(view_id, Selection::single(start, end));
+    doc.set_selection(view.id, Selection::single(start, end));
 }
 pub fn extend_line(cx: &mut Context) {
-    let view_id = cx.view_id;
     let count = cx.count;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
 
-    let pos = doc.selection(view_id).primary();
+    let pos = doc.selection(view.id).primary();
     let text = doc.text();
 
     let line_start = text.char_to_line(pos.anchor);
@@ -732,7 +701,7 @@ pub fn extend_line(cx: &mut Context) {
     let start = text.line_to_char(line_start);
     let end = text.line_to_char(line + 1).saturating_sub(1);
 
-    doc.set_selection(view_id, Selection::single(start, end));
+    doc.set_selection(view.id, Selection::single(start, end));
 }
 
 // heuristic: append changes to history after each command, unless we're in insert mode
@@ -746,38 +715,34 @@ fn _delete_selection(doc: &mut Document, view_id: ViewId) {
 }
 
 pub fn delete_selection(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
-    _delete_selection(doc, view_id);
+    let (view, doc) = cx.current();
+    _delete_selection(doc, view.id);
 
-    doc.append_changes_to_history(view_id);
+    doc.append_changes_to_history(view.id);
 }
 
 pub fn change_selection(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
-    _delete_selection(doc, view_id);
+    let (view, doc) = cx.current();
+    _delete_selection(doc, view.id);
     enter_insert_mode(doc);
 }
 
 pub fn collapse_selection(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let selection = doc
-        .selection(view_id)
+        .selection(view.id)
         .transform(|range| Range::new(range.head, range.head));
 
-    doc.set_selection(view_id, selection);
+    doc.set_selection(view.id, selection);
 }
 
 pub fn flip_selections(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let selection = doc
-        .selection(view_id)
+        .selection(view.id)
         .transform(|range| Range::new(range.head, range.anchor));
 
-    doc.set_selection(view_id, selection);
+    doc.set_selection(view.id, selection);
 }
 
 fn enter_insert_mode(doc: &mut Document) {
@@ -786,31 +751,29 @@ fn enter_insert_mode(doc: &mut Document) {
 
 // inserts at the start of each selection
 pub fn insert_mode(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     enter_insert_mode(doc);
 
     let selection = doc
-        .selection(view_id)
+        .selection(view.id)
         .transform(|range| Range::new(range.to(), range.from()));
-    doc.set_selection(view_id, selection);
+    doc.set_selection(view.id, selection);
 }
 
 // inserts at the end of each selection
 pub fn append_mode(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     enter_insert_mode(doc);
     doc.restore_cursor = true;
 
     let text = doc.text().slice(..);
-    let selection = doc.selection(view_id).transform(|range| {
+    let selection = doc.selection(view.id).transform(|range| {
         Range::new(
             range.from(),
             graphemes::next_grapheme_boundary(text, range.to()), // to() + next char
         )
     });
-    doc.set_selection(view_id, selection);
+    doc.set_selection(view.id, selection);
 }
 
 const COMMAND_LIST: &[&str] = &["write", "open", "quit"];
@@ -970,27 +933,25 @@ pub fn prepend_to_line(cx: &mut Context) {
 pub fn append_to_line(cx: &mut Context) {
     move_line_end(cx);
 
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     enter_insert_mode(doc);
 
     // offset by another 1 char since move_line_end will position on the last char, we want to
     // append past that
-    let selection = doc.selection(view_id).transform(|range| {
+    let selection = doc.selection(view.id).transform(|range| {
         let pos = range.head + 1;
         Range::new(pos, pos)
     });
-    doc.set_selection(view_id, selection);
+    doc.set_selection(view.id, selection);
 }
 
 // o inserts a new line after each line with a selection
 pub fn open_below(cx: &mut Context) {
-    let view_id = cx.view_id;
     let count = cx.count;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     enter_insert_mode(doc);
 
-    let lines = selection_lines(doc.text(), doc.selection(view_id));
+    let lines = selection_lines(doc.text(), doc.selection(view.id));
 
     let positions = lines.into_iter().map(|index| {
         // adjust all positions to the end of the line (next line minus one)
@@ -1032,17 +993,16 @@ pub fn open_below(cx: &mut Context) {
     let transaction =
         Transaction::change(doc.text(), changes.into_iter()).with_selection(selection);
 
-    doc.apply(&transaction, view_id);
+    doc.apply(&transaction, view.id);
 }
 
 // O inserts a new line before each line with a selection
 pub fn open_above(cx: &mut Context) {
-    let view_id = cx.view_id;
     let count = cx.count;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     enter_insert_mode(doc);
 
-    let lines = selection_lines(doc.text(), doc.selection(view_id));
+    let lines = selection_lines(doc.text(), doc.selection(view.id));
 
     let positions = lines.into_iter().map(|index| {
         // adjust all positions to the end of the previous line
@@ -1085,27 +1045,26 @@ pub fn open_above(cx: &mut Context) {
     let transaction =
         Transaction::change(doc.text(), changes.into_iter()).with_selection(selection);
 
-    doc.apply(&transaction, view_id);
+    doc.apply(&transaction, view.id);
 }
 
 pub fn normal_mode(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
 
     doc.mode = Mode::Normal;
 
-    doc.append_changes_to_history(view_id);
+    doc.append_changes_to_history(view.id);
 
     // if leaving append mode, move cursor back by 1
     if doc.restore_cursor {
         let text = doc.text().slice(..);
-        let selection = doc.selection(view_id).transform(|range| {
+        let selection = doc.selection(view.id).transform(|range| {
             Range::new(
                 range.from(),
                 graphemes::prev_grapheme_boundary(text, range.to()),
             )
         });
-        doc.set_selection(view_id, selection);
+        doc.set_selection(view.id, selection);
 
         doc.restore_cursor = false;
     }
@@ -1113,16 +1072,12 @@ pub fn normal_mode(cx: &mut Context) {
 
 // Store a jump on the jumplist.
 fn push_jump(cx: &mut Context) {
-    let jump = {
-        let view_id = cx.view_id;
-        let doc = cx.doc();
-        (doc.id(), doc.selection(view_id).clone())
-    };
-    cx.view().jumps.push(jump);
+    let (view, doc) = cx.current();
+    let jump = { (doc.id(), doc.selection(view.id).clone()) };
+    view.jumps.push(jump);
 }
 
 pub fn goto_mode(cx: &mut Context) {
-    let view_id = cx.view_id;
     let count = cx.count;
 
     if count > 1 {
@@ -1130,9 +1085,9 @@ pub fn goto_mode(cx: &mut Context) {
 
         // TODO: can't go to line 1 since we can't distinguish between g and 1g, g gets converted
         // to 1g
-        let doc = cx.doc();
+        let (view, doc) = cx.current();
         let pos = doc.text().line_to_char(count - 1);
-        doc.set_selection(view_id, Selection::point(pos));
+        doc.set_selection(view.id, Selection::point(pos));
         return;
     }
 
@@ -1173,11 +1128,10 @@ fn _goto(cx: &mut Context, locations: Vec<lsp::Location>) {
         let id = editor
             .open(PathBuf::from(location.uri.path()), action)
             .expect("editor.open failed");
-        let view_id = editor.view().id;
-        let doc = &mut editor.documents[id];
+        let (view, doc) = editor.current();
         let definition_pos = location.range.start;
         let new_pos = helix_lsp::util::lsp_pos_to_pos(doc.text(), definition_pos);
-        doc.set_selection(view_id, Selection::point(new_pos));
+        doc.set_selection(view.id, Selection::point(new_pos));
     }
 
     match locations.as_slice() {
@@ -1201,15 +1155,14 @@ fn _goto(cx: &mut Context, locations: Vec<lsp::Location>) {
 }
 
 pub fn goto_definition(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let language_server = match doc.language_server() {
         Some(language_server) => language_server,
         None => return,
     };
 
     // TODO: blocking here is not ideal
-    let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view_id).cursor());
+    let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor());
 
     // TODO: handle fails
     let res =
@@ -1218,15 +1171,14 @@ pub fn goto_definition(cx: &mut Context) {
 }
 
 pub fn goto_type_definition(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let language_server = match doc.language_server() {
         Some(language_server) => language_server,
         None => return,
     };
 
     // TODO: blocking here is not ideal
-    let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view_id).cursor());
+    let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor());
 
     // TODO: handle fails
     let res = smol::block_on(language_server.goto_type_definition(doc.identifier(), pos))
@@ -1235,15 +1187,14 @@ pub fn goto_type_definition(cx: &mut Context) {
 }
 
 pub fn goto_implementation(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let language_server = match doc.language_server() {
         Some(language_server) => language_server,
         None => return,
     };
 
     // TODO: blocking here is not ideal
-    let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view_id).cursor());
+    let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor());
 
     // TODO: handle fails
     let res = smol::block_on(language_server.goto_implementation(doc.identifier(), pos))
@@ -1252,15 +1203,14 @@ pub fn goto_implementation(cx: &mut Context) {
 }
 
 pub fn goto_reference(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let language_server = match doc.language_server() {
         Some(language_server) => language_server,
         None => return,
     };
 
     // TODO: blocking here is not ideal
-    let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view_id).cursor());
+    let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor());
 
     // TODO: handle fails
     let res =
@@ -1269,8 +1219,7 @@ pub fn goto_reference(cx: &mut Context) {
 }
 
 pub fn signature_help(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
 
     let language_server = match doc.language_server() {
         Some(language_server) => language_server,
@@ -1278,7 +1227,7 @@ pub fn signature_help(cx: &mut Context) {
     };
 
     // TODO: blocking here is not ideal
-    let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view_id).cursor());
+    let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor());
 
     // TODO: handle fails
 
@@ -1371,21 +1320,20 @@ pub mod insert {
 
     // TODO: insert means add text just before cursor, on exit we should be on the last letter.
     pub fn insert_char(cx: &mut Context, c: char) {
-        let view_id = cx.view_id;
-        let doc = cx.doc();
+        let (view, doc) = cx.current();
 
         // run through insert hooks, stopping on the first one that returns Some(t)
         for hook in HOOKS {
-            if let Some(transaction) = hook(doc.text(), doc.selection(view_id), c) {
-                doc.apply(&transaction, view_id);
+            if let Some(transaction) = hook(doc.text(), doc.selection(view.id), c) {
+                doc.apply(&transaction, view.id);
                 return;
             }
         }
 
         let t = Tendril::from_char(c);
-        let transaction = Transaction::insert(doc.text(), doc.selection(view_id), t);
+        let transaction = Transaction::insert(doc.text(), doc.selection(view.id), t);
 
-        doc.apply(&transaction, view_id);
+        doc.apply(&transaction, view.id);
 
         // TODO: need a post insert hook too for certain triggers (autocomplete, signature help, etc)
         // this could also generically look at Transaction, but it's a bit annoying to look at
@@ -1396,22 +1344,20 @@ pub mod insert {
     }
 
     pub fn insert_tab(cx: &mut Context) {
-        let view_id = cx.view_id;
-        let doc = cx.doc();
+        let (view, doc) = cx.current();
         // TODO: round out to nearest indentation level (for example a line with 3 spaces should
         // indent by one to reach 4 spaces).
 
         let indent = Tendril::from(doc.indent_unit());
-        let transaction = Transaction::insert(doc.text(), doc.selection(view_id), indent);
-        doc.apply(&transaction, view_id);
+        let transaction = Transaction::insert(doc.text(), doc.selection(view.id), indent);
+        doc.apply(&transaction, view.id);
     }
 
     pub fn insert_newline(cx: &mut Context) {
-        let view_id = cx.view_id;
-        let doc = cx.doc();
+        let (view, doc) = cx.current();
         let text = doc.text().slice(..);
         let transaction =
-            Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
+            Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
                 // TODO: offset range.head by 1? when calculating?
                 let indent_level = helix_core::indent::suggested_indent_for_pos(
                     doc.syntax(),
@@ -1425,40 +1371,39 @@ pub mod insert {
                 text.push_str(&indent);
                 (range.head, range.head, Some(text.into()))
             });
-        doc.apply(&transaction, view_id);
+        doc.apply(&transaction, view.id);
     }
 
     // TODO: handle indent-aware delete
     pub fn delete_char_backward(cx: &mut Context) {
-        let view_id = cx.view_id;
         let count = cx.count;
-        let doc = cx.doc();
+        let (view, doc) = cx.current();
         let text = doc.text().slice(..);
         let transaction =
-            Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
+            Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
                 (
                     graphemes::nth_prev_grapheme_boundary(text, range.head, count),
                     range.head,
                     None,
                 )
             });
-        doc.apply(&transaction, view_id);
+        doc.apply(&transaction, view.id);
     }
 
     pub fn delete_char_forward(cx: &mut Context) {
-        let view_id = cx.view_id;
         let count = cx.count;
         let doc = cx.doc();
+        let (view, doc) = cx.current();
         let text = doc.text().slice(..);
         let transaction =
-            Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
+            Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
                 (
                     range.head,
                     graphemes::nth_next_grapheme_boundary(text, range.head, count),
                     None,
                 )
             });
-        doc.apply(&transaction, view_id);
+        doc.apply(&transaction, view.id);
     }
 }
 
@@ -1481,10 +1426,9 @@ pub fn redo(cx: &mut Context) {
 
 pub fn yank(cx: &mut Context) {
     // TODO: should selections be made end inclusive?
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let values: Vec<String> = doc
-        .selection(view_id)
+        .selection(view.id)
         .fragments(doc.text().slice(..))
         .map(Cow::into_owned)
         .collect();
@@ -1527,25 +1471,24 @@ pub fn paste(cx: &mut Context) {
 
         let mut values = values.into_iter().map(Tendril::from).chain(repeat);
 
-        let view_id = cx.view_id;
-        let doc = cx.doc();
+        let (view, doc) = cx.current();
 
         let transaction = if linewise {
             // paste on the next line
             // TODO: can simply take a range + modifier and compute the right pos without ifs
             let text = doc.text();
-            Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
+            Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
                 let line_end = text.line_to_char(text.char_to_line(range.head) + 1);
                 (line_end, line_end, Some(values.next().unwrap()))
             })
         } else {
-            Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
+            Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
                 (range.head + 1, range.head + 1, Some(values.next().unwrap()))
             })
         };
 
-        doc.apply(&transaction, view_id);
-        doc.append_changes_to_history(view_id);
+        doc.apply(&transaction, view.id);
+        doc.append_changes_to_history(view.id);
     }
 }
 
@@ -1567,9 +1510,8 @@ fn get_lines(doc: &Document, view_id: ViewId) -> Vec<usize> {
 }
 
 pub fn indent(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
-    let lines = get_lines(doc, view_id);
+    let (view, doc) = cx.current();
+    let lines = get_lines(doc, view.id);
 
     // Indent by one level
     let indent = Tendril::from(doc.indent_unit());
@@ -1581,14 +1523,13 @@ pub fn indent(cx: &mut Context) {
             (pos, pos, Some(indent.clone()))
         }),
     );
-    doc.apply(&transaction, view_id);
-    doc.append_changes_to_history(view_id);
+    doc.apply(&transaction, view.id);
+    doc.append_changes_to_history(view.id);
 }
 
 pub fn unindent(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
-    let lines = get_lines(doc, view_id);
+    let (view, doc) = cx.current();
+    let lines = get_lines(doc, view.id);
     let mut changes = Vec::with_capacity(lines.len());
     let tab_width = doc.tab_width();
 
@@ -1616,14 +1557,13 @@ pub fn unindent(cx: &mut Context) {
 
     let transaction = Transaction::change(doc.text(), changes.into_iter());
 
-    doc.apply(&transaction, view_id);
-    doc.append_changes_to_history(view_id);
+    doc.apply(&transaction, view.id);
+    doc.append_changes_to_history(view.id);
 }
 
 pub fn format_selections(cx: &mut Context) {
     use helix_lsp::lsp;
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
 
     // via lsp if available
     // else via tree-sitter indentation calculations
@@ -1631,7 +1571,7 @@ pub fn format_selections(cx: &mut Context) {
     // TODO: blocking here is not ideal
 
     let ranges: Vec<lsp::Range> = doc
-        .selection(view_id)
+        .selection(view.id)
         .iter()
         .map(|range| helix_lsp::util::range_to_lsp_range(doc.text(), *range))
         .collect();
@@ -1653,23 +1593,22 @@ pub fn format_selections(cx: &mut Context) {
 
         let transaction = helix_lsp::util::generate_transaction_from_edits(doc.text(), edits);
 
-        doc.apply(&transaction, view_id);
+        doc.apply(&transaction, view.id);
     }
 
-    doc.append_changes_to_history(view_id);
+    doc.append_changes_to_history(view.id);
 }
 
 pub fn join_selections(cx: &mut Context) {
     use movement::skip_over_next;
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
     let text = doc.text();
     let slice = doc.text().slice(..);
 
     let mut changes = Vec::new();
     let fragment = Tendril::from(" ");
 
-    for selection in doc.selection(view_id) {
+    for selection in doc.selection(view.id) {
         let start = text.char_to_line(selection.from());
         let mut end = text.char_to_line(selection.to());
         if start == end {
@@ -1700,8 +1639,8 @@ pub fn join_selections(cx: &mut Context) {
     // TODO: select inserted spaces
     // .with_selection(selection);
 
-    doc.apply(&transaction, view_id);
-    doc.append_changes_to_history(view_id);
+    doc.apply(&transaction, view.id);
+    doc.append_changes_to_history(view.id);
 }
 
 pub fn keep_selections(cx: &mut Context) {
@@ -1719,12 +1658,11 @@ pub fn keep_selections(cx: &mut Context) {
 }
 
 pub fn keep_primary_selection(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
 
-    let range = doc.selection(view_id).primary();
+    let range = doc.selection(view.id).primary();
     let selection = Selection::single(range.anchor, range.head);
-    doc.set_selection(view_id, selection);
+    doc.set_selection(view.id, selection);
 }
 
 //
@@ -1773,8 +1711,7 @@ pub fn completion(cx: &mut Context) {
     // The prefix still has to satisfy `company-minimum-prefix-length' before that
     // happens.  The value of nil means no idle completion."
 
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
 
     let language_server = match doc.language_server() {
         Some(language_server) => language_server,
@@ -1782,13 +1719,13 @@ pub fn completion(cx: &mut Context) {
     };
 
     // TODO: blocking here is not ideal
-    let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view_id).cursor());
+    let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor());
 
     // TODO: handle fails
 
     let res = smol::block_on(language_server.completion(doc.identifier(), pos)).unwrap();
 
-    let trigger_offset = doc.selection(view_id).cursor();
+    let trigger_offset = doc.selection(view.id).cursor();
 
     cx.callback(
         res,
@@ -1825,8 +1762,7 @@ pub fn completion(cx: &mut Context) {
 pub fn hover(cx: &mut Context) {
     use helix_lsp::lsp;
 
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
 
     let language_server = match doc.language_server() {
         Some(language_server) => language_server,
@@ -1837,7 +1773,7 @@ pub fn hover(cx: &mut Context) {
 
     // TODO: blocking here is not ideal, make commands async fn?
     // not like we can process additional input meanwhile though
-    let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view_id).cursor());
+    let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor());
 
     // TODO: handle fails
     let res = smol::block_on(language_server.text_document_hover(doc.identifier(), pos))
@@ -1873,36 +1809,33 @@ pub fn next_view(cx: &mut Context) {
 
 // comments
 pub fn toggle_comments(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
-    let transaction = comment::toggle_line_comments(doc.text(), doc.selection(view_id));
+    let (view, doc) = cx.current();
+    let transaction = comment::toggle_line_comments(doc.text(), doc.selection(view.id));
 
-    doc.apply(&transaction, view_id);
-    doc.append_changes_to_history(view_id);
+    doc.apply(&transaction, view.id);
+    doc.append_changes_to_history(view.id);
 }
 
 // tree sitter node selection
 
 pub fn expand_selection(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
 
     if let Some(syntax) = doc.syntax() {
         let text = doc.text().slice(..);
-        let selection = object::expand_selection(syntax, text, doc.selection(view_id));
-        doc.set_selection(view_id, selection);
+        let selection = object::expand_selection(syntax, text, doc.selection(view.id));
+        doc.set_selection(view.id, selection);
     }
 }
 
 pub fn match_brackets(cx: &mut Context) {
-    let view_id = cx.view_id;
-    let doc = cx.doc();
+    let (view, doc) = cx.current();
 
     if let Some(syntax) = doc.syntax() {
-        let pos = doc.selection(view_id).cursor();
+        let pos = doc.selection(view.id).cursor();
         if let Some(pos) = match_brackets::find(syntax, doc.text(), pos) {
             let selection = Selection::point(pos);
-            doc.set_selection(view_id, selection);
+            doc.set_selection(view.id, selection);
         };
     }
 }
@@ -1922,16 +1855,14 @@ pub fn jump_forward(cx: &mut Context) {
 
 pub fn jump_backward(cx: &mut Context) {
     let count = cx.count;
-    let view = cx.view();
+    let (view, doc) = cx.current();
 
     if let Some((id, selection)) = view.jumps.backward(count) {
         // TODO: position first_line so that main cursor is centered
         view.first_line = 0;
         view.doc = *id;
         let selection = selection.clone();
-        let view_id = view.id;
-        let doc = cx.doc();
-        doc.set_selection(view_id, selection);
+        doc.set_selection(view.id, selection);
     };
 }
 
diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs
index 9c753007..53241c57 100644
--- a/helix-term/src/ui/completion.rs
+++ b/helix-term/src/ui/completion.rs
@@ -44,10 +44,7 @@ impl Completion {
                         // doc.state = snapshot.clone();
                     }
                     PromptEvent::Validate => {
-                        let view = editor.view();
-                        let view_id = view.id;
-                        let id = view.doc;
-                        let doc = &mut editor.documents[id];
+                        let (view, doc) = editor.current();
 
                         // revert state to what it was before the last update
                         // doc.state = snapshot.clone();
@@ -92,18 +89,18 @@ impl Completion {
                         }
 
                         // if more text was entered, remove it
-                        let cursor = doc.selection(view_id).cursor();
+                        let cursor = doc.selection(view.id).cursor();
                         if trigger_offset < cursor {
                             let remove = Transaction::change(
                                 doc.text(),
                                 vec![(trigger_offset, cursor, None)].into_iter(),
                             );
-                            doc.apply(&remove, view_id);
+                            doc.apply(&remove, view.id);
                         }
 
                         let transaction =
                             util::generate_transaction_from_edits(doc.text(), vec![edit]);
-                        doc.apply(&transaction, view_id);
+                        doc.apply(&transaction, view.id);
                     }
                     _ => (),
                 };
@@ -127,10 +124,7 @@ impl Component for Completion {
         {
             // recompute menu based on matches
             let menu = self.popup.contents();
-            let view = cx.editor.view();
-            let view_id = view.id;
-            let id = view.doc;
-            let doc = cx.editor.document(id).unwrap();
+            let (view, doc) = cx.editor.current();
 
             // cx.hooks()
             // cx.add_hook(enum type,  ||)
@@ -142,7 +136,7 @@ impl Component for Completion {
             // TODO: hooks should get processed immediately so maybe do it after select!(), before
             // looping?
 
-            let cursor = doc.selection(view_id).cursor();
+            let cursor = doc.selection(view.id).cursor();
             if self.trigger_offset <= cursor {
                 let fragment = doc.text().slice(self.trigger_offset..cursor);
                 // ^ problem seems to be that we handle events here before the editor layer, so the
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs
index d0822500..24c46bde 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -487,14 +487,12 @@ impl Component for EditorView {
                 EventResult::Consumed(None)
             }
             Event::Key(event) => {
-                let view = cx.editor.view();
-                let view_id = view.id;
-                let id = view.doc;
-                let mode = cx.editor.document(id).unwrap().mode();
+                let (view, doc) = cx.editor.current();
+                let mode = doc.mode();
 
                 let mut cxt = commands::Context {
+                    view_id: view.id,
                     editor: &mut cx.editor,
-                    view_id,
                     count: 1,
                     callback: None,
                     callbacks: cx.callbacks,
@@ -524,9 +522,10 @@ impl Component for EditorView {
                 // appease borrowck
                 let callback = cxt.callback.take();
 
-                cx.editor.ensure_cursor_in_view(cx.editor.tree.focus);
+                let (view, doc) = cx.editor.current();
+                view.ensure_cursor_in_view(doc);
 
-                if mode == Mode::Normal && cx.editor.document(id).unwrap().mode() == Mode::Insert {
+                if mode == Mode::Normal && doc.mode() == Mode::Insert {
                     // HAXX: if we just entered insert mode from normal, clear key buf
                     // and record the command that got us into this mode.
 
diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs
index 479e684a..8cbf706e 100644
--- a/helix-term/src/ui/mod.rs
+++ b/helix-term/src/ui/mod.rs
@@ -45,11 +45,8 @@ pub fn regex_prompt(
             match event {
                 PromptEvent::Abort => {
                     // TODO: also revert text
-                    let view = editor.view();
-                    let view_id = view.id;
-                    let id = view.doc;
-                    let doc = &mut editor.documents[id];
-                    doc.set_selection(view_id, snapshot.clone());
+                    let (view, doc) = editor.current();
+                    doc.set_selection(view.id, snapshot.clone());
                 }
                 PromptEvent::Validate => {
                     // TODO: push_jump to store selection just before jump
@@ -62,19 +59,15 @@ pub fn regex_prompt(
 
                     match Regex::new(input) {
                         Ok(regex) => {
-                            // let view = &mut editor.view_mut();
-                            let view = editor.view();
-                            let view_id = view.id;
-                            let id = view.doc;
-                            let doc = &mut editor.documents[id];
+                            let (view, doc) = editor.current();
 
                             // revert state to what it was before the last update
                             // TODO: also revert text
-                            doc.set_selection(view_id, snapshot.clone());
+                            doc.set_selection(view.id, snapshot.clone());
 
                             fun(doc, regex);
 
-                            editor.ensure_cursor_in_view(view_id);
+                            view.ensure_cursor_in_view(doc);
                         }
                         Err(_err) => (), // TODO: mark command line as error
                     }
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index b2408eb9..a4ce2aca 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -172,6 +172,12 @@ impl Editor {
         self.tree.is_empty()
     }
 
+    pub fn current(&mut self) -> (&mut View, &mut Document) {
+        let view = self.tree.get_mut(self.tree.focus);
+        let doc = &mut self.documents[view.doc];
+        (view, doc)
+    }
+
     pub fn view(&self) -> &View {
         self.tree.get(self.tree.focus)
     }