-- | Provide mapping between position in stale content and current. module Futhark.LSP.PositionMapping ( mappingFromDiff, PositionMapping, toStalePos, toCurrentLoc, StaleFile (..), ) where import Data.Algorithm.Diff (Diff, PolyDiff (Both, First, Second), getDiff) import Data.Bifunctor (Bifunctor (bimap, first, second)) import Data.Text qualified as T import Data.Vector qualified as V import Futhark.Util.Loc (Loc (Loc), Pos (Pos)) import Language.LSP.VFS (VirtualFile) -- | A mapping between current file content and the stale (last successful compiled) file content. -- Currently, only supports entire line mapping, -- more detailed mapping might be achieved via referring to haskell-language-server@efb4b94 data PositionMapping = PositionMapping { -- | The mapping from stale position to current. -- e.g. staleToCurrent[2] = 4 means "line 2" in the stale file, corresponds to "line 4" in the current file. staleToCurrent :: V.Vector Int, -- | The mapping from current position to stale. currentToStale :: V.Vector Int } deriving (Show) -- | Stale pretty document stored in state. data StaleFile = StaleFile { -- | The last successfully compiled file content. -- Using VirtualFile for convenience, we can use anything with {version, content} staleContent :: VirtualFile, -- | PositionMapping between current and stale file content. -- Nothing if last type-check is successful. staleMapping :: Maybe PositionMapping } deriving (Show) -- | Compute PositionMapping using the diff between two texts. mappingFromDiff :: [T.Text] -> [T.Text] -> PositionMapping mappingFromDiff stale current = do let (stale_to_current, current_to_stale) = rawMapping (getDiff stale current) 0 0 PositionMapping (V.fromList stale_to_current) (V.fromList current_to_stale) where rawMapping :: [Diff T.Text] -> Int -> Int -> ([Int], [Int]) rawMapping [] _ _ = ([], []) rawMapping (Both _ _ : xs) lold lnew = bimap (lnew :) (lold :) $ rawMapping xs (lold + 1) (lnew + 1) rawMapping (First _ : xs) lold lnew = first (-1 :) $ rawMapping xs (lold + 1) lnew rawMapping (Second _ : xs) lold lnew = second (-1 :) $ rawMapping xs lold (lnew + 1) -- | Transform current Pos to the stale pos for query -- Note: line and col in Pos is larger by one toStalePos :: Maybe PositionMapping -> Pos -> Maybe Pos toStalePos (Just (PositionMapping _ current_to_stale)) pos = if l > Prelude.length current_to_stale then Nothing else Just $ Pos file (V.unsafeIndex current_to_stale (l - 1) + 1) c o where Pos file l c o = pos toStalePos Nothing pos = Just pos -- some refactoring might be needed, same logic as toStalePos toCurrentPos :: Maybe PositionMapping -> Pos -> Maybe Pos toCurrentPos (Just (PositionMapping stale_to_current _)) pos = if l > Prelude.length stale_to_current then Nothing else Just $ Pos file (V.unsafeIndex stale_to_current (l - 1) + 1) c o where Pos file l c o = pos toCurrentPos Nothing pos = Just pos -- | Transform stale Loc gotten from stale AST to current Loc. toCurrentLoc :: Maybe PositionMapping -> Loc -> Maybe Loc toCurrentLoc mapping loc = do let Loc start end = loc current_start <- toCurrentPos mapping start current_end <- toCurrentPos mapping end Just $ Loc current_start current_end