{-# LANGUAGE CPP, TypeFamilies, OverloadedStrings #-} {-# LANGUAGE GeneralizedNewtypeDeriving, MultiParamTypeClasses #-} module Control.OperationalTransformation.Selection ( Range (..) , Selection (..) , createCursor , size , somethingSelected ) where import Control.OperationalTransformation import Control.OperationalTransformation.Text import Data.Aeson import Control.Applicative import Data.Monoid import Data.List (sort) import qualified Data.Text as T #if MIN_VERSION_ghc(7,8,0) import GHC.Exts (IsList (..)) #endif -- | Range has `anchor` and `head` properties, which are zero-based indices into -- the document. The `anchor` is the side of the selection that stays fixed, -- `head` is the side of the selection where the cursor is. When both are -- equal, the range represents a cursor. data Range = Range { rangeAnchor :: !Int, rangeHead :: !Int } deriving (Show, Read, Eq, Ord) instance ToJSON Range where toJSON (Range a h) = object [ "anchor" .= a, "head" .= h ] instance FromJSON Range where parseJSON (Object o) = Range <$> o .: "anchor" <*> o .: "head" parseJSON _ = fail "expected an object" instance OTCursor Range TextOperation where updateCursor (TextOperation actions) (Range a h) = Range a' h' where a' = updateComponent a h' = if a == h then a' else updateComponent h updateComponent c = loop c c actions loop :: Int -> Int -> [Action] -> Int loop oldIndex newIndex as | oldIndex < 0 = newIndex | otherwise = case as of (op:ops) -> case op of Retain r -> loop (oldIndex-r) newIndex ops Insert i -> loop oldIndex (newIndex + T.length i) ops Delete d -> loop (oldIndex-d) (newIndex - min oldIndex d) ops _ -> newIndex -- matching on `[]` gives a non-exhaustive pattern -- match warning for some reason -- | A selection consists of a list of ranges. Each range may represent a -- selected part of the document or a cursor in the document. newtype Selection = Selection { ranges :: [Range] } deriving (Monoid, Show, Read) instance OTCursor Selection TextOperation where updateCursor op = Selection . updateCursor op . ranges instance Eq Selection where Selection rs1 == Selection rs2 = sort rs1 == sort rs2 instance Ord Selection where Selection rs1 `compare` Selection rs2 = sort rs1 `compare` sort rs2 instance ToJSON Selection where toJSON (Selection rs) = object [ "ranges" .= rs ] instance FromJSON Selection where parseJSON (Object o) = Selection <$> o .: "ranges" parseJSON _ = fail "expected an object" #if MIN_VERSION_ghc(7,8,0) instance IsList Selection where type Item Selection = Range fromList = Selection toList = ranges #endif -- | Create a selection that represents a cursor. createCursor :: Int -> Selection createCursor i = Selection [Range i i] -- | Does the selection contain any characters? somethingSelected :: Selection -> Bool somethingSelected = any (\r -> rangeAnchor r /= rangeHead r) . ranges -- | Number of selected characters size :: Selection -> Int size = sum . map (\r -> abs (rangeAnchor r - rangeHead r)) . ranges