{-# LANGUAGE CPP #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Language.Haskell.LSP.VFS
(
VFS
, VirtualFile(..)
, openVFS
, changeFromClientVFS
, changeFromServerVFS
, closeVFS
, applyChanges
, applyChange
, changeChars
) where
import Control.Lens
import Control.Monad
import Data.Text ( Text )
import Data.List
import Data.Ord
#if __GLASGOW_HASKELL__ < 804
import Data.Monoid
#endif
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Map as Map
import Data.Maybe
import Data.Rope.UTF16 ( Rope )
import qualified Data.Rope.UTF16 as Rope
import qualified Language.Haskell.LSP.Types as J
import qualified Language.Haskell.LSP.Types.Lens as J
import Language.Haskell.LSP.Utility
{-# ANN module ("hlint: ignore Eta reduce" :: String) #-}
{-# ANN module ("hlint: ignore Redundant do" :: String) #-}
data VirtualFile =
VirtualFile {
_version :: Int
, _text :: Rope
} deriving (Show)
type VFS = Map.Map J.Uri VirtualFile
openVFS :: VFS -> J.DidOpenTextDocumentNotification -> IO VFS
openVFS vfs (J.NotificationMessage _ _ params) = do
let J.DidOpenTextDocumentParams
(J.TextDocumentItem uri _ version text) = params
return $ Map.insert uri (VirtualFile version (Rope.fromText text)) vfs
changeFromClientVFS :: VFS -> J.DidChangeTextDocumentNotification -> IO VFS
changeFromClientVFS vfs (J.NotificationMessage _ _ params) = do
let
J.DidChangeTextDocumentParams vid (J.List changes) = params
J.VersionedTextDocumentIdentifier uri version = vid
case Map.lookup uri vfs of
Just (VirtualFile _ str) -> do
let str' = applyChanges str changes
return $ Map.insert uri (VirtualFile (fromMaybe 0 version) str') vfs
Nothing -> do
logs $ "haskell-lsp:changeVfs:can't find uri:" ++ show uri
return vfs
changeFromServerVFS :: VFS -> J.ApplyWorkspaceEditRequest -> IO VFS
changeFromServerVFS initVfs (J.RequestMessage _ _ _ params) = do
let J.ApplyWorkspaceEditParams edit = params
J.WorkspaceEdit mChanges mDocChanges = edit
case mDocChanges of
Just (J.List textDocEdits) -> applyEdits textDocEdits
Nothing -> case mChanges of
Just cs -> applyEdits $ HashMap.foldlWithKey' changeToTextDocumentEdit [] cs
Nothing -> do
logs "haskell-lsp:changeVfs:no changes"
return initVfs
where
changeToTextDocumentEdit acc uri edits =
acc ++ [J.TextDocumentEdit (J.VersionedTextDocumentIdentifier uri (Just 0)) edits]
applyEdits = foldM f initVfs . sortOn (^. J.textDocument . J.version)
f vfs (J.TextDocumentEdit vid (J.List edits)) = do
let sortedEdits = sortOn (Down . (^. J.range)) edits
changeEvents = map editToChangeEvent sortedEdits
ps = J.DidChangeTextDocumentParams vid (J.List changeEvents)
notif = J.NotificationMessage "" J.TextDocumentDidChange ps
changeFromClientVFS vfs notif
editToChangeEvent (J.TextEdit range text) = J.TextDocumentContentChangeEvent (Just range) Nothing text
closeVFS :: VFS -> J.DidCloseTextDocumentNotification -> IO VFS
closeVFS vfs (J.NotificationMessage _ _ params) = do
let J.DidCloseTextDocumentParams (J.TextDocumentIdentifier uri) = params
return $ Map.delete uri vfs
applyChanges :: Rope -> [J.TextDocumentContentChangeEvent] -> Rope
applyChanges = foldl' applyChange
applyChange :: Rope -> J.TextDocumentContentChangeEvent -> Rope
applyChange _ (J.TextDocumentContentChangeEvent Nothing Nothing str)
= Rope.fromText str
applyChange str (J.TextDocumentContentChangeEvent (Just (J.Range (J.Position sl sc) _to)) (Just len) txt)
= changeChars str start len txt
where
start = Rope.rowColumnCodeUnits (Rope.RowColumn sl sc) str
applyChange str (J.TextDocumentContentChangeEvent (Just (J.Range (J.Position sl sc) (J.Position el ec))) Nothing txt)
= changeChars str start len txt
where
start = Rope.rowColumnCodeUnits (Rope.RowColumn sl sc) str
end = Rope.rowColumnCodeUnits (Rope.RowColumn el ec) str
len = end - start
applyChange str (J.TextDocumentContentChangeEvent Nothing (Just _) _txt)
= str
changeChars :: Rope -> Int -> Int -> Text -> Rope
changeChars str start len new = mconcat [before, Rope.fromText new, after']
where
(before, after) = Rope.splitAt start str
after' = Rope.drop len after