diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 1b46aa62..d0795d74 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -19,7 +19,7 @@ use std::path::{Path, PathBuf};
 use helix_view::{
     document::Mode,
     view::{View, PADDING},
-    Document, DocumentId, Editor,
+    Document, DocumentId, Editor, ViewId,
 };
 
 use crossterm::event::{KeyCode, KeyEvent};
@@ -31,6 +31,7 @@ use crate::application::{LspCallbackWrapper, LspCallbacks};
 pub struct Context<'a> {
     pub count: usize,
     pub editor: &'a mut Editor,
+    pub view_id: ViewId,
 
     pub callback: Option<crate::compositor::Callback>,
     pub on_next_key_callback: Option<Box<dyn FnOnce(&mut Context, KeyEvent)>>,
@@ -101,9 +102,10 @@ 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 text = doc.text().slice(..);
-    let selection = doc.selection().transform(|range| {
+    let selection = doc.selection(view_id).transform(|range| {
         movement::move_horizontally(
             text,
             range,
@@ -112,14 +114,15 @@ pub fn move_char_left(cx: &mut Context) {
             false, /* extend */
         )
     });
-    doc.set_selection(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 text = doc.text().slice(..);
-    let selection = doc.selection().transform(|range| {
+    let selection = doc.selection(view_id).transform(|range| {
         movement::move_horizontally(
             text,
             range,
@@ -128,14 +131,15 @@ pub fn move_char_right(cx: &mut Context) {
             false, /* extend */
         )
     });
-    doc.set_selection(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 text = doc.text().slice(..);
-    let selection = doc.selection().transform(|range| {
+    let selection = doc.selection(view_id).transform(|range| {
         movement::move_vertically(
             text,
             range,
@@ -144,14 +148,15 @@ pub fn move_line_up(cx: &mut Context) {
             false, /* extend */
         )
     });
-    doc.set_selection(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 text = doc.text().slice(..);
-    let selection = doc.selection().transform(|range| {
+    let selection = doc.selection(view_id).transform(|range| {
         movement::move_vertically(
             text,
             range,
@@ -160,12 +165,13 @@ pub fn move_line_down(cx: &mut Context) {
             false, /* extend */
         )
     });
-    doc.set_selection(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());
+    let lines = selection_lines(doc.text(), doc.selection(view_id));
 
     let positions = lines
         .into_iter()
@@ -180,12 +186,13 @@ pub fn move_line_end(cx: &mut Context) {
 
     let selection = Selection::new(positions.collect(), 0);
 
-    doc.set_selection(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());
+    let lines = selection_lines(doc.text(), doc.selection(view_id));
 
     let positions = lines
         .into_iter()
@@ -197,7 +204,7 @@ pub fn move_line_start(cx: &mut Context) {
 
     let selection = Selection::new(positions.collect(), 0);
 
-    doc.set_selection(selection);
+    doc.set_selection(view_id, selection);
 }
 
 // TODO: move vs extend could take an extra type Extend/Move that would
@@ -206,93 +213,101 @@ 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 text = doc.text().slice(..);
 
-    let selection = doc.selection().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(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 text = doc.text().slice(..);
 
-    let selection = doc.selection().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(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 text = doc.text().slice(..);
 
-    let selection = doc.selection().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(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(Selection::point(0));
+    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 text = doc.text();
     let last_line = text.line_to_char(text.len_lines().saturating_sub(2));
-    doc.set_selection(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 text = doc.text().slice(..);
 
-    let selection = doc.selection().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(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 text = doc.text().slice(..);
 
-    let selection = doc.selection().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(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 text = doc.text().slice(..);
 
-    let selection = doc.selection().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(selection);
+    doc.set_selection(view_id, selection);
 }
 
 #[inline]
@@ -313,10 +328,11 @@ where
             ..
         } = event
         {
+            let view_id = cx.view_id;
             let doc = cx.doc();
             let text = doc.text().slice(..);
 
-            let selection = doc.selection().transform(|mut range| {
+            let selection = doc.selection(view_id).transform(|mut range| {
                 if let Some(pos) = search::find_nth_next(text, ch, range.head, count, inclusive) {
                     if extend {
                         Range::new(range.anchor, pos)
@@ -330,7 +346,7 @@ where
                 }
             });
 
-            doc.set_selection(selection);
+            doc.set_selection(view_id, selection);
         }
     })
 }
@@ -417,15 +433,16 @@ pub fn replace(cx: &mut Context) {
         {
             let text = Tendril::from_char(ch);
 
+            let view_id = cx.view_id;
             let doc = cx.doc();
 
             let transaction =
-                Transaction::change_by_selection(doc.text(), doc.selection(), |range| {
+                Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
                     (range.from(), range.to() + 1, Some(text.clone()))
                 });
 
-            doc.apply(&transaction);
-            doc.append_changes_to_history();
+            doc.apply(&transaction, view_id);
+            doc.append_changes_to_history(view_id);
         }
     })
 }
@@ -434,7 +451,7 @@ 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 cursor = coords_at_pos(doc.text().slice(..), doc.selection().cursor());
+    let cursor = coords_at_pos(doc.text().slice(..), doc.selection(view.id).cursor());
     let doc_last_line = doc.text().len_lines() - 1;
 
     let last_line = view.last_line(doc);
@@ -465,6 +482,8 @@ 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
@@ -473,7 +492,7 @@ fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
     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(Selection::point(pos));
+    doc.set_selection(view_id, Selection::point(pos));
 }
 
 pub fn page_up(cx: &mut Context) {
@@ -502,9 +521,10 @@ 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 text = doc.text().slice(..);
-    let selection = doc.selection().transform(|range| {
+    let selection = doc.selection(view_id).transform(|range| {
         movement::move_horizontally(
             text,
             range,
@@ -513,14 +533,15 @@ pub fn extend_char_left(cx: &mut Context) {
             true, /* extend */
         )
     });
-    doc.set_selection(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 text = doc.text().slice(..);
-    let selection = doc.selection().transform(|range| {
+    let selection = doc.selection(view_id).transform(|range| {
         movement::move_horizontally(
             text,
             range,
@@ -529,14 +550,15 @@ pub fn extend_char_right(cx: &mut Context) {
             true, /* extend */
         )
     });
-    doc.set_selection(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 text = doc.text().slice(..);
-    let selection = doc.selection().transform(|range| {
+    let selection = doc.selection(view_id).transform(|range| {
         movement::move_vertically(
             text,
             range,
@@ -545,14 +567,15 @@ pub fn extend_line_up(cx: &mut Context) {
             true, /* extend */
         )
     });
-    doc.set_selection(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 text = doc.text().slice(..);
-    let selection = doc.selection().transform(|range| {
+    let selection = doc.selection(view_id).transform(|range| {
         movement::move_vertically(
             text,
             range,
@@ -561,21 +584,24 @@ pub fn extend_line_down(cx: &mut Context) {
             true, /* extend */
         )
     });
-    doc.set_selection(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 end = doc.text().len_chars().saturating_sub(1);
-    doc.set_selection(Selection::single(0, end))
+    doc.set_selection(view_id, Selection::single(0, end))
 }
 
 pub fn select_regex(cx: &mut Context) {
-    let prompt = ui::regex_prompt(cx, "select:".to_string(), |doc, regex| {
+    let view_id = cx.view_id;
+    let prompt = ui::regex_prompt(cx, "select:".to_string(), move |doc, regex| {
         let text = doc.text().slice(..);
-        if let Some(selection) = selection::select_on_matches(text, doc.selection(), &regex) {
-            doc.set_selection(selection);
+        if let Some(selection) = selection::select_on_matches(text, doc.selection(view_id), &regex)
+        {
+            doc.set_selection(view_id, selection);
         }
     });
 
@@ -583,23 +609,25 @@ pub fn select_regex(cx: &mut Context) {
 }
 
 pub fn split_selection(cx: &mut Context) {
-    let prompt = ui::regex_prompt(cx, "split:".to_string(), |doc, regex| {
+    let view_id = cx.view_id;
+    let prompt = ui::regex_prompt(cx, "split:".to_string(), move |doc, regex| {
         let text = doc.text().slice(..);
-        let selection = selection::split_on_matches(text, doc.selection(), &regex);
-        doc.set_selection(selection);
+        let selection = selection::split_on_matches(text, doc.selection(view_id), &regex);
+        doc.set_selection(view_id, selection);
     });
 
     cx.push_layer(Box::new(prompt));
 }
 
 pub fn split_selection_on_newline(cx: &mut Context) {
+    let view_id = cx.view_id;
     let doc = cx.doc();
     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(), &REGEX);
-    doc.set_selection(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
@@ -609,9 +637,9 @@ pub fn split_selection_on_newline(cx: &mut Context) {
 // I'd probably collect all the matches right now and store the current index. The cache needs
 // wiping if input happens.
 
-fn _search(doc: &mut Document, contents: &str, regex: &Regex) {
+fn _search(doc: &mut Document, view_id: ViewId, contents: &str, regex: &Regex) {
     let text = doc.text();
-    let start = doc.selection().cursor();
+    let start = doc.selection(view_id).cursor();
 
     // TODO: use find_at to find the next match after the cursor, loop around the end
     if let Some(mat) = regex.find_at(&contents, start) {
@@ -619,7 +647,7 @@ fn _search(doc: &mut Document, contents: &str, regex: &Regex) {
         let end = text.byte_to_char(mat.end());
         let selection = Selection::single(start, end - 1);
         // TODO: (first_match, regex) stuff in register?
-        doc.set_selection(selection);
+        doc.set_selection(view_id, selection);
     };
 }
 
@@ -633,10 +661,11 @@ pub fn search(cx: &mut Context) {
     // feed chunks into the regex yet
     let contents = doc.text().slice(..).to_string();
 
+    let view_id = cx.view_id;
     let prompt = ui::regex_prompt(cx, "search:".to_string(), move |doc, regex| {
         let text = doc.text();
-        let start = doc.selection().cursor();
-        _search(doc, &contents, &regex);
+        let start = doc.selection(view_id).cursor();
+        _search(doc, view_id, &contents, &regex);
 
         // TODO: only store on enter (accept), not update
         register::set('\\', vec![regex.as_str().to_string()]);
@@ -648,17 +677,19 @@ pub fn search(cx: &mut Context) {
 pub fn search_next(cx: &mut Context) {
     if let Some(query) = register::get('\\') {
         let query = query.first().unwrap();
+        let view_id = cx.view_id;
         let doc = cx.doc();
         let contents = doc.text().slice(..).to_string();
         let regex = Regex::new(&query).unwrap();
-        _search(doc, &contents, &regex);
+        _search(doc, view_id, &contents, &regex);
     }
 }
 
 pub fn search_selection(cx: &mut Context) {
+    let view_id = cx.view_id;
     let doc = cx.doc();
     let contents = doc.text().slice(..);
-    let query = doc.selection().primary().fragment(contents);
+    let query = doc.selection(view_id).primary().fragment(contents);
     let regex = regex::escape(&query);
     register::set('\\', vec![regex]);
     search_next(cx);
@@ -672,22 +703,24 @@ 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 pos = doc.selection().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(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 pos = doc.selection().primary();
+    let pos = doc.selection(view_id).primary();
     let text = doc.text();
 
     let line_start = text.char_to_line(pos.anchor);
@@ -700,47 +733,52 @@ 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(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
 
-fn _delete_selection(doc: &mut Document) {
-    let transaction = Transaction::change_by_selection(doc.text(), doc.selection(), |range| {
-        (range.from(), range.to() + 1, None)
-    });
-    doc.apply(&transaction);
+fn _delete_selection(doc: &mut Document, view_id: ViewId) {
+    let transaction =
+        Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
+            (range.from(), range.to() + 1, None)
+        });
+    doc.apply(&transaction, view_id);
 }
 
 pub fn delete_selection(cx: &mut Context) {
+    let view_id = cx.view_id;
     let doc = cx.doc();
-    _delete_selection(doc);
+    _delete_selection(doc, view_id);
 
-    doc.append_changes_to_history();
+    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);
+    _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 selection = doc
-        .selection()
+        .selection(view_id)
         .transform(|range| Range::new(range.head, range.head));
 
-    doc.set_selection(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 selection = doc
-        .selection()
+        .selection(view_id)
         .transform(|range| Range::new(range.head, range.anchor));
 
-    doc.set_selection(selection);
+    doc.set_selection(view_id, selection);
 }
 
 fn enter_insert_mode(doc: &mut Document) {
@@ -749,29 +787,31 @@ 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();
     enter_insert_mode(doc);
 
     let selection = doc
-        .selection()
+        .selection(view_id)
         .transform(|range| Range::new(range.to(), range.from()));
-    doc.set_selection(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();
     enter_insert_mode(doc);
     doc.restore_cursor = true;
 
     let text = doc.text().slice(..);
-    let selection = doc.selection().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(selection);
+    doc.set_selection(view_id, selection);
 }
 
 const COMMAND_LIST: &[&str] = &["write", "open", "quit"];
@@ -931,25 +971,27 @@ 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();
     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().transform(|range| {
+    let selection = doc.selection(view_id).transform(|range| {
         let pos = range.head + 1;
         Range::new(pos, pos)
     });
-    doc.set_selection(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();
     enter_insert_mode(doc);
 
-    let lines = selection_lines(doc.text(), doc.selection());
+    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)
@@ -991,16 +1033,17 @@ pub fn open_below(cx: &mut Context) {
     let transaction =
         Transaction::change(doc.text(), changes.into_iter()).with_selection(selection);
 
-    doc.apply(&transaction);
+    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();
     enter_insert_mode(doc);
 
-    let lines = selection_lines(doc.text(), doc.selection());
+    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
@@ -1043,26 +1086,27 @@ pub fn open_above(cx: &mut Context) {
     let transaction =
         Transaction::change(doc.text(), changes.into_iter()).with_selection(selection);
 
-    doc.apply(&transaction);
+    doc.apply(&transaction, view_id);
 }
 
 pub fn normal_mode(cx: &mut Context) {
+    let view_id = cx.view_id;
     let doc = cx.doc();
 
     doc.mode = Mode::Normal;
 
-    doc.append_changes_to_history();
+    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().transform(|range| {
+        let selection = doc.selection(view_id).transform(|range| {
             Range::new(
                 range.from(),
                 graphemes::prev_grapheme_boundary(text, range.to()),
             )
         });
-        doc.set_selection(selection);
+        doc.set_selection(view_id, selection);
 
         doc.restore_cursor = false;
     }
@@ -1071,13 +1115,15 @@ 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().clone())
+        (doc.id(), doc.selection(view_id).clone())
     };
     cx.view().jumps.push(jump);
 }
 
 pub fn goto_mode(cx: &mut Context) {
+    let view_id = cx.view_id;
     let count = cx.count;
 
     if count > 1 {
@@ -1087,7 +1133,7 @@ pub fn goto_mode(cx: &mut Context) {
         // to 1g
         let doc = cx.doc();
         let pos = doc.text().line_to_char(count - 1);
-        doc.set_selection(Selection::point(pos));
+        doc.set_selection(view_id, Selection::point(pos));
         return;
     }
 
@@ -1128,10 +1174,11 @@ 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 definition_pos = location.range.start;
         let new_pos = helix_lsp::util::lsp_pos_to_pos(doc.text(), definition_pos);
-        doc.set_selection(Selection::point(new_pos));
+        doc.set_selection(view_id, Selection::point(new_pos));
     }
 
     match locations.as_slice() {
@@ -1155,6 +1202,7 @@ 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 language_server = match doc.language_server() {
         Some(language_server) => language_server,
@@ -1162,7 +1210,7 @@ pub fn goto_definition(cx: &mut Context) {
     };
 
     // TODO: blocking here is not ideal
-    let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection().cursor());
+    let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view_id).cursor());
 
     // TODO: handle fails
     let res =
@@ -1171,6 +1219,7 @@ 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 language_server = match doc.language_server() {
         Some(language_server) => language_server,
@@ -1178,7 +1227,7 @@ pub fn goto_type_definition(cx: &mut Context) {
     };
 
     // TODO: blocking here is not ideal
-    let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection().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))
@@ -1187,6 +1236,7 @@ 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 language_server = match doc.language_server() {
         Some(language_server) => language_server,
@@ -1194,7 +1244,7 @@ pub fn goto_implementation(cx: &mut Context) {
     };
 
     // TODO: blocking here is not ideal
-    let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection().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))
@@ -1203,6 +1253,7 @@ 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 language_server = match doc.language_server() {
         Some(language_server) => language_server,
@@ -1210,7 +1261,7 @@ pub fn goto_reference(cx: &mut Context) {
     };
 
     // TODO: blocking here is not ideal
-    let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection().cursor());
+    let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view_id).cursor());
 
     // TODO: handle fails
     let res =
@@ -1219,6 +1270,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 language_server = match doc.language_server() {
@@ -1227,7 +1279,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().cursor());
+    let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view_id).cursor());
 
     // TODO: handle fails
 
@@ -1320,20 +1372,21 @@ 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();
 
         // 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(), c) {
-                doc.apply(&transaction);
+            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(), t);
+        let transaction = Transaction::insert(doc.text(), doc.selection(view_id), t);
 
-        doc.apply(&transaction);
+        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
@@ -1344,58 +1397,69 @@ pub mod insert {
     }
 
     pub fn insert_tab(cx: &mut Context) {
+        let view_id = cx.view_id;
         let doc = cx.doc();
         // 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(), indent);
-        doc.apply(&transaction);
+        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 text = doc.text().slice(..);
-        let transaction = Transaction::change_by_selection(doc.text(), doc.selection(), |range| {
-            // TODO: offset range.head by 1? when calculating?
-            let indent_level =
-                helix_core::indent::suggested_indent_for_pos(doc.syntax(), text, range.head, true);
-            let indent = doc.indent_unit().repeat(indent_level);
-            let mut text = String::with_capacity(1 + indent.len());
-            text.push('\n');
-            text.push_str(&indent);
-            (range.head, range.head, Some(text.into()))
-        });
-        doc.apply(&transaction);
+        let transaction =
+            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(),
+                    text,
+                    range.head,
+                    true,
+                );
+                let indent = doc.indent_unit().repeat(indent_level);
+                let mut text = String::with_capacity(1 + indent.len());
+                text.push('\n');
+                text.push_str(&indent);
+                (range.head, range.head, Some(text.into()))
+            });
+        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 text = doc.text().slice(..);
-        let transaction = Transaction::change_by_selection(doc.text(), doc.selection(), |range| {
-            (
-                graphemes::nth_prev_grapheme_boundary(text, range.head, count),
-                range.head,
-                None,
-            )
-        });
-        doc.apply(&transaction);
+        let transaction =
+            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);
     }
 
     pub fn delete_char_forward(cx: &mut Context) {
+        let view_id = cx.view_id;
         let count = cx.count;
         let doc = cx.doc();
         let text = doc.text().slice(..);
-        let transaction = Transaction::change_by_selection(doc.text(), doc.selection(), |range| {
-            (
-                range.head,
-                graphemes::nth_next_grapheme_boundary(text, range.head, count),
-                None,
-            )
-        });
-        doc.apply(&transaction);
+        let transaction =
+            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);
     }
 }
 
@@ -1405,20 +1469,23 @@ pub mod insert {
 // storing it?
 
 pub fn undo(cx: &mut Context) {
-    cx.doc().undo();
+    let view_id = cx.view_id;
+    cx.doc().undo(view_id);
 }
 
 pub fn redo(cx: &mut Context) {
-    cx.doc().redo();
+    let view_id = cx.view_id;
+    cx.doc().redo(view_id);
 }
 
 // Yank / Paste
 
 pub fn yank(cx: &mut Context) {
     // TODO: should selections be made end inclusive?
+    let view_id = cx.view_id;
     let doc = cx.doc();
     let values: Vec<String> = doc
-        .selection()
+        .selection(view_id)
         .fragments(doc.text().slice(..))
         .map(|cow| cow.into_owned())
         .collect();
@@ -1461,32 +1528,33 @@ 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 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(), |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(), |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);
-        doc.append_changes_to_history();
+        doc.apply(&transaction, view_id);
+        doc.append_changes_to_history(view_id);
     }
 }
 
-fn get_lines(doc: &Document) -> Vec<usize> {
+fn get_lines(doc: &Document, view_id: ViewId) -> Vec<usize> {
     let mut lines = Vec::new();
 
     // Get all line numbers
-    for range in doc.selection() {
+    for range in doc.selection(view_id) {
         let start = doc.text().char_to_line(range.from());
         let end = doc.text().char_to_line(range.to());
 
@@ -1500,8 +1568,9 @@ fn get_lines(doc: &Document) -> Vec<usize> {
 }
 
 pub fn indent(cx: &mut Context) {
+    let view_id = cx.view_id;
     let doc = cx.doc();
-    let lines = get_lines(doc);
+    let lines = get_lines(doc, view_id);
 
     // Indent by one level
     let indent = Tendril::from(doc.indent_unit());
@@ -1513,13 +1582,14 @@ pub fn indent(cx: &mut Context) {
             (pos, pos, Some(indent.clone()))
         }),
     );
-    doc.apply(&transaction);
-    doc.append_changes_to_history();
+    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);
+    let lines = get_lines(doc, view_id);
     let mut changes = Vec::with_capacity(lines.len());
     let tab_width = doc.tab_width();
 
@@ -1547,12 +1617,13 @@ pub fn unindent(cx: &mut Context) {
 
     let transaction = Transaction::change(doc.text(), changes.into_iter());
 
-    doc.apply(&transaction);
-    doc.append_changes_to_history();
+    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();
 
     // via lsp if available
@@ -1561,7 +1632,7 @@ pub fn format_selections(cx: &mut Context) {
     // TODO: blocking here is not ideal
 
     let ranges: Vec<lsp::Range> = doc
-        .selection()
+        .selection(view_id)
         .iter()
         .map(|range| helix_lsp::util::range_to_lsp_range(doc.text(), *range))
         .collect();
@@ -1583,14 +1654,15 @@ pub fn format_selections(cx: &mut Context) {
 
         let transaction = helix_lsp::util::generate_transaction_from_edits(doc.text(), edits);
 
-        doc.apply(&transaction);
+        doc.apply(&transaction, view_id);
     }
 
-    doc.append_changes_to_history();
+    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 text = doc.text();
     let slice = doc.text().slice(..);
@@ -1598,7 +1670,7 @@ pub fn join_selections(cx: &mut Context) {
     let mut changes = Vec::new();
     let fragment = Tendril::from(" ");
 
-    for selection in doc.selection() {
+    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 {
@@ -1629,17 +1701,18 @@ pub fn join_selections(cx: &mut Context) {
     // TODO: select inserted spaces
     // .with_selection(selection);
 
-    doc.apply(&transaction);
-    doc.append_changes_to_history();
+    doc.apply(&transaction, view_id);
+    doc.append_changes_to_history(view_id);
 }
 
 pub fn keep_selections(cx: &mut Context) {
     // keep selections matching regex
-    let prompt = ui::regex_prompt(cx, "keep:".to_string(), |doc, regex| {
+    let view_id = cx.view_id;
+    let prompt = ui::regex_prompt(cx, "keep:".to_string(), move |doc, regex| {
         let text = doc.text().slice(..);
 
-        if let Some(selection) = selection::keep_matches(text, doc.selection(), &regex) {
-            doc.set_selection(selection);
+        if let Some(selection) = selection::keep_matches(text, doc.selection(view_id), &regex) {
+            doc.set_selection(view_id, selection);
         }
     });
 
@@ -1647,11 +1720,12 @@ 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 range = doc.selection().primary();
+    let range = doc.selection(view_id).primary();
     let selection = Selection::single(range.anchor, range.head);
-    doc.set_selection(selection);
+    doc.set_selection(view_id, selection);
 }
 
 //
@@ -1700,6 +1774,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 language_server = match doc.language_server() {
@@ -1708,13 +1783,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().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().cursor();
+    let trigger_offset = doc.selection(view_id).cursor();
 
     cx.callback(
         res,
@@ -1751,6 +1826,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 language_server = match doc.language_server() {
@@ -1762,7 +1838,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().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))
@@ -1798,33 +1874,36 @@ 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());
+    let transaction = comment::toggle_line_comments(doc.text(), doc.selection(view_id));
 
-    doc.apply(&transaction);
-    doc.append_changes_to_history();
+    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();
 
     if let Some(syntax) = doc.syntax() {
         let text = doc.text().slice(..);
-        let selection = object::expand_selection(syntax, text, doc.selection());
-        doc.set_selection(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();
 
     if let Some(syntax) = doc.syntax() {
-        let pos = doc.selection().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(selection);
+            doc.set_selection(view_id, selection);
         };
     }
 }
@@ -1836,6 +1915,7 @@ pub fn jump_forward(cx: &mut Context) {
     let view = cx.view();
 
     if let Some((id, selection)) = view.jumps.forward(count) {
+        // TODO: position first_line so that main cursor is centered
         view.first_line = 0;
         view.doc = *id;
     };
@@ -1846,11 +1926,13 @@ pub fn jump_backward(cx: &mut Context) {
     let view = cx.view();
 
     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(selection);
+        doc.set_selection(view_id, selection);
     };
 }
 
diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs
index 322e5b7b..9c753007 100644
--- a/helix-term/src/ui/completion.rs
+++ b/helix-term/src/ui/completion.rs
@@ -21,6 +21,7 @@ use lsp::CompletionItem;
 pub struct Completion {
     popup: Popup<Menu<CompletionItem>>, // TODO: Popup<Menu> need to be able to access contents.
     trigger_offset: usize,
+    // TODO: maintain a completioncontext with trigger kind & trigger char
 }
 
 impl Completion {
@@ -43,7 +44,9 @@ impl Completion {
                         // doc.state = snapshot.clone();
                     }
                     PromptEvent::Validate => {
-                        let id = editor.view().doc;
+                        let view = editor.view();
+                        let view_id = view.id;
+                        let id = view.doc;
                         let doc = &mut editor.documents[id];
 
                         // revert state to what it was before the last update
@@ -89,18 +92,18 @@ impl Completion {
                         }
 
                         // if more text was entered, remove it
-                        let cursor = doc.selection().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);
+                            doc.apply(&remove, view_id);
                         }
 
                         let transaction =
                             util::generate_transaction_from_edits(doc.text(), vec![edit]);
-                        doc.apply(&transaction);
+                        doc.apply(&transaction, view_id);
                     }
                     _ => (),
                 };
@@ -124,15 +127,32 @@ impl Component for Completion {
         {
             // recompute menu based on matches
             let menu = self.popup.contents();
-            let id = cx.editor.view().doc;
+            let view = cx.editor.view();
+            let view_id = view.id;
+            let id = view.doc;
             let doc = cx.editor.document(id).unwrap();
 
-            let cursor = doc.selection().cursor();
+            // cx.hooks()
+            // cx.add_hook(enum type,  ||)
+            // cx.trigger_hook(enum type, &str, ...) <-- there has to be enough to identify doc/view
+            // callback with editor & compositor
+            //
+            // trigger_hook sends event into channel, that's consumed in the global loop and
+            // triggers all registered callbacks
+            // TODO: hooks should get processed immediately so maybe do it after select!(), before
+            // looping?
+
+            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
+                // keypress isn't included in the editor layer yet...
+                // so we can't use ..= for now.
                 let text = Cow::from(fragment);
                 // TODO: logic is same as ui/picker
                 menu.score(&text);
+
+                // TODO: if after scoring the selection is 0 items, remove popup
             }
         }
 
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs
index 1f4bf6bd..7ceeb6ca 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -64,7 +64,7 @@ impl EditorView {
         // TODO: this seems to prevent setting style later
         // surface.set_style(viewport, theme.get("ui.background"));
 
-        self.render_diagnostics(&doc, area, surface, theme, is_focused);
+        self.render_diagnostics(&doc, view, area, surface, theme, is_focused);
 
         let area = Rect::new(
             viewport.x,
@@ -224,7 +224,7 @@ impl EditorView {
             let selection_style = Style::default().bg(Color::Rgb(84, 0, 153));
 
             for selection in doc
-                .selection()
+                .selection(view.id)
                 .iter()
                 .filter(|range| range.overlaps(&screen))
             {
@@ -332,6 +332,7 @@ impl EditorView {
     pub fn render_diagnostics(
         &self,
         doc: &Document,
+        view: &View,
         viewport: Rect,
         surface: &mut Surface,
         theme: &Theme,
@@ -344,7 +345,7 @@ impl EditorView {
             widgets::{Paragraph, Widget},
         };
 
-        let cursor = doc.selection().cursor();
+        let cursor = doc.selection(view.id).cursor();
         let line = doc.text().char_to_line(cursor);
 
         let diagnostics = doc.diagnostics.iter().filter(|diagnostic| {
@@ -486,11 +487,14 @@ impl Component for EditorView {
                 EventResult::Consumed(None)
             }
             Event::Key(event) => {
-                let id = cx.editor.view().doc;
+                let view = cx.editor.view();
+                let view_id = view.id;
+                let id = view.doc;
                 let mode = cx.editor.document(id).unwrap().mode();
 
                 let mut cxt = commands::Context {
                     editor: &mut cx.editor,
+                    view_id,
                     count: 1,
                     callback: None,
                     callbacks: cx.callbacks,
diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs
index 47c75d2f..341a30e0 100644
--- a/helix-term/src/ui/mod.rs
+++ b/helix-term/src/ui/mod.rs
@@ -35,7 +35,8 @@ pub fn regex_prompt(
     prompt: String,
     fun: impl Fn(&mut Document, Regex) + 'static,
 ) -> Prompt {
-    let snapshot = cx.doc().selection().clone();
+    let view_id = cx.view().id;
+    let snapshot = cx.doc().selection(view_id).clone();
 
     Prompt::new(
         prompt,
@@ -44,9 +45,11 @@ pub fn regex_prompt(
             match event {
                 PromptEvent::Abort => {
                     // TODO: also revert text
-                    let id = editor.view().doc;
+                    let view = editor.view();
+                    let view_id = view.id;
+                    let id = view.doc;
                     let doc = &mut editor.documents[id];
-                    doc.set_selection(snapshot.clone());
+                    doc.set_selection(view_id, snapshot.clone());
                 }
                 PromptEvent::Validate => {
                     // TODO: push_jump to store selection just before jump
@@ -60,16 +63,18 @@ pub fn regex_prompt(
                     match Regex::new(input) {
                         Ok(regex) => {
                             // let view = &mut editor.view_mut();
-                            let id = editor.view().doc;
+                            let view = editor.view();
+                            let view_id = view.id;
+                            let id = view.doc;
                             let doc = &mut editor.documents[id];
 
                             // revert state to what it was before the last update
                             // TODO: also revert text
-                            doc.set_selection(snapshot.clone());
+                            doc.set_selection(view_id, snapshot.clone());
 
                             fun(doc, regex);
 
-                            editor.ensure_cursor_in_view(editor.view().id);
+                            editor.ensure_cursor_in_view(view_id);
                         }
                         Err(_err) => (), // TODO: mark command line as error
                     }
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs
index cf160cca..d79cddde 100644
--- a/helix-view/src/document.rs
+++ b/helix-view/src/document.rs
@@ -8,7 +8,9 @@ use helix_core::{
     ChangeSet, Diagnostic, History, Rope, Selection, State, Syntax, Transaction,
 };
 
-use crate::DocumentId;
+use crate::{DocumentId, ViewId};
+
+use std::collections::HashMap;
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash)]
 pub enum Mode {
@@ -21,6 +23,7 @@ pub struct Document {
     // rope + selection
     pub(crate) id: DocumentId,
     state: State,
+    pub(crate) selections: HashMap<ViewId, Selection>,
 
     path: Option<PathBuf>,
 
@@ -73,6 +76,7 @@ impl Document {
             id: DocumentId::default(),
             path: None,
             state: State::new(text),
+            selections: HashMap::default(),
             mode: Mode::Normal,
             restore_cursor: false,
             syntax: None,
@@ -178,12 +182,12 @@ impl Document {
         self.language_server = language_server;
     }
 
-    pub fn set_selection(&mut self, selection: Selection) {
+    pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) {
         // TODO: use a transaction?
-        self.state.selection = selection;
+        self.selections.insert(view_id, selection);
     }
 
-    fn _apply(&mut self, transaction: &Transaction) -> bool {
+    fn _apply(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
         let old_doc = self.text().clone();
 
         let success = transaction.changes().apply(&mut self.state.doc);
@@ -191,10 +195,11 @@ impl Document {
         if !transaction.changes().is_empty() {
             // update the selection: either take the selection specified in the transaction, or map the
             // current selection through changes.
-            self.state.selection = transaction
+            let selection = transaction
                 .selection()
                 .cloned()
-                .unwrap_or_else(|| self.selection().clone().map(transaction.changes()));
+                .unwrap_or_else(|| self.selection(view_id).clone().map(transaction.changes()));
+            self.set_selection(view_id, selection);
 
             self.version += 1;
 
@@ -227,14 +232,14 @@ impl Document {
         success
     }
 
-    pub fn apply(&mut self, transaction: &Transaction) -> bool {
+    pub fn apply(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
         // store the state just before any changes are made. This allows us to undo to the
         // state just before a transaction was applied.
         if self.changes.is_empty() && !transaction.changes().is_empty() {
             self.old_state = Some(self.state.clone());
         }
 
-        let success = self._apply(&transaction);
+        let success = self._apply(&transaction, view_id);
 
         self.modified = true;
         // TODO: be smarter about modified by keeping track of saved version instead. That way if
@@ -249,9 +254,9 @@ impl Document {
         success
     }
 
-    pub fn undo(&mut self) -> bool {
+    pub fn undo(&mut self, view_id: ViewId) -> bool {
         if let Some(transaction) = self.history.undo() {
-            let success = self._apply(&transaction);
+            let success = self._apply(&transaction, view_id);
 
             // reset changeset to fix len
             self.changes = ChangeSet::new(self.text());
@@ -261,9 +266,9 @@ impl Document {
         false
     }
 
-    pub fn redo(&mut self) -> bool {
+    pub fn redo(&mut self, view_id: ViewId) -> bool {
         if let Some(transaction) = self.history.redo() {
-            let success = self._apply(&transaction);
+            let success = self._apply(&transaction, view_id);
 
             // reset changeset to fix len
             self.changes = ChangeSet::new(self.text());
@@ -273,7 +278,7 @@ impl Document {
         false
     }
 
-    pub fn append_changes_to_history(&mut self) {
+    pub fn append_changes_to_history(&mut self, view_id: ViewId) {
         if self.changes.is_empty() {
             return;
         }
@@ -282,7 +287,8 @@ impl Document {
         let changes = std::mem::replace(&mut self.changes, new_changeset);
         // Instead of doing this messy merge we could always commit, and based on transaction
         // annotations either add a new layer or compose into the previous one.
-        let transaction = Transaction::from(changes).with_selection(self.selection().clone());
+        let transaction =
+            Transaction::from(changes).with_selection(self.selection(view_id).clone());
 
         // HAXX: we need to reconstruct the state as it was before the changes..
         let old_state = self.old_state.take().expect("no old_state available");
@@ -362,8 +368,8 @@ impl Document {
         &self.state.doc
     }
 
-    pub fn selection(&self) -> &Selection {
-        &self.state.selection
+    pub fn selection(&self, view_id: ViewId) -> &Selection {
+        &self.selections[&view_id]
     }
 
     pub fn relative_path(&self) -> Option<&Path> {
@@ -400,13 +406,14 @@ mod test {
         use helix_lsp::{lsp, Client};
         let text = Rope::from("hello");
         let mut doc = Document::new(text);
-        doc.set_selection(Selection::single(5, 5));
+        let view = ViewId::default();
+        doc.set_selection(view, Selection::single(5, 5));
 
         // insert
 
-        let transaction = Transaction::insert(doc.text(), doc.selection(), " world".into());
+        let transaction = Transaction::insert(doc.text(), doc.selection(view), " world".into());
         let old_doc = doc.state.clone();
-        doc.apply(&transaction);
+        doc.apply(&transaction, view);
         let changes = Client::changeset_to_changes(&old_doc.doc, doc.text(), transaction.changes());
 
         assert_eq!(
@@ -425,7 +432,7 @@ mod test {
 
         let transaction = transaction.invert(&old_doc.doc);
         let old_doc = doc.state.clone();
-        doc.apply(&transaction);
+        doc.apply(&transaction, view);
         let changes = Client::changeset_to_changes(&old_doc.doc, doc.text(), transaction.changes());
 
         // line: 0-based.
@@ -450,13 +457,13 @@ mod test {
 
         // also tests that changes are layered, positions depend on previous changes.
 
-        doc.state.selection = Selection::single(0, 5);
+        doc.set_selection(view, Selection::single(0, 5));
         let transaction = Transaction::change(
             &doc.state.doc,
             vec![(0, 2, Some("aei".into())), (3, 5, Some("ou".into()))].into_iter(),
         );
         // aeilou
-        doc.apply(&transaction);
+        doc.apply(&transaction, view);
         let changes =
             Client::changeset_to_changes(&doc.state.doc, doc.text(), transaction.changes());
 
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index f062b55d..f74bbb13 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -91,24 +91,40 @@ impl Editor {
         };
 
         use crate::tree::Layout;
+        use helix_core::Selection;
         match action {
             Action::Replace => {
                 let view = self.view();
-                let jump = (view.doc, self.documents[view.doc].selection().clone());
+                let jump = (
+                    view.doc,
+                    self.documents[view.doc].selection(view.id).clone(),
+                );
 
                 let view = self.view_mut();
                 view.jumps.push(jump);
                 view.doc = id;
                 view.first_line = 0;
+                let view_id = view.id;
+
+                // initialize selection for view
+                let doc = &mut self.documents[id];
+                doc.selections.insert(view_id, Selection::point(0));
+
                 return Ok(id);
             }
             Action::HorizontalSplit => {
                 let view = View::new(id)?;
-                self.tree.split(view, Layout::Horizontal);
+                let view_id = self.tree.split(view, Layout::Horizontal);
+                // initialize selection for view
+                let doc = &mut self.documents[id];
+                doc.selections.insert(view_id, Selection::point(0));
             }
             Action::VerticalSplit => {
                 let view = View::new(id)?;
-                self.tree.split(view, Layout::Vertical);
+                let view_id = self.tree.split(view, Layout::Vertical);
+                // initialize selection for view
+                let doc = &mut self.documents[id];
+                doc.selections.insert(view_id, Selection::point(0));
             }
         }
 
@@ -134,6 +150,9 @@ impl Editor {
             smol::block_on(language_server.text_document_did_close(doc.identifier())).unwrap();
         }
 
+        // remove selection
+        self.documents[view.doc].selections.remove(&id);
+
         // self.documents.remove(view.doc);
         self.tree.remove(id);
         self._refresh();
@@ -183,7 +202,7 @@ impl Editor {
         const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
         let view = self.view();
         let doc = &self.documents[view.doc];
-        let cursor = doc.selection().cursor();
+        let cursor = doc.selection(view.id).cursor();
         if let Some(mut pos) = view.screen_coords_at_pos(doc, doc.text().slice(..), cursor) {
             pos.col += view.area.x as usize + OFFSET as usize;
             pos.row += view.area.y as usize;
diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs
index 9e4c1373..89caaf3e 100644
--- a/helix-view/src/view.rs
+++ b/helix-view/src/view.rs
@@ -74,7 +74,7 @@ impl View {
     }
 
     pub fn ensure_cursor_in_view(&mut self, doc: &Document) {
-        let cursor = doc.selection().cursor();
+        let cursor = doc.selection(self.id).cursor();
         let line = doc.text().char_to_line(cursor);
         let document_end = self.first_line + (self.area.height as usize).saturating_sub(2);