module Development.IDE.Core.PositionMapping
( PositionMapping(..)
, PositionResult(..)
, lowerRange
, upperRange
, positionResultToMaybe
, fromCurrentPosition
, toCurrentPosition
, PositionDelta(..)
, addDelta
, mkDelta
, toCurrentRange
, fromCurrentRange
, applyChange
, zeroMapping
, toCurrent
, fromCurrent
) where
import Control.Monad
import qualified Data.Text as T
import Language.Haskell.LSP.Types
import Data.List
data PositionResult a
= PositionRange
{ unsafeLowerRange :: a
, unsafeUpperRange :: a }
| PositionExact !a
deriving (Eq,Ord,Show,Functor)
lowerRange :: PositionResult a -> a
lowerRange (PositionExact a) = a
lowerRange (PositionRange lower _) = lower
upperRange :: PositionResult a -> a
upperRange (PositionExact a) = a
upperRange (PositionRange _ upper) = upper
positionResultToMaybe :: PositionResult a -> Maybe a
positionResultToMaybe (PositionExact a) = Just a
positionResultToMaybe _ = Nothing
instance Applicative PositionResult where
pure = PositionExact
(PositionExact f) <*> a = fmap f a
(PositionRange f g) <*> (PositionExact a) = PositionRange (f a) (g a)
(PositionRange f g) <*> (PositionRange lower upper) = PositionRange (f lower) (g upper)
instance Monad PositionResult where
(PositionExact a) >>= f = f a
(PositionRange lower upper) >>= f = PositionRange lower' upper'
where
lower' = lowerRange $ f lower
upper' = upperRange $ f upper
data PositionDelta = PositionDelta
{ toDelta :: !(Position -> PositionResult Position)
, fromDelta :: !(Position -> PositionResult Position)
}
fromCurrentPosition :: PositionMapping -> Position -> Maybe Position
fromCurrentPosition (PositionMapping pm) = positionResultToMaybe . fromDelta pm
toCurrentPosition :: PositionMapping -> Position -> Maybe Position
toCurrentPosition (PositionMapping pm) = positionResultToMaybe . toDelta pm
newtype PositionMapping = PositionMapping PositionDelta
toCurrentRange :: PositionMapping -> Range -> Maybe Range
toCurrentRange mapping (Range a b) =
Range <$> toCurrentPosition mapping a <*> toCurrentPosition mapping b
fromCurrentRange :: PositionMapping -> Range -> Maybe Range
fromCurrentRange mapping (Range a b) =
Range <$> fromCurrentPosition mapping a <*> fromCurrentPosition mapping b
zeroMapping :: PositionMapping
zeroMapping = PositionMapping idDelta
composeDelta :: PositionDelta
-> PositionDelta
-> PositionDelta
composeDelta (PositionDelta to1 from1) (PositionDelta to2 from2) =
PositionDelta (to1 <=< to2)
(from1 >=> from2)
idDelta :: PositionDelta
idDelta = PositionDelta pure pure
mkDelta :: [TextDocumentContentChangeEvent] -> PositionDelta
mkDelta cs = foldl' applyChange idDelta cs
addDelta :: PositionDelta -> PositionMapping -> PositionMapping
addDelta delta (PositionMapping pm) = PositionMapping (composeDelta delta pm)
applyChange :: PositionDelta -> TextDocumentContentChangeEvent -> PositionDelta
applyChange PositionDelta{..} (TextDocumentContentChangeEvent (Just r) _ t) = PositionDelta
{ toDelta = toCurrent r t <=< toDelta
, fromDelta = fromDelta <=< fromCurrent r t
}
applyChange posMapping _ = posMapping
toCurrent :: Range -> T.Text -> Position -> PositionResult Position
toCurrent (Range start@(Position startLine startColumn) end@(Position endLine endColumn)) t (Position line column)
| line < startLine || line == startLine && column < startColumn =
PositionExact $ Position line column
| line > endLine || line == endLine && column >= endColumn =
PositionExact $ Position newLine newColumn
| otherwise = PositionRange start end
where
lineDiff = linesNew - linesOld
linesNew = T.count "\n" t
linesOld = endLine - startLine
newEndColumn
| linesNew == 0 = startColumn + T.length t
| otherwise = T.length $ T.takeWhileEnd (/= '\n') t
!newColumn
| line == endLine = column + newEndColumn - endColumn
| otherwise = column
!newLine = line + lineDiff
fromCurrent :: Range -> T.Text -> Position -> PositionResult Position
fromCurrent (Range start@(Position startLine startColumn) end@(Position endLine endColumn)) t (Position line column)
| line < startLine || line == startLine && column < startColumn =
PositionExact $ Position line column
| line > newEndLine || line == newEndLine && column >= newEndColumn =
PositionExact $ Position newLine newColumn
| otherwise = PositionRange start end
where
lineDiff = linesNew - linesOld
linesNew = T.count "\n" t
linesOld = endLine - startLine
newEndLine = endLine + lineDiff
newEndColumn
| linesNew == 0 = startColumn + T.length t
| otherwise = T.length $ T.takeWhileEnd (/= '\n') t
!newColumn
| line == newEndLine = column - (newEndColumn - endColumn)
| otherwise = column
!newLine = line - lineDiff