diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index 38c0d88a..dcc6433b 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -39,7 +39,8 @@ impl Application {
 
         let files = args.values_of_t::<PathBuf>("files").unwrap();
         for file in files {
-            editor.open(file)?;
+            use helix_view::editor::Action;
+            editor.open(file, Action::HorizontalSplit)?;
         }
 
         compositor.push(Box::new(ui::EditorView::new()));
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 9544b0e0..12d80a0f 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -798,7 +798,8 @@ pub fn command_mode(cx: &mut Context) {
                     // editor.should_close = true,
                 }
                 ["o", path] | ["open", path] => {
-                    editor.open(path.into());
+                    use helix_view::editor::Action;
+                    editor.open(path.into(), Action::Replace);
                 }
                 ["w"] | ["write"] => {
                     // TODO: non-blocking via save() command
@@ -992,11 +993,13 @@ pub fn exit_select_mode(cx: &mut Context) {
 }
 
 fn goto(cx: &mut Context, locations: Vec<lsp::Location>) {
+    use helix_view::editor::Action;
     cx.doc().mode = Mode::Normal;
 
     match locations.as_slice() {
         [location] => {
-            cx.editor.open(PathBuf::from(location.uri.path()));
+            cx.editor
+                .open(PathBuf::from(location.uri.path()), Action::Replace);
             let doc = cx.doc();
             let definition_pos = location.range.start;
             let new_pos = helix_lsp::util::lsp_pos_to_pos(doc.text(), definition_pos);
@@ -1012,7 +1015,7 @@ fn goto(cx: &mut Context, locations: Vec<lsp::Location>) {
                     format!("{}:{}", file, line).into()
                 },
                 move |editor: &mut Editor, item| {
-                    editor.open(PathBuf::from(item.uri.path()));
+                    editor.open(PathBuf::from(item.uri.path()), Action::Replace);
                     // TODO: issues with doc already being broo
                     let id = editor.view().doc;
                     let doc = &mut editor.documents[id];
diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs
index f91a9f35..a625aa14 100644
--- a/helix-term/src/ui/mod.rs
+++ b/helix-term/src/ui/mod.rs
@@ -101,7 +101,10 @@ pub fn file_picker(root: &str) -> Picker<PathBuf> {
             path.strip_prefix("./").unwrap().to_str().unwrap().into()
         },
         move |editor: &mut Editor, path: &PathBuf| {
-            let document_id = editor.open(path.into()).expect("editor.open failed");
+            use helix_view::editor::Action;
+            let document_id = editor
+                .open(path.into(), Action::Replace)
+                .expect("editor.open failed");
         },
     )
 }
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index 0407f344..a2c3c101 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -16,6 +16,12 @@ pub struct Editor {
     pub executor: &'static smol::Executor<'static>,
 }
 
+pub enum Action {
+    Replace,
+    HorizontalSplit,
+    VerticalSplit,
+}
+
 impl Editor {
     pub fn new(executor: &'static smol::Executor<'static>, mut area: tui::layout::Rect) -> Self {
         let theme = Theme::default();
@@ -41,50 +47,63 @@ impl Editor {
         }
     }
 
-    pub fn open(&mut self, path: PathBuf) -> Result<DocumentId, Error> {
-        let existing_view = self.documents().find(|doc| doc.path() == Some(&path));
+    pub fn open(&mut self, path: PathBuf, action: Action) -> Result<DocumentId, Error> {
+        let id = self
+            .documents()
+            .find(|doc| doc.path() == Some(&path))
+            .map(|doc| doc.id);
 
-        // TODO:
-        // if view with doc, focus it
-        // else open new split
+        let id = if let Some(id) = id {
+            id
+        } else {
+            let mut doc = Document::load(path, self.theme.scopes())?;
 
-        // if let Some((view, _)) = existing_view {
-        //     let id = view.doc.id;
-        //     self.tree.focus = view.id;
-        //     return Ok(id);
-        // }
+            // try to find a language server based on the language name
+            let language_server = doc
+                .language
+                .as_ref()
+                .and_then(|language| self.language_servers.get(language, self.executor));
 
-        let mut doc = Document::load(path, self.theme.scopes())?;
+            if let Some(language_server) = language_server {
+                doc.set_language_server(Some(language_server.clone()));
 
-        // try to find a language server based on the language name
-        let language_server = doc
-            .language
-            .as_ref()
-            .and_then(|language| self.language_servers.get(language, self.executor));
+                let language_id = doc
+                    .language()
+                    .and_then(|s| s.split('.').last()) // source.rust
+                    .map(ToOwned::to_owned)
+                    .unwrap_or_default();
 
-        if let Some(language_server) = language_server {
-            doc.set_language_server(Some(language_server.clone()));
+                smol::block_on(language_server.text_document_did_open(
+                    doc.url().unwrap(),
+                    doc.version(),
+                    doc.text(),
+                    language_id,
+                ))
+                .unwrap();
+            }
 
-            let language_id = doc
-                .language()
-                .and_then(|s| s.split('.').last()) // source.rust
-                .map(ToOwned::to_owned)
-                .unwrap_or_default();
+            let id = self.documents.insert(doc);
+            self.documents[id].id = id;
+            id
+        };
 
-            smol::block_on(language_server.text_document_did_open(
-                doc.url().unwrap(),
-                doc.version(),
-                doc.text(),
-                language_id,
-            ))
-            .unwrap();
+        use crate::tree::Layout;
+        match action {
+            Action::Replace => {
+                self.view_mut().doc = id;
+                // TODO: reset selection?
+                return Ok(id);
+            }
+            Action::HorizontalSplit => {
+                let view = View::new(id)?;
+                self.tree.split(view, Layout::Horizontal);
+            }
+            Action::VerticalSplit => {
+                let view = View::new(id)?;
+                self.tree.split(view, Layout::Vertical);
+            }
         }
 
-        let id = self.documents.insert(doc);
-        self.documents[id].id = id;
-
-        let view = View::new(id)?;
-        self.tree.insert(view);
         self._refresh();
 
         Ok(id)
diff --git a/helix-view/src/tree.rs b/helix-view/src/tree.rs
index 4751f840..f7ef7806 100644
--- a/helix-view/src/tree.rs
+++ b/helix-view/src/tree.rs
@@ -28,10 +28,10 @@ pub enum Content {
 }
 
 impl Node {
-    pub fn container() -> Self {
+    pub fn container(layout: Layout) -> Self {
         Node {
             parent: ViewId::default(),
-            content: Content::Container(Box::new(Container::new())),
+            content: Content::Container(Box::new(Container::new(layout))),
         }
     }
 
@@ -45,6 +45,7 @@ impl Node {
 
 // TODO: screen coord to container + container coordinate helpers
 
+#[derive(PartialEq, Eq)]
 pub enum Layout {
     Horizontal,
     Vertical,
@@ -58,9 +59,9 @@ pub struct Container {
 }
 
 impl Container {
-    pub fn new() -> Self {
+    pub fn new(layout: Layout) -> Self {
         Self {
-            layout: Layout::Horizontal,
+            layout,
             children: Vec::new(),
             area: Rect::default(),
         }
@@ -69,13 +70,13 @@ impl Container {
 
 impl Default for Container {
     fn default() -> Self {
-        Self::new()
+        Self::new(Layout::Horizontal)
     }
 }
 
 impl Tree {
     pub fn new(area: Rect) -> Self {
-        let root = Node::container();
+        let root = Node::container(Layout::Horizontal);
 
         let mut nodes = HopSlotMap::with_key();
         let root = nodes.insert(root);
@@ -131,6 +132,79 @@ impl Tree {
         node
     }
 
+    pub fn split(&mut self, view: View, layout: Layout) -> ViewId {
+        let focus = self.focus;
+        let parent = self.nodes[focus].parent;
+
+        let node = Node::view(view);
+        let node = self.nodes.insert(node);
+        self.get_mut(node).id = node;
+
+        let container = match &mut self.nodes[parent] {
+            Node {
+                content: Content::Container(container),
+                ..
+            } => container,
+            _ => unreachable!(),
+        };
+
+        if container.layout == layout {
+            // insert node after the current item if there is children already
+            let pos = if container.children.is_empty() {
+                0
+            } else {
+                let pos = container
+                    .children
+                    .iter()
+                    .position(|&child| child == focus)
+                    .unwrap();
+                pos + 1
+            };
+            container.children.insert(pos, node);
+            self.nodes[node].parent = parent;
+        } else {
+            let split = Node::container(layout);
+            let split = self.nodes.insert(split);
+
+            let container = match &mut self.nodes[split] {
+                Node {
+                    content: Content::Container(container),
+                    ..
+                } => container,
+                _ => unreachable!(),
+            };
+            container.children.push(focus);
+            container.children.push(node);
+            self.nodes[focus].parent = split;
+            self.nodes[node].parent = split;
+
+            let container = match &mut self.nodes[parent] {
+                Node {
+                    content: Content::Container(container),
+                    ..
+                } => container,
+                _ => unreachable!(),
+            };
+
+            let pos = container
+                .children
+                .iter()
+                .position(|&child| child == focus)
+                .unwrap();
+
+            // replace focus on parent with split
+            container.children[pos] = split;
+        }
+
+        // focus the new node
+        self.focus = node;
+
+        // recalculate all the sizes
+        self.recalculate();
+
+        node
+    }
+
     pub fn remove(&mut self, index: ViewId) {
         let mut stack = Vec::new();