diff --git a/helix-core/src/buffer.rs b/helix-core/src/buffer.rs
deleted file mode 100644
index 746e01ea..00000000
--- a/helix-core/src/buffer.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-use anyhow::Error;
-use ropey::Rope;
-use std::{env, fs::File, io::BufReader, path::PathBuf};
-
-pub struct Buffer {
-    pub contents: Rope,
-}
-
-impl Buffer {
-    pub fn load(path: PathBuf) -> Result<Self, Error> {
-        let _current_dir = env::current_dir()?;
-
-        let contents = Rope::from_reader(BufReader::new(File::open(path)?))?;
-
-        // TODO: create if not found
-        Ok(Buffer { contents })
-    }
-}
diff --git a/helix-core/src/commands.rs b/helix-core/src/commands.rs
index a6b74449..70596045 100644
--- a/helix-core/src/commands.rs
+++ b/helix-core/src/commands.rs
@@ -65,6 +65,6 @@ pub fn insert(state: &mut State, c: char) {
     let pos = state.selection.primary().head;
     let changes = ChangeSet::insert(&state.doc, pos, c);
     // TODO: need to store history
-    changes.apply(state.contents_mut());
+    changes.apply(&mut state.doc);
     state.selection = state.selection.clone().map(&changes);
 }
diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs
index f9e987a6..c7c020d6 100644
--- a/helix-core/src/lib.rs
+++ b/helix-core/src/lib.rs
@@ -1,5 +1,4 @@
 #![allow(unused)]
-mod buffer;
 pub mod commands;
 mod graphemes;
 mod selection;
@@ -9,8 +8,6 @@ mod transaction;
 pub use ropey::{Rope, RopeSlice};
 pub use tendril::StrTendril as Tendril;
 
-pub use buffer::Buffer;
-
 pub use selection::Range as SelectionRange;
 pub use selection::Selection;
 
diff --git a/helix-core/src/state.rs b/helix-core/src/state.rs
index 8568c3c3..c3a5c3cd 100644
--- a/helix-core/src/state.rs
+++ b/helix-core/src/state.rs
@@ -1,5 +1,8 @@
 use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes};
-use crate::{Buffer, Rope, RopeSlice, Selection, SelectionRange};
+use crate::{Rope, RopeSlice, Selection, SelectionRange};
+use anyhow::Error;
+
+use std::path::PathBuf;
 
 pub enum Mode {
     Normal,
@@ -8,7 +11,9 @@ pub enum Mode {
 
 /// A state represents the current editor state of a single buffer.
 pub struct State {
-    pub doc: Buffer,
+    /// Path to file on disk.
+    pub path: Option<PathBuf>,
+    pub doc: Rope,
     pub selection: Selection,
     pub mode: Mode,
 }
@@ -28,15 +33,31 @@ pub enum Granularity {
 
 impl State {
     #[must_use]
-    pub fn new(doc: Buffer) -> Self {
+    pub fn new(doc: Rope) -> Self {
         Self {
+            path: None,
             doc,
             selection: Selection::single(0, 0),
             mode: Mode::Normal,
         }
     }
 
-    // TODO: buf/selection accessors
+    #[must_use]
+    pub fn load(path: PathBuf) -> Result<Self, Error> {
+        use std::{env, fs::File, io::BufReader, path::PathBuf};
+        let _current_dir = env::current_dir()?;
+
+        let doc = Rope::from_reader(BufReader::new(File::open(path.clone())?))?;
+
+        // TODO: create if not found
+
+        let mut state = Self::new(doc);
+        state.path = Some(path);
+
+        Ok(state)
+    }
+
+    // TODO: doc/selection accessors
 
     // update/transact:
     // update(desc) => transaction ?  transaction.doc() for applied doc
@@ -68,7 +89,7 @@ impl State {
         granularity: Granularity,
         count: usize,
     ) -> usize {
-        let text = &self.doc.contents;
+        let text = &self.doc;
         match (dir, granularity) {
             // TODO: clamp movement to line, prevent moving onto \n at the end
             (Direction::Backward, Granularity::Character) => {
@@ -125,16 +146,6 @@ impl State {
         Selection::new(ranges.collect(), sel.primary_index)
         // TODO: update selection in state via transaction
     }
-
-    pub fn contents(&self) -> &Rope {
-        // used to access file contents for rendering to screen
-        &self.doc.contents
-    }
-
-    pub fn contents_mut(&mut self) -> &mut Rope {
-        // used to access file contents for rendering to screen
-        &mut self.doc.contents
-    }
 }
 
 /// Coordinates are a 0-indexed line and column pair.
diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs
index 2d8afc9b..d078679c 100644
--- a/helix-core/src/transaction.rs
+++ b/helix-core/src/transaction.rs
@@ -1,4 +1,4 @@
-use crate::{Buffer, Rope, Selection, Tendril};
+use crate::{Rope, Selection, Tendril};
 
 // TODO: divided into three different operations, I sort of like having just
 // Splice { extent, Option<text>, distance } better.
@@ -39,16 +39,16 @@ pub struct ChangeSet {
 
 impl ChangeSet {
     #[must_use]
-    pub fn new(buf: &Buffer) -> Self {
-        let len = buf.contents.len_chars();
+    pub fn new(doc: &Rope) -> Self {
+        let len = doc.len_chars();
         Self {
             changes: vec![Change::Retain(len)],
             len,
         }
     }
 
-    pub fn insert(buf: &Buffer, pos: usize, c: char) -> Self {
-        let len = buf.contents.len_chars();
+    pub fn insert(doc: &Rope, pos: usize, c: char) -> Self {
+        let len = doc.len_chars();
         Self {
             changes: vec![
                 Change::Retain(pos),
diff --git a/helix-term/src/editor.rs b/helix-term/src/editor.rs
index ef4af3dc..4abc8477 100644
--- a/helix-term/src/editor.rs
+++ b/helix-term/src/editor.rs
@@ -9,7 +9,7 @@ use crossterm::{
     terminal::{self, disable_raw_mode, enable_raw_mode},
 };
 use futures::{future::FutureExt, select, StreamExt};
-use helix_core::{state::coords_at_pos, state::Mode, Buffer, State};
+use helix_core::{state::coords_at_pos, state::Mode, State};
 use std::io::{self, stdout, Write};
 use std::path::PathBuf;
 use std::time::Duration;
@@ -38,9 +38,7 @@ impl Editor {
     }
 
     pub fn open(&mut self, path: PathBuf) -> Result<(), Error> {
-        let buffer = Buffer::load(path)?;
-        let state = State::new(buffer);
-        self.state = Some(state);
+        self.state = Some(State::load(path)?);
         Ok(())
     }
 
@@ -48,7 +46,7 @@ impl Editor {
         match &self.state {
             Some(state) => {
                 let lines = state
-                    .contents()
+                    .doc
                     .lines_at(self.first_line as usize)
                     .take(self.size.1 as usize)
                     .map(|x| x.as_str().unwrap());
@@ -90,7 +88,7 @@ impl Editor {
 
                 // render the cursor
                 let pos = state.selection.primary().head;
-                let coords = coords_at_pos(&state.doc.contents.slice(..), pos);
+                let coords = coords_at_pos(&state.doc.slice(..), pos);
                 execute!(
                     stdout,
                     cursor::MoveTo((coords.1 + 2) as u16, coords.0 as u16)