diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2016 Chris Penner
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/rasa-ext-cursors.cabal b/rasa-ext-cursors.cabal
new file mode 100644
--- /dev/null
+++ b/rasa-ext-cursors.cabal
@@ -0,0 +1,39 @@
+name:                rasa-ext-cursors
+version:             0.1.0.0
+synopsis:            Rasa Ext adding cursor(s)
+description:         Rasa Ext adding cursor(s)
+homepage:            https://github.com/ChrisPenner/rasa/
+license:             MIT
+license-file:        LICENSE
+author:              Chris Penner
+maintainer:          christopher.penner@gmail.com
+copyright:           2016 Chris Penner
+category:            Extension
+build-type:          Simple
+-- extra-source-files:
+cabal-version:       >=1.10
+
+library
+  hs-source-dirs:      src
+  exposed-modules:     Rasa.Ext.Cursors
+  other-modules:
+                       Rasa.Ext.Cursors.Actions
+                       Rasa.Ext.Cursors.Base
+  build-depends:       base >= 4.7 && < 5
+                     , rasa
+                     , rasa-ext-style
+                     , text-lens
+                     , lens
+                     , mtl
+                     , text
+                     , yi-rope
+                     , data-default
+  default-language:    Haskell2010
+
+  default-extensions:
+
+  ghc-options:         -Wall
+
+source-repository head
+  type:     git
+  location: https://github.com/ChrisPenner/rasa
diff --git a/src/Rasa/Ext/Cursors.hs b/src/Rasa/Ext/Cursors.hs
new file mode 100644
--- /dev/null
+++ b/src/Rasa/Ext/Cursors.hs
@@ -0,0 +1,31 @@
+module Rasa.Ext.Cursors
+  (
+  -- * Main
+  cursors
+  -- * Actions
+  , delete
+  , insertText
+  , findNext
+  , findNextFrom
+  , findPrev
+  , findPrevFrom
+
+  -- * Working with Cursor Ranges
+  , eachRange
+  , addRange
+  , ranges
+  , rangeDo
+  , rangeDo_
+  , overRanges
+  , moveRangesByN
+  , moveRangesByC
+  ) where
+
+import Rasa.Ext.Cursors.Base
+import Rasa.Ext.Cursors.Actions
+
+import Rasa.Ext
+
+-- | Registers hooks for the extension. The user should add this to their config.
+cursors :: Scheduler ()
+cursors = beforeRender $ bufDo displayRange
diff --git a/src/Rasa/Ext/Cursors/Actions.hs b/src/Rasa/Ext/Cursors/Actions.hs
new file mode 100644
--- /dev/null
+++ b/src/Rasa/Ext/Cursors/Actions.hs
@@ -0,0 +1,79 @@
+module Rasa.Ext.Cursors.Actions
+  ( delete
+  , insertText
+  , findNext
+  , findPrev
+  , findNextFrom
+  , findPrevFrom
+  , moveRangesByN
+  , moveRangesByC
+  ) where
+
+import qualified Data.Text as T
+
+import Control.Lens
+import Control.Lens.Text
+import Rasa.Ext
+import Rasa.Ext.Cursors.Base
+
+-- | Moves all Ranges that are on the same END row as the given range by the coord's row and column
+-- This is used to adjust cursors when things have been inserted/deleted before them in the row.
+moveSameLineRangesBy :: Range -> Coord -> BufAction ()
+moveSameLineRangesBy (Range _ (Coord endRow endCol)) amt = do
+  let moveInLine r@(Range (Coord startRow startCol) _) = return $
+        if endRow == startRow && startCol > endCol
+           then moveRange amt r
+           else r
+  ranges <~ rangeDo moveInLine
+
+-- | Delete the text of all ranges in a buffer
+delete :: BufAction ()
+delete = rangeDo_ $ \r -> do
+  deleteRange r
+  moveSameLineRangesBy r (negate $ sizeOfR r)
+
+-- | Insert text at the beginning of all ranges in the buffer.
+insertText :: T.Text -> BufAction ()
+insertText txt = rangeDo_ $ \r@(Range s _) -> do
+  insertAt s txt
+  moveSameLineRangesBy r (Coord 0 (T.length txt))
+
+-- | Move all ranges to the location of the next occurence of the given text.
+findNext :: T.Text -> BufAction ()
+findNext pat = do
+  res <- rangeDo $ \(Range _ e) -> do
+    off <- findNextFrom pat e
+    let end = moveCursorByN 1 off
+    return $ Range off end
+  ranges .= res
+
+-- | Get the 'Coord' of the next occurence of the given text after the given 'Coord'
+findNextFrom :: T.Text -> Coord -> BufAction Coord
+findNextFrom pat c = do
+  distance <- use (rope . afterC c . asText . tillNext pat . from asText . to sizeOf)
+  return (distance + c)
+
+-- | Move all ranges to the location of the previous occurence of the given text.
+findPrev :: T.Text -> BufAction ()
+findPrev pat = do
+  res <- rangeDo $ \(Range s _) -> do
+    off <- findPrevFrom pat s
+    let end = moveCursorByN 1 off
+    return $ Range off end
+  ranges .= res
+
+-- | Get the 'Coord' of the previous occurence of the given text before the given 'Coord'
+findPrevFrom :: T.Text -> Coord -> BufAction Coord
+findPrevFrom pat c = do
+  txt <- use rope
+  let Offset o = c^.from (asCoord txt)
+  distance <- use (text . before o . tillPrev pat . to T.length .to negate)
+  return ((Offset $ distance + o)^.asCoord txt)
+
+-- | Move all ranges by the given number of columns
+moveRangesByN :: Int -> BufAction ()
+moveRangesByN n = overRanges $ return . moveRangeByN n
+
+-- | Move all ranges by the given number of rows and columns
+moveRangesByC :: Coord -> BufAction ()
+moveRangesByC c = overRanges $ return . moveRange c
diff --git a/src/Rasa/Ext/Cursors/Base.hs b/src/Rasa/Ext/Cursors/Base.hs
new file mode 100644
--- /dev/null
+++ b/src/Rasa/Ext/Cursors/Base.hs
@@ -0,0 +1,79 @@
+{-# LANGUAGE TemplateHaskell, OverloadedStrings, Rank2Types #-}
+
+module Rasa.Ext.Cursors.Base
+  ( rangeDo
+  , rangeDo_
+  , ranges
+  , displayRange
+  , eachRange
+  , overRanges
+  , addRange
+  ) where
+
+
+import Rasa.Ext
+import Rasa.Ext.Style
+
+import Control.Monad.State
+import Control.Lens
+import Data.Typeable
+import Data.List
+import Data.Default
+import qualified Yi.Rope as Y
+
+-- | Stores the cursor ranges in each buffer.
+data Cursors = Cursors
+  { _cursors :: [Range]
+  } deriving (Typeable, Show)
+
+makeLenses ''Cursors
+
+instance Default Cursors where
+  def = Cursors {
+  _cursors=[Range (Coord 0 0) (Coord 0 1)]
+}
+
+-- | Adjusts input ranges to contain at least one character.
+ensureSize :: Range -> Range
+ensureSize r@(Range s e)
+  | s == e = Range s (moveCursorByN 1 e)
+  | otherwise = r
+
+-- | Sorts Ranges, removes duplicates, ensures they contain at least one character
+-- and restricts them to fit within the given text.
+cleanRanges :: Y.YiString -> [Range] -> [Range]
+cleanRanges txt = fmap (ensureSize . clampRange txt) . reverse . nub . sort
+
+-- | A lens over all the stored cursor ranges for a buffer
+ranges :: Lens' Buffer [Range]
+ranges = lens getter setter
+  where getter buf = buf^.bufExt.cursors
+        setter buf new = let txt = buf^.rope
+                          in buf & bufExt.cursors .~ cleanRanges txt new
+
+-- | A Traversal over each Range for the given buffer.
+eachRange :: Traversal' Buffer Range
+eachRange = ranges.traverse
+
+-- | Sequences actions over each range as a 'BufAction'
+rangeDo :: (Range -> BufAction a) -> BufAction [a]
+rangeDo f = use ranges >>= mapM f
+
+-- | 'rangeDo' with void return.
+rangeDo_ :: (Range -> BufAction a) -> BufAction ()
+rangeDo_ = void . rangeDo
+
+-- | Sequences actions over each range and replaces each range with its result.
+overRanges :: (Range -> BufAction Range) -> BufAction ()
+overRanges f = ranges <~ rangeDo f
+
+-- | Adds a new range to the list of ranges.
+addRange :: Range -> BufAction ()
+addRange r = ranges <>= [r]
+
+-- | Sets style attributes to show a given range.
+displayRange ::  BufAction ()
+displayRange = rangeDo_ setStyle
+  where
+    setStyle :: Range -> BufAction ()
+    setStyle r = addStyle r (flair ReverseVideo)
