packages feed

text-region-0.3.0.0: src/Data/Text/Region.hs

{-# LANGUAGE RankNTypes, TupleSections, FlexibleContexts, MultiParamTypeClasses, FlexibleInstances #-}

module Data.Text.Region (
	pt, start, lineStart, regionLength, till, linesSize, regionLines, emptyRegion, line,
	regionSize, expandLines, atRegion, overlaps, applyMap, cutMap, insertMap,
	cutRegion, insertRegion,
	EditAction(..), replace, cut, paste, overwrite, apply, update, undo,

	module Data.Text.Region.Types
	) where

import Prelude hiding (id, (.))
import Prelude.Unicode

import Control.Category
import Control.Lens

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)

-- | Does regions overlaps
overlaps ∷ Region → Region → Bool
overlaps l r
	| r ^. regionFrom ≥ l ^. regionTo = False
	| r ^. regionTo ≤ l ^. regionFrom = False
	| otherwise = True

applyMap ∷ Map → Region → Region
applyMap = view ∘ mapIso

-- | 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 (region sets insertion point and data size)
-- Region tries not to extend if data inserted at region bound except when region is empty
-- This allows define replace as cut and insert in special case when we replace region itself
insertRegion ∷ Region → Region → Region
insertRegion (Region is ie) (Region s e)
	| (s ≡ e) ∧ (is ≡ s) = Region is ie
	| otherwise = Region
		(if is ≤ s then (s .-. is) .+. ie else s)
		(if is < e then (e .-. is) .+. ie else e)

class Editable s ⇒ EditAction e s where
	-- | Make replace action over 'Region' and 'Contents'
	replaceAction ∷ Region → Contents s → e s
	-- | Make 'Map' from action
	actionMap ∷ e s → Map
	-- | Perform action, modifying 'Contents'
	perform ∷ e s → Contents s → Contents s
	-- | Get action undo
	inversed ∷ e s → Contents s → e s

-- | Replace region with data
replace ∷ EditAction e s ⇒ Region → s → e s
replace r = replaceAction r ∘ view contents

-- | Cuts region
cut ∷ EditAction e s ⇒ Region → e s
cut r = replaceAction r emptyContents

-- | Pastes 'Contents' at some 'Point'
paste ∷ EditAction e s ⇒ Point → s → e s
paste p = replaceAction (p `till` p) ∘ view contents

-- | Overwrites 'Contents' at some 'Point'
overwrite ∷ EditAction e s ⇒ Point → s → e s
overwrite p c = replaceAction (p `regionSize` measure cts) cts where
	cts = view contents c

-- | 'perform' for 'Edit'
apply ∷ Editable s ⇒ Edit s → s → s
apply = over contents ∘ perform

-- | Get undo
undo ∷ Editable s ⇒ Edit s → s → Edit s
undo e = inversed e ∘ view contents

-- | Update regions
update ∷ (Editable s, Regioned r) ⇒ Edit s → r → r
update e = over regions (applyMap ∘ actionMap $ e)

instance Editable s ⇒ EditAction Replace s where
	replaceAction = Replace
	actionMap (Replace r w) = insertMap (r & regionLength .~ measure w) `mappend` cutMap r
	perform (Replace r w) cts = cts & atRegion r .~ w
	inversed (Replace r w) cts = Replace (r & regionLength .~ measure w) (cts ^. atRegion r)

instance Editable s ⇒ EditAction Edit s where
	replaceAction rgn txt = Edit [replaceAction rgn txt]
	actionMap = foldr go mempty ∘ view replaces where
		go r m = actionMap (over replaceRegion (applyMap m) r) `mappend` m
	perform = snd ∘ foldr go (mempty, id) ∘ view replaces where
		go r (m, fn) = (actionMap r' `mappend` m, perform r' ∘ fn) where
			r' = over replaceRegion (applyMap m) r
	inversed e@(Edit rs) cts = Edit [Replace (applyMap m r) (cts ^. atRegion r) | Replace r _ ← rs] where
		m = actionMap e