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/src/Data/Text/Region.hs b/src/Data/Text/Region.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Text/Region.hs
@@ -0,0 +1,221 @@
+{-# LANGUAGE RankNTypes, TupleSections, FlexibleContexts, MultiParamTypeClasses, FlexibleInstances #-}
+
+module Data.Text.Region (
+	pt, start, lineStart, regionLength, till, linesSize, regionLines, emptyRegion, line,
+	regionSize, expandLines, atRegion, ApplyMap(..), updateMap, cutMap, insertMap,
+	cutRegion, insertRegion,
+	EditAction(..), cut, paste, overwrite, inverse, applyEdit, apply,
+	edit, edit_, push, run_, run, runGroup, undo, redo,
+
+	module Data.Text.Region.Types
+	) where
+
+import Prelude hiding (id, (.))
+import Prelude.Unicode
+
+import Control.Arrow
+import Control.Category
+import Control.Lens
+import Control.Monad.State
+
+import Data.Text.Region.Types
+
+-- | Make 'Point' from line and column
+pt ∷ Int → Int → Point
+pt = Point
+
+-- | 'Point' at the beginning
+start ∷ Point
+start = pt 0 0
+
+-- | 'Point' at the beginning of line
+lineStart ∷ Int → Point
+lineStart l = pt l 0
+
+-- | Regions length
+regionLength ∷ Lens' Region Size
+regionLength = lens fromr tor where
+	fromr (Region f t) = t .-. f
+	tor (Region f _) sz = Region f (f .+. sz)
+
+-- | Region from one 'Point' to another
+till ∷ Point → Point → Region
+l `till` r = Region (min l r) (max l r)
+
+-- | Distance of @n@ lines
+linesSize ∷ Int → Size
+linesSize = pt 0
+
+-- 'Region' height in lines, any 'Region' at least of line height 1
+regionLines ∷ Lens' Region Int
+regionLines = lens fromr tor where
+	fromr (Region f t) = succ $ (t ^. pointLine) - (f ^. pointLine)
+	tor (Region f t) l = Region f (set pointLine (f ^. pointLine + l) t)
+
+-- | Is 'Region' empty
+emptyRegion ∷ Region → Bool
+emptyRegion r = r ^. regionFrom ≡ r ^. regionTo
+
+-- | n'th line region, starts at the beginning of line and ends on the next line
+line ∷ Int → Region
+line l = lineStart l `till` lineStart (succ l)
+
+-- | Make 'Region' by start position and 'Size'
+regionSize ∷ Point → Size → Region
+regionSize pt' sz = pt' `till` (pt' .+. sz)
+
+-- | Expand 'Region' to contain full lines
+expandLines ∷ Region → Region
+expandLines (Region f t) = lineStart (f ^. pointLine) `till` lineStart (succ $ t ^. pointLine)
+
+-- | Get contents at 'Region'
+atRegion ∷ Editable s ⇒ Region → Lens' (Contents s) (Contents s)
+atRegion r = lens fromc toc where
+	fromc cts = cts ^. splitted (r ^. regionTo) . _1 . splitted (r ^. regionFrom) . _2
+	toc cts cts' = (cts ^. splitted (r ^. regionFrom) . _1) `concatCts` cts' `concatCts` (cts ^. splitted (r ^. regionTo) . _2)
+
+class ApplyMap a where
+	applyMap ∷ Map → a → a
+
+instance ApplyMap () where
+	applyMap _ = id
+
+instance ApplyMap a ⇒ ApplyMap [a] where
+	applyMap m = map (applyMap m)
+
+instance ApplyMap Map where
+	applyMap = mappend
+
+instance ApplyMap Region where
+	applyMap = view ∘ mapIso
+
+instance ApplyMap Point where
+	applyMap m p = view regionFrom $ applyMap m (p `till` p)
+
+instance ApplyMap (Replace s) where
+	applyMap m (Replace r w) = Replace (applyMap m r) w
+
+instance ApplyMap (e s) ⇒ ApplyMap (Chain e s) where
+	applyMap m (Chain rs) = Chain (map (applyMap m) rs)
+
+-- | Update 'Region' after some action
+updateMap ∷ (EditAction e s, ApplyMap a) ⇒ e s → a → a
+updateMap = applyMap ∘ actionMap
+
+-- | Cut 'Region' mapping
+cutMap ∷ Region → Map
+cutMap rgn = Map $ iso (cutRegion rgn) (insertRegion rgn)
+
+-- | Opposite to 'cutMap'
+insertMap ∷ Region → Map
+insertMap = invert ∘ cutMap
+
+-- | Update second 'Region' position as if it was data cutted at first 'Region'
+cutRegion ∷ Region → Region → Region
+cutRegion (Region is ie) (Region s e) = Region
+	(if is < s then (s .-. ie) .+. is else s)
+	(if is < e then (e .-. ie) .+. is else e)
+
+-- | Update second region position as if it was data inserted at first region
+insertRegion ∷ Region → Region → Region
+insertRegion (Region is ie) (Region s e) = Region
+	(if is < s then (s .-. is) .+. ie else s)
+	(if is < e then (e .-. is) .+. ie else e)
+
+class (Editable s, ApplyMap (e s)) ⇒ EditAction e s where
+	-- | Make replace action over 'Region' and 'Contents'
+	replace ∷ Region → Contents s → e s
+	-- | Make 'Map' from action
+	actionMap ∷ e s → Map
+	-- | Perform action, modifying 'Contents' and returning inverse (undo) action
+	perform ∷ e s → State (Contents s) (e s)
+
+-- | Cuts region
+cut ∷ EditAction e s ⇒ Region → e s
+cut r = replace r emptyContents
+
+-- | Pastes 'Contents' at some 'Point'
+paste ∷ EditAction e s ⇒ Point → Contents s → e s
+paste p = replace (p `till` p)
+
+-- | Overwrites 'Contents' at some 'Point'
+overwrite ∷ EditAction e s ⇒ Point → Contents s → e s
+overwrite p c = replace (p `regionSize` measure c) c
+
+-- | Get undo-action
+inverse ∷ EditAction e s ⇒ Contents s → e s → e s
+inverse cts act = evalState (perform act) cts
+
+-- | Apply action to 'Contents'
+applyEdit ∷ EditAction e s ⇒ e s → Contents s → Contents s
+applyEdit act = snd ∘ runState (perform act)
+
+-- | 'applyEdit' for 'Edit'
+apply ∷ EditAction Replace s ⇒ Edit s → Contents s → Contents s
+apply = applyEdit
+
+instance Editable s ⇒ EditAction Replace s where
+	replace = Replace
+	actionMap (Replace r w) = insertMap ((r ^. regionFrom) `regionSize` measure w) `mappend` cutMap r
+	perform (Replace r w) = state $ \cts → (Replace ((r ^. regionFrom) `regionSize` measure w) (cts ^. atRegion r), atRegion r .~ w $ cts)
+
+instance EditAction e s ⇒ EditAction (Chain e) s where
+	replace rgn txt = Chain [replace rgn txt]
+	actionMap (Chain []) = mempty
+	actionMap (Chain (r : rs)) = actionMap (applyMap (actionMap r) (Chain rs)) `mappend` actionMap r
+	perform (Chain rs) = (Chain ∘ reverse) <$> go mempty rs where
+		go _ [] = return []
+		go m (c : cs) = (:) <$> perform (applyMap m c) <*> go (actionMap (applyMap m c) `mappend` m) cs
+
+-- | Run edit monad and return result with updated contents
+edit ∷ EditAction Replace s ⇒ s → r → EditM s r a → (a, s)
+edit txt rs act = second (view $ edited . from contents) $ runState (runEditM act) (editState txt rs)
+
+-- | Run edit monad and return updated contents
+edit_ ∷ EditAction Replace s ⇒ s → r → EditM s r a → s
+edit_ txt rs = snd ∘ edit txt rs
+
+-- | Push action into history, also drops redo stack
+push ∷ EditAction Replace s ⇒ ActionIso (Edit s) → EditM s r ()
+push e = modify (over (history . undoStack) (e :)) >> modify (set (history . redoStack) [])
+
+-- | Run edit action and returns corresponding redo-undo action
+run_ ∷ (EditAction Replace s, ApplyMap r) ⇒ Edit s → EditM s r (ActionIso (Edit s))
+run_ e = do
+	cts ← gets (view edited)
+	let
+		(undo', cts') = runState (perform e) cts
+	modify (set edited cts')
+	modify (over regions (applyMap $ actionMap e))
+	return $ ActionIso e undo'
+
+-- | Run edit action with updating undo/redo stack
+run ∷ (EditAction Replace s, ApplyMap r) ⇒ Edit s → EditM s r ()
+run e = run_ e >>= push
+
+-- | Run edit actions, updating undo/redo stack for each of them, but act like they was applied simultaneously
+-- For example, cutting 1-st and then 3-rd letter:
+-- @run (cut first) >> run (cut third) -- 1234 -> 234 -> 23@
+-- @runGroup [cut first, cut third] -- 1234 -> 234 -> 24@
+runGroup ∷ (EditAction Replace s, ApplyMap r) ⇒ [Edit s] → EditM s r ()
+runGroup = go mempty where
+	go _ [] = return ()
+	go m (e:es) = run e' >> go (applyMap m $ actionMap e') es where
+		e' = applyMap m e
+
+-- | Undo last action
+undo ∷ (EditAction Replace s, ApplyMap r) ⇒ EditM s r ()
+undo = do
+	us@(~(u:_)) ← gets (view $ history . undoStack)
+	unless (null us) $ do
+		_ ← run_ (u ^. actionBack)
+		modify (over (history . undoStack) tail)
+		modify (over (history . redoStack) (u :))
+
+redo ∷ (EditAction Replace s, ApplyMap r) ⇒ EditM s r ()
+redo = do
+	rs@(~(r:_)) ← gets (view $ history . redoStack)
+	unless (null rs) $ do
+		_ ← run_ (r ^. action)
+		modify (over (history . redoStack) tail)
+		modify (over (history . undoStack) (r :))
diff --git a/src/Data/Text/Region/Types.hs b/src/Data/Text/Region/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Text/Region/Types.hs
@@ -0,0 +1,253 @@
+{-# LANGUAGE TemplateHaskell, RankNTypes, TypeSynonymInstances, FlexibleInstances, OverloadedStrings, GeneralizedNewtypeDeriving, FlexibleContexts #-}
+
+module Data.Text.Region.Types (
+	Point(..), pointLine, pointColumn, Size, (.-.), (.+.),
+	Region(..), regionFrom, regionTo,
+	Map(..),
+	Contents, emptyContents,
+	concatCts, splitCts, splitted,
+	Editable(..), contents, by, measure,
+	Replace(..), replaceRegion, replaceWith, Chain(..), chain, Edit,
+	ActionIso(..), action, actionBack,
+	ActionStack(..), undoStack, redoStack, emptyStack,
+	EditState(..), editState, history, edited, regions,
+	EditM(..),
+
+	module Data.Group
+	) where
+
+import Prelude hiding (id, (.))
+import Prelude.Unicode
+
+import Control.Category
+import Control.Lens hiding ((.=))
+import Control.Monad.State
+import Data.Aeson
+import qualified Data.ByteString.Lazy.Char8 as L
+import Data.Group
+import Data.List
+import Data.Text (Text)
+import qualified Data.Text as T
+
+-- | Point at text: zero-based line and column
+data Point = Point {
+	_pointLine ∷ Int,
+	_pointColumn ∷ Int }
+		deriving (Eq, Ord, Read, Show)
+
+makeLenses ''Point
+
+instance ToJSON Point where
+	toJSON (Point l c) = object ["line" .= l, "column" .= c]
+
+instance FromJSON Point where
+	parseJSON = withObject "point" $ \v → Point <$> v .: "line" <*> v .: "column"
+
+instance Monoid Point where
+	mempty = Point 0 0
+	Point l c `mappend` Point bl bc
+		| l ≡ 0 = Point bl (c + bc)
+		| otherwise = Point (l + bl) c
+
+instance Group Point where
+	invert (Point l c) = Point (negate l) (negate c)
+
+-- | Distance between 'Point's is measured in lines and columns.
+-- And it is defined, that distance between point at l:c and point (l + 1):0 is one line no matter c is
+-- because we need to go to new line to reach destination point
+-- Columns are taken into account only if points are on the same line
+type Size = Point
+
+-- | @pt .-. base@ is distance from @base@ to @pt@
+-- Distance can't be less then zero lines and columns
+(.-.) ∷ Point → Point → Point
+Point l c .-. Point bl bc
+	| bl < l = Point (l - bl) c
+	| bl ≡ l = Point 0 (max 0 (c - bc))
+	| otherwise = Point 0 0
+
+-- | Opposite to ".-.", @(pt .-. base) .+. base = pt@
+(.+.) ∷ Point → Point → Point
+(Point l c) .+. (Point bl bc)
+	| l ≡ 0 = Point bl (c + bc)
+	| otherwise = Point (l + bl) c
+
+-- | Region from 'Point' to another
+data Region = Region {
+	_regionFrom ∷ Point,
+	_regionTo ∷ Point }
+		deriving (Eq, Ord, Read, Show)
+
+makeLenses ''Region
+
+instance ToJSON Region where
+	toJSON (Region f t) = object ["from" .= f, "to" .= t]
+
+instance FromJSON Region where
+	parseJSON = withObject "region" $ \v -> Region <$> v .: "from" <*> v .: "to"
+
+-- | Main idea is that there are only two basic actions, that changes regions: inserting and cutting
+-- When something is cutted out or inserted in, 'Region' positions must be updated
+-- All editings can be represented as many cuts and inserts, so we can combine them to get function
+-- which maps source regions to regions on updated data
+-- Because insert is dual to cut (and therefore composes something like iso), we can also get function to map regions back
+-- Combining this functions while edit, we get function, that maps regions from source data to edited one
+-- To get back function, we must also combine opposite actions, or we can represent actions as 'Iso'
+-- Same idea goes for modifying contents, represent each action as isomorphism and combine them together
+newtype Map = Map { mapIso :: Iso' Region Region }
+
+instance Monoid Map where
+	mempty = Map $ iso id id
+	Map l `mappend` Map r = Map (r . l)
+
+instance Group Map where
+	invert (Map f) = Map (from f)
+
+-- | Contents is list of lines, list must have at least one (maybe empty) line
+type Contents a = [a]
+
+-- | Empty contents are contents with one empty line
+emptyContents ∷ Monoid a ⇒ Contents a
+emptyContents = [mempty]
+
+checkCts ∷ Contents a → Contents a
+checkCts [] = error "Contents can't be empty"
+checkCts cs = cs
+
+concatCts ∷ Monoid a ⇒ Contents a → Contents a → Contents a
+concatCts ls rs = init (checkCts ls) ++ [last (checkCts ls) `mappend` head (checkCts rs)] ++ tail (checkCts rs)
+
+-- | Split 'Contents' at some 'Point'
+splitCts ∷ Editable a ⇒ Point → Contents a → (Contents a, Contents a)
+splitCts (Point l c) cts = (take l cts ++ [p], s : drop (succ l) cts) where
+	(p, s) = splitContents c (cts !! l)
+
+-- | Get splitted 'Contents' at some 'Point'
+splitted ∷ Editable a ⇒ Point → Iso' (Contents a) (Contents a, Contents a)
+splitted p = iso (splitCts p) (uncurry concatCts)
+
+-- | Something editable, string types implements this
+class Monoid a ⇒ Editable a where
+	-- | Split editable at some position
+	splitContents ∷ Int → a → (a, a)
+	contentsLength ∷ a → Int
+	splitLines ∷ a → [a]
+	joinLines ∷ [a] → a
+
+-- | Get 'Contents' for some 'Editable', splitting lines
+contents ∷ (Editable a, Editable b) ⇒ Iso a b (Contents a) (Contents b)
+contents = iso splitLines joinLines
+
+by ∷ Editable a ⇒ a → Contents a
+by = splitLines
+
+instance Editable String where
+	splitContents = splitAt
+	contentsLength = length
+	splitLines s = case break (≡ '\n') s of
+		(pre', "") → [pre']
+		(pre', _:post') → pre' : splitLines post'
+	joinLines = intercalate "\n"
+
+instance Editable Text where
+	splitContents = T.splitAt
+	contentsLength = T.length
+	splitLines = T.split (≡ '\n')
+	joinLines = T.intercalate "\n"
+
+-- | Contents 'Size'
+measure ∷ Editable s ⇒ Contents s → Size
+measure [] = error "Invalid input"
+measure cts = Point (pred $ length cts) (contentsLength $ last cts)
+
+-- | Serializable edit action
+data Replace s = Replace {
+	-- | 'Region' to replace
+	_replaceRegion ∷ Region,
+	-- | 'Contents' to replace with
+	_replaceWith ∷ Contents s }
+		deriving (Eq)
+
+makeLenses ''Replace
+
+instance (Editable s, ToJSON s) ⇒ ToJSON (Replace s) where
+	toJSON (Replace e c) = object ["region" .= e, "contents" .= view (from contents) c]
+
+instance (Editable s, FromJSON s) ⇒ FromJSON (Replace s) where
+	parseJSON = withObject "edit" $ \v → Replace <$> v .: "region" <*> (view contents <$> v .: "contents")
+
+instance (Editable s, ToJSON s) ⇒ Show (Replace s) where
+	show = L.unpack ∘ encode
+
+-- | Chain of edit actions
+newtype Chain e s = Chain {
+	_chain ∷ [e s] } deriving (Eq, Show, Monoid)
+
+makeLenses ''Chain
+
+instance ToJSON (e s) ⇒ ToJSON (Chain e s) where
+	toJSON = toJSON ∘ _chain
+
+instance FromJSON (e s) ⇒ FromJSON (Chain e s) where
+	parseJSON = fmap Chain ∘ parseJSON
+
+type Edit s = Chain Replace s
+
+-- | Some action with its inverse
+data ActionIso e = ActionIso {
+	_action ∷ e,
+	_actionBack ∷ e }
+
+makeLenses ''ActionIso
+
+instance Monoid e ⇒ Monoid (ActionIso e) where
+	mempty = ActionIso mempty mempty
+	ActionIso l l' `mappend` ActionIso r r' = ActionIso (l `mappend` r) (r' `mappend` l')
+
+instance Monoid e ⇒ Group (ActionIso e) where
+	invert (ActionIso f b) = ActionIso b f
+
+instance ToJSON e ⇒ ToJSON (ActionIso e) where
+	toJSON (ActionIso f b) = object ["fore" .= f, "back" .= b]
+
+instance FromJSON e ⇒ FromJSON (ActionIso e) where
+	parseJSON = withObject "action-iso" $ \v → ActionIso <$> v .: "fore" <*> v .: "back"
+
+-- | Stack of undo/redo actions
+data ActionStack e = ActionStack {
+	_undoStack ∷ [ActionIso e],
+	_redoStack ∷ [ActionIso e] }
+
+makeLenses ''ActionStack
+
+instance ToJSON e ⇒ ToJSON (ActionStack e) where
+	toJSON (ActionStack u r) = object ["undo" .= u, "redo" .= r]
+
+instance FromJSON e ⇒ FromJSON (ActionStack e) where
+	parseJSON = withObject "action-stack" $ \v → ActionStack <$> v .: "undo" <*> v .: "redo"
+
+emptyStack ∷ ActionStack e
+emptyStack = ActionStack [] []
+
+-- | Edit state
+data EditState s r = EditState {
+	-- | Edit history is stack of edit actions
+	_history ∷ ActionStack (Edit s),
+	-- | Currently edited data
+	_edited ∷ Contents s,
+	-- | Some region-based state, that will be updated on each edit
+	_regions ∷ r }
+
+makeLenses ''EditState
+
+instance (Editable s, ToJSON s, ToJSON r) ⇒ ToJSON (EditState s r) where
+	toJSON (EditState h e rs) = object ["history" .= h, "contents" .= view (from contents) e, "regions" .= rs ]
+
+instance (Editable s, FromJSON s, FromJSON r) ⇒ FromJSON (EditState s r) where
+	parseJSON = withObject "edit-state" $ \v → EditState <$> v .: "history" <*> fmap (view contents) (v .: "contents") <*> v .: "regions"
+
+-- | Make edit state for contents
+editState ∷ Editable s ⇒ s → r → EditState s r
+editState x = EditState emptyStack (x ^. contents)
+
+newtype EditM s r a = EditM { runEditM ∷ State (EditState s r) a } deriving (Applicative, Functor, Monad, MonadState (EditState s r))
diff --git a/tests/Test.hs b/tests/Test.hs
new file mode 100644
--- /dev/null
+++ b/tests/Test.hs
@@ -0,0 +1,48 @@
+module Main (
+	main
+	) where
+
+import Prelude.Unicode
+
+import Control.Lens
+import Control.Monad.State
+import Data.Text.Region
+import Test.Hspec
+
+text ∷ String
+text = "foo bar baz quux"
+
+bar ∷ Region
+bar = pt 0 3 `till` pt 0 7
+
+quux ∷ Region
+quux = pt 0 11 `till` pt 0 16
+
+nums ∷ String
+nums = " 123456"
+
+xxx ∷ String
+xxx = "xxx "
+
+main ∷ IO ()
+main = hspec $ do
+	describe "regions are updated" $ do
+		it "should delete correctly" $
+			apply (cut quux `mappend` cut bar) (by text) ≡ by "foo baz"
+	describe "editor monad" $ do
+		it "should perform undo/redo" $
+			(≡ text) $ edit_ text () $ do
+				runGroup [
+					cut bar,
+					replace quux (by nums),
+					paste start (by xxx)]
+				undo >> redo >> undo >> undo >> undo
+		it "should reverse text" $
+			(≡ reverse text) $ edit_ text (pt 0 (length text)) $ replicateM_ (length text) $ do
+				-- cut first letter and insert at caret
+				let
+					l = pt 0 0 `till` pt 0 1
+				c ← gets (view regions)
+				cts ← gets (view edited)
+				run $ paste c (view (atRegion l) cts)
+				run $ cut l
diff --git a/text-region.cabal b/text-region.cabal
new file mode 100644
--- /dev/null
+++ b/text-region.cabal
@@ -0,0 +1,46 @@
+name:                text-region
+version:             0.1.0.0
+synopsis:            Provides functions to update text region positions according to text edit actions
+homepage:            https://github.com/mvoidex/text-region
+license:             BSD3
+author:              Alexandr `Voidex` Ruchkin
+maintainer:          voidex@live.com
+category:            Text
+build-type:          Simple
+cabal-version:       >=1.10
+
+library
+  hs-source-dirs: src
+  default-language: Haskell2010
+  ghc-options: -threaded -Wall -fno-warn-tabs
+  default-extensions: UnicodeSyntax
+  exposed-modules:     
+    Data.Text.Region
+    Data.Text.Region.Types
+  build-depends:
+    base >= 4.8 && < 4.9,
+    base-unicode-symbols >= 0.2,
+    aeson >= 0.9,
+    bytestring >= 0.10,
+    containers >= 0.5,
+    groups >= 0.4.0,
+    lens >= 4.12,
+    mtl >= 2.2,
+    text >= 1.2.1
+
+test-suite test
+  type: exitcode-stdio-1.0
+  main-is: Test.hs
+  hs-source-dirs: tests
+  ghc-options: -threaded -Wall -fno-warn-tabs
+  default-language: Haskell2010
+  default-extensions: UnicodeSyntax
+  build-depends:
+    base >= 4.8 && < 4.9,
+    base-unicode-symbols >= 0.2,
+    text-region,
+    hspec,
+    containers >= 0.5,
+    lens >= 4.12,
+    mtl >= 2.2,
+    text >= 1.2.1
