diff --git a/app/Main.hs b/app/Main.hs index 9073fcf..38301b4 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -33,18 +33,21 @@ import UnliftIO.Directory import UnliftIO.Exception import UnliftIO.Process -import Transform.ClientNot -import Transform.ClientReq -import Transform.ClientRsp -import Transform.ServerNot -import Transform.ServerReq -import Transform.ServerRsp -import Transform.Util - -import Parsing -import Process -import RequestMap +import Language.LSP.NotebookLanguageServer.Transform.ClientNot +import Language.LSP.NotebookLanguageServer.Transform.ClientReq +import Language.LSP.NotebookLanguageServer.Transform.ClientRsp +import Language.LSP.NotebookLanguageServer.Transform.ServerNot +import Language.LSP.NotebookLanguageServer.Transform.ServerReq +import Language.LSP.NotebookLanguageServer.Transform.ServerRsp +import Language.LSP.NotebookLanguageServer.Transform.Util + +import Language.LSP.NotebookLanguageServer.Parsing +import Language.LSP.NotebookLanguageServer.Process +import Language.LSP.NotebookLanguageServer.RequestMap +import Language.LSP.Notebook import Streams +import Language.LSP.NotebookLanguageServer.Config +import Transform.Config data Options = Options { @@ -141,20 +144,21 @@ main = do , _message = T.decodeUtf8 $ fromLogStr msg } - transformerState <- newTransformerState optDidSaveDebouncePeriodMs + let config = cppNotebookConfig optDidSaveDebouncePeriodMs + transformerState <- newTransformerState flip runLoggingT logFn $ filterLogger logFilterFn $ flip runReaderT transformerState $ - withAsync (readWrappedOut optDebugServerReads clientReqMap serverReqMap wrappedOut sendToStdout) $ \_wrappedOutAsync -> + withAsync (readWrappedOut config optDebugServerReads clientReqMap serverReqMap wrappedOut sendToStdout) $ \_wrappedOutAsync -> withAsync (readWrappedErr wrappedErr) $ \_wrappedErrAsync -> - withAsync (forever $ handleStdin optDebugServerWrites optDebugClientReads wrappedIn clientReqMap serverReqMap) $ \_stdinAsync -> do + withAsync (forever $ handleStdin config optDebugServerWrites optDebugClientReads wrappedIn clientReqMap serverReqMap) $ \_stdinAsync -> do waitForProcess p >>= \case ExitFailure n -> logErrorN [i|clangd subprocess exited with code #{n}|] ExitSuccess -> logInfoN [i|clangd subprocess exited successfully|] handleStdin :: forall m. ( - MonadLoggerIO m, MonadReader TransformerState m, MonadUnliftIO m, MonadFail m - ) => Bool -> Bool -> Handle -> MVar ClientRequestMap -> MVar ServerRequestMap -> m () -handleStdin debugServerWrites debugClientReads wrappedIn clientReqMap serverReqMap = do + MonadLoggerIO m, MonadReader (TransformerState CppNotebookTransformer) m, MonadUnliftIO m, MonadFail m + ) => NotebookConfig CppNotebookTransformer -> Bool -> Bool -> Handle -> MVar ClientRequestMap -> MVar ServerRequestMap -> m () +handleStdin config debugServerWrites debugClientReads wrappedIn clientReqMap serverReqMap = do (A.eitherDecode <$> liftIO (parseStream stdin)) >>= \case Left err -> logErr [i|Couldn't decode incoming message: #{err}|] Right (x :: A.Value) -> do @@ -171,9 +175,9 @@ handleStdin debugServerWrites debugClientReads wrappedIn clientReqMap serverReqM modifyMVar_ clientReqMap $ \m -> case updateClientRequestMap m msgId (SMethodAndParams meth (msg ^. Lens.params)) of Just m' -> return m' Nothing -> return m - transformClientReq meth msg >>= writeToServerHandle wrappedIn . A.encode + transformClientReq config meth msg >>= writeToServerHandle wrappedIn . A.encode Right (ClientToServerNot meth msg) -> - transformClientNot sendExtraNotification meth msg >>= writeToServerHandle wrappedIn . A.encode + transformClientNot config sendExtraNotification meth msg >>= writeToServerHandle wrappedIn . A.encode where writeToServerHandle :: Handle -> BL.ByteString -> m () writeToServerHandle h bs = do @@ -185,9 +189,9 @@ handleStdin debugServerWrites debugClientReads wrappedIn clientReqMap serverReqM liftIO $ writeToHandle wrappedIn $ A.encode msg readWrappedOut :: ( - MonadUnliftIO m, MonadLoggerIO m, MonadReader TransformerState m, MonadFail m - ) => Bool -> MVar ClientRequestMap -> MVar ServerRequestMap -> Handle -> (forall a. ToJSON a => a -> m ()) -> m b -readWrappedOut debugServerReads clientReqMap serverReqMap wrappedOut sendToStdout = forever $ do + MonadUnliftIO m, MonadLoggerIO m, MonadReader (TransformerState CppNotebookTransformer) m, MonadFail m + ) => NotebookConfig CppNotebookTransformer -> Bool -> MVar ClientRequestMap -> MVar ServerRequestMap -> Handle -> (forall a. ToJSON a => a -> m ()) -> m b +readWrappedOut config debugServerReads clientReqMap serverReqMap wrappedOut sendToStdout = forever $ do (A.eitherDecode <$> liftIO (parseStream wrappedOut)) >>= \case Left err -> logErr [i|Couldn't decode wrapped output: #{err}|] Right (x :: A.Value) -> do @@ -206,7 +210,7 @@ readWrappedOut debugServerReads clientReqMap serverReqMap wrappedOut sendToStdou Nothing -> return m sendToStdout (transformServerReq meth msg) Right (ServerToClientRsp meth initialParams msg) -> - transformServerRsp meth initialParams msg >>= sendToStdout + transformServerRsp config meth initialParams msg >>= sendToStdout readWrappedErr :: MonadLoggerIO m => Handle -> m () readWrappedErr wrappedErr = forever $ do diff --git a/app/Parsing.hs b/app/Parsing.hs deleted file mode 100644 index 187993f..0000000 --- a/app/Parsing.hs +++ /dev/null @@ -1,88 +0,0 @@ -{-# LANGUAGE ConstraintKinds #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE DuplicateRecordFields #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE PolyKinds #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE UndecidableInstances #-} -{-# OPTIONS_GHC -Wno-unticked-promoted-constructors #-} - -module Parsing where - -import Data.Aeson -import Data.Aeson.Types -import Data.Proxy -import Language.LSP.Protocol.Message hiding (LookupFunc) - - -data ServerToClientMessage where - ServerToClientReq :: forall (m :: Method ServerToClient Request). (HasJSON (TRequestMessage m)) => SMethod m -> TRequestMessage m -> ServerToClientMessage - ServerToClientNot :: forall (m :: Method ServerToClient Notification). (HasJSON (TNotificationMessage m)) => SMethod m -> TNotificationMessage m -> ServerToClientMessage - ServerToClientRsp :: forall (m :: Method ClientToServer Request). (HasJSON (TResponseMessage m)) => SMethod m -> MessageParams m -> TResponseMessage m -> ServerToClientMessage - -data ClientToServerMessage where - ClientToServerReq :: forall (m :: Method ClientToServer Request). (HasJSON (TRequestMessage m)) => SMethod m -> TRequestMessage m -> ClientToServerMessage - ClientToServerNot :: forall (m :: Method ClientToServer Notification). (HasJSON (TNotificationMessage m)) => SMethod m -> TNotificationMessage m -> ClientToServerMessage - ClientToServerRsp :: forall (m :: Method ServerToClient Request). (HasJSON (TResponseMessage m)) => SMethod m -> TResponseMessage m -> ClientToServerMessage - -type LookupFunc f = forall (m :: Method f 'Request). LspId m -> Maybe (SMethod m, MessageParams m) - -{-# INLINE parseServerMessage #-} -parseServerMessage :: LookupFunc ClientToServer -> Value -> Parser ServerToClientMessage -parseServerMessage lookupId v@(Object o) = do - methMaybe <- o .:! "method" - idMaybe <- o .:! "id" - case methMaybe of - -- Request or Notification - Just (SomeServerMethod m) -> - case splitServerMethod m of - IsServerNot -> ServerToClientNot m <$> parseJSON v - IsServerReq -> ServerToClientReq m <$> parseJSON v - IsServerEither | SMethod_CustomMethod (p :: Proxy s') <- m -> do - case idMaybe of - -- Request - Just _ -> - let m' = (SMethod_CustomMethod p :: SMethod (Method_CustomMethod s' :: Method ServerToClient Request)) - in ServerToClientReq m' <$> parseJSON v - Nothing -> - let m' = (SMethod_CustomMethod p :: SMethod (Method_CustomMethod s' :: Method ServerToClient Notification)) - in ServerToClientNot m' <$> parseJSON v - Nothing -> do - case idMaybe of - Just i -> do - case lookupId i of - Just (m, initialParams) -> clientResponseJSON m $ ServerToClientRsp m initialParams <$> parseJSON v - Nothing -> fail $ unwords ["Failed in looking up response type of", show v] - Nothing -> fail $ unwords ["Got unexpected message without method or id"] -parseServerMessage _ v = fail $ unwords ["parseServerMessage expected object, got:",show v] - -{-# INLINE parseClientMessage #-} -parseClientMessage :: LookupFunc ServerToClient -> Value -> Parser ClientToServerMessage -parseClientMessage lookupId v@(Object o) = do - methMaybe <- o .:! "method" - idMaybe <- o .:! "id" - case methMaybe of - -- Request or Notification - Just (SomeClientMethod m) -> - case splitClientMethod m of - IsClientNot -> ClientToServerNot m <$> parseJSON v - IsClientReq -> ClientToServerReq m <$> parseJSON v - IsClientEither | SMethod_CustomMethod (p :: Proxy s') <- m -> do - case idMaybe of - -- Request - Just _ -> - let m' = (SMethod_CustomMethod p :: SMethod (Method_CustomMethod s' :: Method ClientToServer Request)) - in ClientToServerReq m' <$> parseJSON v - Nothing -> - let m' = (SMethod_CustomMethod p :: SMethod (Method_CustomMethod s' :: Method ClientToServer Notification)) - in ClientToServerNot m' <$> parseJSON v - Nothing -> do - case idMaybe of - Just i -> do - case lookupId i of - Just (m, _initialParams) -> serverResponseJSON m $ ClientToServerRsp m <$> parseJSON v - Nothing -> fail $ unwords ["Failed in looking up response type of", show v] - Nothing -> fail $ unwords ["Got unexpected message without method or id"] -parseClientMessage _ v = fail $ unwords ["parseClientMessage expected object, got:",show v] diff --git a/app/Process.hs b/app/Process.hs deleted file mode 100644 index 701d9ef..0000000 --- a/app/Process.hs +++ /dev/null @@ -1,38 +0,0 @@ -{-# LANGUAGE QuasiQuotes #-} - -module Process where - -import Control.Monad -import Control.Monad.IO.Class -import Control.Monad.Logger -import Control.Retry -import Data.Maybe -import Data.String.Interpolate -import System.Process - - -gracefullyStopProcess :: (MonadIO m, MonadLogger m) => ProcessHandle -> Int -> m () -gracefullyStopProcess p gracePeriodUs = do - liftIO $ interruptProcessGroupOf p - gracefullyWaitForProcess p gracePeriodUs - -gracefullyWaitForProcess :: (MonadIO m, MonadLogger m) => ProcessHandle -> Int -> m () -gracefullyWaitForProcess p gracePeriodUs = do - let waitForExit = do - let policy = limitRetriesByCumulativeDelay gracePeriodUs $ capDelay 200_000 $ exponentialBackoff 1_000 - retrying policy (\_ x -> return $ isNothing x) $ \_ -> do - liftIO $ getProcessExitCode p - - waitForExit >>= \case - Just _ -> return () - Nothing -> do - pid <- liftIO $ getPid p - logWarnN [i|(#{pid}) Process didn't stop after #{gracePeriodUs}us; trying to interrupt|] - - liftIO $ interruptProcessGroupOf p - waitForExit >>= \case - Just _ -> return () - Nothing -> void $ do - logWarnN [i|(#{pid}) Process didn't stop after a further #{gracePeriodUs}us; going to kill|] - liftIO $ terminateProcess p - liftIO $ waitForProcess p diff --git a/app/RequestMap.hs b/app/RequestMap.hs deleted file mode 100644 index 6a0ea6c..0000000 --- a/app/RequestMap.hs +++ /dev/null @@ -1,37 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE PolyKinds #-} - -module RequestMap where - -import Data.IxMap -import Data.Kind -import Language.LSP.Protocol.Message - - --- * Client - -data SMethodAndParams m = SMethodAndParams (SMethod m) (MessageParams m) - -type ClientRequestMap = IxMap LspId (SMethodAndParams :: Method 'ClientToServer 'Request -> Type) - -newClientRequestMap :: ClientRequestMap -newClientRequestMap = emptyIxMap - -updateClientRequestMap :: ClientRequestMap -> LspId m -> SMethodAndParams (m :: Method 'ClientToServer 'Request) -> Maybe ClientRequestMap -updateClientRequestMap reqMap id x = insertIxMap id x reqMap - -lookupClientRequestMap :: ClientRequestMap -> LspId m -> Maybe (SMethodAndParams (m :: Method 'ClientToServer 'Request)) -lookupClientRequestMap reqMap id = lookupIxMap id reqMap - --- * Server - -type ServerRequestMap = IxMap LspId (SMethodAndParams :: Method 'ServerToClient 'Request -> Type) - -newServerRequestMap :: ServerRequestMap -newServerRequestMap = emptyIxMap - -updateServerRequestMap :: ServerRequestMap -> LspId m -> (SMethodAndParams (m :: Method 'ServerToClient 'Request)) -> Maybe ServerRequestMap -updateServerRequestMap reqMap id x = insertIxMap id x reqMap - -lookupServerRequestMap :: ServerRequestMap -> LspId m -> Maybe (SMethodAndParams (m :: Method 'ServerToClient 'Request)) -lookupServerRequestMap reqMap id = lookupIxMap id reqMap diff --git a/cpp-notebook-language-server.cabal b/cpp-notebook-language-server.cabal index 2dc2c42..d1035b4 100644 --- a/cpp-notebook-language-server.cabal +++ b/cpp-notebook-language-server.cabal @@ -1,6 +1,6 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.38.1. +-- This file has been generated from package.yaml by hpack version 0.39.1. -- -- see: https://github.com/sol/hpack @@ -17,17 +17,7 @@ library Language.LSP.Notebook.CppParser Language.LSP.Notebook.DeclarationSifter Language.LSP.Test - Language.LSP.Transformer - Transform.ClientNot - Transform.ClientNot.DidSave - Transform.ClientReq - Transform.ClientRsp - Transform.Common - Transform.ServerNot - Transform.ServerReq - Transform.ServerRsp - Transform.ServerRsp.Hover - Transform.Util + Transform.Config other-modules: Paths_cpp_notebook_language_server hs-source-dirs: @@ -50,37 +40,21 @@ library ghc-options: -Wunused-packages -Wall -fno-warn-name-shadowing build-depends: aeson - , auto-update , base >=4.7 && <5 , bytestring , containers - , filepath - , lens - , lens-regex-pcre , lsp-types >=2.0.0.1 - , monad-logger - , mtl - , myers-diff - , network-uri + , notebook-language-server-core , pcre-light - , random - , safe , string-interpolate , text - , text-rope - , time , unliftio - , unliftio-core - , uuid , vector default-language: Haskell2010 executable cpp-notebook-language-server main-is: Main.hs other-modules: - Parsing - Process - RequestMap Streams Test Paths_cpp_notebook_language_server @@ -111,9 +85,8 @@ executable cpp-notebook-language-server , lsp-types >=2.0.0.1 , monad-logger , mtl + , notebook-language-server-core , optparse-applicative - , process - , retry , safe , string-interpolate , text @@ -175,6 +148,7 @@ test-suite cpp-notebook-language-server-test , monad-logger , mtl , myers-diff + , notebook-language-server-core , quickcheck-instances , sandwich , sandwich-contexts diff --git a/package.yaml b/package.yaml index beebfed..de4fdc4 100644 --- a/package.yaml +++ b/package.yaml @@ -8,7 +8,6 @@ extra-source-files: dependencies: - base >= 4.7 && < 5 - lsp-types >= 2.0.0.1 -- monad-logger - string-interpolate - text - unliftio @@ -38,22 +37,10 @@ library: source-dirs: src dependencies: - aeson - - auto-update - bytestring - containers - - filepath - - lens - - lens-regex-pcre - - mtl - - myers-diff - - network-uri + - notebook-language-server-core - pcre-light - - random - - safe - - text-rope - - time - - unliftio-core - - uuid - vector executables: @@ -70,12 +57,12 @@ executables: dependencies: - aeson - bytestring + - cpp-notebook-language-server - lens + - monad-logger - mtl + - notebook-language-server-core - optparse-applicative - - process - - retry - - cpp-notebook-language-server - safe - unix - unliftio-core @@ -92,7 +79,6 @@ tests: - -O2 - -W dependencies: - - QuickCheck - containers - cpp-notebook-language-server - directory @@ -105,6 +91,8 @@ tests: - monad-logger - mtl - myers-diff + - notebook-language-server-core + - QuickCheck - quickcheck-instances - sandwich - sandwich-contexts diff --git a/src/Language/LSP/Transformer.hs b/src/Language/LSP/Transformer.hs deleted file mode 100644 index 6aae734..0000000 --- a/src/Language/LSP/Transformer.hs +++ /dev/null @@ -1,126 +0,0 @@ -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE TypeOperators #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE OverloadedLabels #-} - -module Language.LSP.Transformer ( - Doc - - , Transformer(..) - , ProjectIn(..) - , (:>)(..) - , defaultHandleDiff - - , listToDoc - , docToList - - , applyChange - , applyChanges - ) where - -import Control.Lens ((^.)) -import Control.Monad (foldM) -import Control.Monad.IO.Class (MonadIO) -import Data.Diff.Myers -import qualified Data.Diff.Types as DT -import Data.Either -import Data.Kind -import qualified Data.List as L -import Data.Text (Text) -import qualified Data.Text as T -import Data.Text.Rope (Rope) -import qualified Data.Text.Rope as Rope -import Language.LSP.Protocol.Lens -import Language.LSP.Protocol.Types as J - - -type Doc = Rope.Rope - -class Transformer a where - type Params a - - unproject :: a -> Doc -> Maybe Doc - unproject _ _ = Nothing - - transformPosition :: a -> Position -> Maybe Position - - untransformPosition :: a -> Position -> Maybe Position - -class Transformer a => ProjectIn m a where - project :: Params a -> Doc -> m (Doc, a, Either Text ()) - - handleDiffMulti :: Monad m => Params a -> Doc -> [TextDocumentContentChangeEvent] -> a -> m ([TextDocumentContentChangeEvent], a) - handleDiffMulti params before changes tx = do - (finalChanges, _finalLines, finalTx) <- foldM f ([], before, tx) changes - return (finalChanges, finalTx) - where - f (changesSoFar, curLines, txSoFar) change = do - (newChanges, tx') <- handleDiff params curLines change txSoFar - return (changesSoFar <> newChanges, applyChanges [change] curLines, tx') - - handleDiff :: Monad m => Params a -> Doc -> TextDocumentContentChangeEvent -> a -> m ([TextDocumentContentChangeEvent], a) - handleDiff = defaultHandleDiff - -data (a :: Type) :> (b :: Type) = a :> b - deriving Show -infixr :> - -instance (Transformer a, Transformer b) => Transformer (a :> b) where - type Params (a :> b) = Params a :> Params b - unproject (x :> y) lines = unproject y lines >>= unproject x - transformPosition (x :> y) p = transformPosition x p >>= transformPosition y - untransformPosition (x :> y) p = untransformPosition y p >>= untransformPosition x - -instance (ProjectIn m a, ProjectIn m b, Monad m) => ProjectIn m (a :> b) where - project (xParams :> yParams) lines = do - (lines', x, eitherErr1) <- project xParams lines - (lines'', y, eitherErr2) <- project yParams lines' - let overallErr = case lefts [eitherErr1, eitherErr2] of - [] -> Right () - errs -> Left (T.intercalate "; " errs) - return (lines'', x :> y, overallErr) - handleDiff (xParams :> yParams) before change (x :> y) = do - (change', x') <- handleDiff xParams before change x - (beforeTransformed, _, _err) <- project @m @a xParams before - (change'', y') <- handleDiffMulti yParams beforeTransformed change' y - return (change'', x' :> y') - --- Default implementation uses diff. -defaultHandleDiff :: forall m a. (ProjectIn m a, Monad m) => Params a -> Doc -> TextDocumentContentChangeEvent -> a -> m ([TextDocumentContentChangeEvent], a) -defaultHandleDiff params before change _transformer = do - (before', _ :: a, _eitherErrBefore) <- project params before - let after = applyChanges [change] before - (after', transformer' :: a, _eitherErrAfter) <- project params after - let change' = fmap repackChangeEvent $ diffTextsToChangeEventsConsolidate (Rope.toText before') (Rope.toText after') - return (change', transformer') - where - repackChangeEvent (DT.ChangeEvent range text) = TextDocumentContentChangeEvent $ InL $ TextDocumentContentChangePartial (repackRange range) Nothing text - repackRange (DT.Range (DT.Position l1 c1) (DT.Position l2 c2)) = Range (Position (fromIntegral l1) (fromIntegral c1)) (Position (fromIntegral l2) (fromIntegral c2)) - --- * Applying changes - -listToDoc :: [Text] -> Doc -listToDoc = Rope.fromText . T.intercalate "\n" - -docToList :: Doc -> [Text] -docToList = T.splitOn "\n" . Rope.toText - --- * Based on code from haskell-lsp/lsp (https://github.com/haskell/lsp/tree/master/lsp) --- Under MIT license - -applyChanges :: [J.TextDocumentContentChangeEvent] -> Rope -> Rope -applyChanges changes rope = L.foldl' (flip applyChange) rope changes - -applyChange :: J.TextDocumentContentChangeEvent -> Rope -> Rope -applyChange (J.TextDocumentContentChangeEvent (InL partial)) str = - changeChars str (Rope.Position (fromIntegral sl) (fromIntegral sc)) (Rope.Position (fromIntegral fl) (fromIntegral fc)) (partial ^. text) - where - J.Range (J.Position sl sc) (J.Position fl fc) = partial ^. range -applyChange (J.TextDocumentContentChangeEvent (InR whole)) _ = Rope.fromText (whole ^. text) - -changeChars :: Rope -> Rope.Position -> Rope.Position -> Text -> Rope -changeChars str start finish new = mconcat [before', Rope.fromText new, after] - where - (before, after) = Rope.splitAtPosition finish str - (before', _) = Rope.splitAtPosition start before diff --git a/src/Transform/ClientNot.hs b/src/Transform/ClientNot.hs deleted file mode 100644 index 3475ae9..0000000 --- a/src/Transform/ClientNot.hs +++ /dev/null @@ -1,140 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE PolyKinds #-} -{-# LANGUAGE RankNTypes #-} - -module Transform.ClientNot where - -import Control.Debounce -import Control.Lens hiding ((:>), (<.>), List) -import Control.Monad -import Control.Monad.IO.Class -import Control.Monad.IO.Unlift -import Control.Monad.Logger -import Control.Monad.Reader -import Data.Aeson as A -import Data.Map as M -import Data.String.Interpolate -import Data.Text as T -import qualified Data.Text.Rope as Rope -import Data.Time -import qualified Data.UUID.V4 as UUID -import Language.LSP.Notebook -import Language.LSP.Protocol.Lens as Lens -import Language.LSP.Protocol.Message -import Language.LSP.Protocol.Types -import Language.LSP.Transformer -import Transform.ClientNot.DidSave -import Transform.ServerRsp.Hover (mkDocRegex) -import Transform.Util -import UnliftIO.Concurrent -import UnliftIO.Directory -import UnliftIO.Exception - - -type ClientNotMethod m = SMethod (m :: Method 'ClientToServer 'Notification) - -transformClientNot :: ( - TransformerMonad n, HasJSON (TNotificationMessage m) - ) => SendExtraNotificationFn n - -> ClientNotMethod m - -> TNotificationMessage m - -> n (TNotificationMessage m) -transformClientNot sendExtraNotification meth msg = do - start <- liftIO getCurrentTime - p' <- transformClientNot' sendExtraNotification meth (msg ^. params) - stop <- liftIO getCurrentTime - let msg' = set params p' msg - when (msg' /= msg) $ logDebugN [i|Transforming client not #{meth} in #{diffUTCTime stop start}: (#{A.encode msg} --> #{A.encode msg'})|] - return msg' - -transformClientNot' :: ( - TransformerMonad n - ) => (forall (o :: Method 'ClientToServer 'Notification). ToJSON (TNotificationMessage o) => TNotificationMessage o -> n ()) - -> ClientNotMethod m - -> MessageParams m - -> n (MessageParams m) - -transformClientNot' sendExtraNotification SMethod_TextDocumentDidOpen params = whenAnything params $ \u -> do - let t = params ^. (textDocument . text) - let ls = Rope.fromText t - let txParams = if isNotebook u then transformerParams else idTransformerParams - (ls', transformer' :: CppNotebookTransformer, eitherErr) <- liftIO $ project txParams ls - - logDebugN [i|(#{u}) Transforming TextDocumentDidOpen. Is notebook? #{isNotebook u}|] - - case eitherErr of - Left err -> logWarnN [i|(#{u}) Project failed: #{err}|] - Right () -> return () - - TransformerState {..} <- ask - newUri <- if isNotebook u then addExtensionToUri ".cpp" u else return u - - newPath <- case uriToFilePath newUri of - Nothing -> throwIO $ userError [i|Failed to convert new URI to file path: #{newUri}|] - Just x -> pure x - - uuid <- liftIO UUID.nextRandom - - debouncedDidChange <- withRunInIO $ \runInIO -> mkDebounce ( - defaultDebounceSettings { - debounceAction = do - withMVar transformerDocuments $ \m -> - case M.lookup (getUri u) m of - (Just ds@(DocumentState {..})) - | documentUuid == uuid -> runInIO $ doDidSave ds sendExtraNotification - | otherwise -> return () - _ -> return () - , debounceFreq = transformerDidSaveDebouncePeriodMs * 1000 - , debounceEdge = trailingEdge - }) - - modifyMVar_ transformerDocuments $ \x -> return $! M.insert (getUri u) ( - DocumentState { - transformer = transformer' - , curLines = ls - , curLines' = ls' - , origUri = u - , newUri = newUri - , newPath = newPath - , referenceRegex = case uriToFilePath newUri of - Just s -> mkDocRegex (T.pack s) - Nothing -> mkDocRegex (getUri newUri) - , documentUuid = uuid - , debouncedDidChange = debouncedDidChange - }) x - - return $ params - & set (textDocument . text) (Rope.toText ls') - & set (textDocument . uri) newUri - -transformClientNot' _ SMethod_TextDocumentDidChange params = whenAnything params $ modifyTransformer params $ \ds@(DocumentState {transformer=tx, curLines=before, curLines'=before', origUri, newUri, debouncedDidChange}) -> do - let txParams = if isNotebook origUri then transformerParams else idTransformerParams - let changeEvents = params ^. contentChanges - (changeEvents', tx') <- liftIO $ handleDiffMulti txParams before changeEvents tx - let after = applyChanges changeEvents before - let after' = applyChanges changeEvents' before' - - liftIO debouncedDidChange - - return ( - ds { transformer = tx' - , curLines = after - , curLines' = after' - } - , params & set contentChanges changeEvents' - & set (textDocument . uri) newUri - ) - -transformClientNot' _ SMethod_TextDocumentDidClose params = whenAnything params $ \u -> do - TransformerState {..} <- ask - maybeDocumentState <- modifyMVar transformerDocuments (return . flipTuple . M.updateLookupWithKey (\_ _ -> Nothing) (getUri u)) - newUri <- case maybeDocumentState of - Just (DocumentState {..}) -> do - removePathForcibly newPath - pure newUri - Nothing -> addExtensionToUri ".rs" u -- The client shouldn't be closing a non-open doc - return $ params - & set (textDocument . uri) newUri - -transformClientNot' _ _ params = return params diff --git a/src/Transform/ClientNot/DidSave.hs b/src/Transform/ClientNot/DidSave.hs deleted file mode 100644 index f24a0ba..0000000 --- a/src/Transform/ClientNot/DidSave.hs +++ /dev/null @@ -1,30 +0,0 @@ -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE MonoLocalBinds #-} - -module Transform.ClientNot.DidSave where - -import Control.Monad.IO.Class -import qualified Data.Text.IO as T -import qualified Data.Text.Rope as Rope -import Language.LSP.Protocol.Message -import Language.LSP.Protocol.Types -import Transform.Util - - -doDidSave :: TransformerMonad m => DocumentState -> SendExtraNotificationFn m -> m () -doDidSave (DocumentState {..}) sendExtraNotification = do - whenServerCapabilitiesSatisfy supportsWillSave $ \_ -> - sendExtraNotification $ TNotificationMessage "2.0" SMethod_TextDocumentWillSave $ WillSaveTextDocumentParams { - _textDocument = TextDocumentIdentifier newUri - , _reason = TextDocumentSaveReason_AfterDelay - } - - liftIO $ T.writeFile newPath (Rope.toText curLines') - - whenServerCapabilitiesSatisfy supportsSave $ \maybeSaveOptions -> - sendExtraNotification $ TNotificationMessage "2.0" SMethod_TextDocumentDidSave $ DidSaveTextDocumentParams { - _textDocument = TextDocumentIdentifier newUri - , _text = case maybeSaveOptions of - Just (SaveOptions {_includeText=(Just True)}) -> Just (Rope.toText curLines') - _ -> Nothing - } diff --git a/src/Transform/ClientReq.hs b/src/Transform/ClientReq.hs deleted file mode 100644 index a388d5f..0000000 --- a/src/Transform/ClientReq.hs +++ /dev/null @@ -1,96 +0,0 @@ -{-# LANGUAGE PolyKinds #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE AllowAmbiguousTypes #-} - -module Transform.ClientReq where - -import Control.Lens hiding ((:>)) -import Control.Monad -import Control.Monad.IO.Class -import Control.Monad.Logger -import Control.Monad.Reader -import Data.Aeson as A -import Data.Proxy -import Data.String.Interpolate -import Data.Time -import GHC.TypeLits (sameSymbol) -import Language.LSP.Protocol.Lens as Lens -import Language.LSP.Protocol.Message -import Language.LSP.Protocol.Types -import Language.LSP.Transformer -import Transform.Common -import Transform.Util -import UnliftIO.Concurrent - - -type ClientReqMethod m = SMethod (m :: Method 'ClientToServer 'Request) - - -transformClientReq :: (TransformerMonad n, HasJSON (TRequestMessage m)) => ClientReqMethod m -> TRequestMessage m -> n (TRequestMessage m) -transformClientReq meth msg = do - start <- liftIO getCurrentTime - p' <- transformClientReq' meth (msg ^. params) - stop <- liftIO getCurrentTime - let msg' = set params p' msg - when (msg' /= msg) $ logDebugN [i|Transforming client req #{meth} in #{diffUTCTime stop start}: (#{A.encode msg} --> #{A.encode msg'})|] - return msg' - -transformClientReq' :: forall m n. (TransformerMonad n) => ClientReqMethod m -> MessageParams m -> n (MessageParams m) - -transformClientReq' SMethod_Initialize params = do - -- Store the non-modified params, so we can access the unmodified rootUri - asks transformerInitializeParams >>= flip modifyMVar_ (\_ -> return $ Just params) - pure params - -transformClientReq' SMethod_TextDocumentCodeAction params = whenAnything params $ withTransformer params $ doTransformUriAndRange @m params -transformClientReq' SMethod_TextDocumentCodeLens params = whenAnything params $ withTransformer params $ doTransformUri @m params -transformClientReq' SMethod_TextDocumentCompletion params = whenAnything params $ withTransformer params $ doTransformUriAndPosition @m params -transformClientReq' SMethod_TextDocumentDefinition params = whenAnything params $ withTransformer params $ doTransformUriAndPosition @m params -transformClientReq' SMethod_TextDocumentDocumentHighlight params = whenAnything params $ withTransformer params $ doTransformUriAndPosition @m params -transformClientReq' SMethod_TextDocumentDocumentSymbol params = whenAnything params $ withTransformer params $ doTransformUri @m params -transformClientReq' SMethod_TextDocumentHover params = whenAnything params $ withTransformer params $ doTransformUriAndPosition @m params -transformClientReq' SMethod_TextDocumentImplementation params = whenAnything params $ withTransformer params $ doTransformUriAndPosition @m params -transformClientReq' SMethod_TextDocumentInlayHint params = whenAnything params $ withTransformer params $ doTransformUriAndRange @m params -transformClientReq' SMethod_TextDocumentOnTypeFormatting params = whenAnything params $ withTransformer params $ doTransformUriAndPosition @m params -transformClientReq' SMethod_TextDocumentTypeDefinition params = whenAnything params $ withTransformer params $ doTransformUriAndPosition @m params - --- Custom methods provided by clangd -transformClientReq' (SMethod_CustomMethod p) val = case sameSymbol p (Proxy @"clangd/analyzerStatus") of - Just Refl -> case A.fromJSON val of - A.Error err -> logErrorN [i|Failed to decode custom method "clangd/analyzerStatus": #{err}|] >> return val - A.Success (TextDocumentIdentifier uri) -> - whenAnything' uri val $ - withTransformer val $ \(DocumentState {newUri}) -> - return $ A.toJSON $ TextDocumentIdentifier newUri - Nothing -> return val - -transformClientReq' _ params = return params - - -doTransformUriAndPosition :: forall m n a. ( - TransformerMonad n, HasPosition (MessageParams m) Position, HasTextDocument (MessageParams m) a, HasUri a Uri - ) => MessageParams m -> DocumentState -> n (MessageParams m) -doTransformUriAndPosition params' (DocumentState {transformer=tx, newUri}) = do - let params = params' & set (textDocument . uri) newUri - case transformPosition tx (params ^. position) of - Nothing -> do - logWarnN [i|Couldn't transform position #{params ^. position}|] - return params - Just pos' -> return $ set position pos' params - -doTransformUriAndRange :: forall m n a. ( - TransformerMonad n, HasRange (MessageParams m) Range, HasTextDocument (MessageParams m) a, HasUri a Uri - ) => MessageParams m -> DocumentState -> n (MessageParams m) -doTransformUriAndRange params' (DocumentState {transformer=tx, newUri}) = do - let params = params' & set (textDocument . uri) newUri - case transformRange tx (params ^. range) of - Nothing -> do - logWarnN [i|Couldn't transform range #{params ^. range}|] - return params - Just pos' -> return $ set range pos' params - -doTransformUri :: forall m n a. ( - TransformerMonad n, HasTextDocument (MessageParams m) a, HasUri a Uri - ) => MessageParams m -> DocumentState -> n (MessageParams m) -doTransformUri params' (DocumentState {newUri}) = return $ params' & set (textDocument . uri) newUri diff --git a/src/Transform/ClientRsp.hs b/src/Transform/ClientRsp.hs deleted file mode 100644 index bec366f..0000000 --- a/src/Transform/ClientRsp.hs +++ /dev/null @@ -1,29 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE PolyKinds #-} -{-# LANGUAGE TypeFamilies #-} - -module Transform.ClientRsp where - -import Control.Monad -import Control.Monad.IO.Class -import Control.Monad.Logger -import Data.Aeson as A -import Data.String.Interpolate -import Data.Time -import Language.LSP.Protocol.Message -import Transform.Util - - -type ClientRspMethod m = SMethod (m :: Method 'ServerToClient 'Request) - -transformClientRsp :: (TransformerMonad n, HasJSON (TResponseMessage m)) => ClientRspMethod m -> TResponseMessage m -> n (TResponseMessage m) -transformClientRsp meth msg = do - start <- liftIO getCurrentTime - msg' <- transformClientRsp' meth msg - stop <- liftIO getCurrentTime - when (msg' /= msg) $ logDebugN [i|Transforming client resp #{meth} in #{diffUTCTime stop start}: (#{A.encode msg} --> #{A.encode msg'})|] - return msg' - - -transformClientRsp' :: (TransformerMonad n) => ClientRspMethod m -> TResponseMessage m -> n (TResponseMessage m) -transformClientRsp' _meth msg = pure msg diff --git a/src/Transform/Common.hs b/src/Transform/Common.hs deleted file mode 100644 index 758207d..0000000 --- a/src/Transform/Common.hs +++ /dev/null @@ -1,51 +0,0 @@ -{-# OPTIONS_GHC -fno-warn-orphans #-} -{-# LANGUAGE MultiParamTypeClasses #-} - -module Transform.Common where - -import Control.Lens hiding (List) -import Data.Maybe -import Language.LSP.Notebook -import Language.LSP.Protocol.Lens as Lens -import Language.LSP.Protocol.Types -import Language.LSP.Transformer - - --- * Transform - -transformRange :: CppNotebookTransformer -> Range -> Maybe Range -transformRange = transformRanged - -transformRanged :: (HasRange a Range) => CppNotebookTransformer -> a -> Maybe a -transformRanged tx x = x - & traverseOf (range . start) (transformPosition tx) - >>= traverseOf (range . end) (transformPosition tx) - --- * Untransform - -untransformRange :: CppNotebookTransformer -> Range -> Maybe Range -untransformRange = untransformRanged - -untransformRanged :: (HasRange a Range) => CppNotebookTransformer -> a -> Maybe a -untransformRanged tx x = x - & traverseOf (range . start) (untransformPosition tx) - >>= traverseOf (range . end) (untransformPosition tx) - -untransformRangedMaybe :: (HasRange a (Maybe Range)) => CppNotebookTransformer -> a -> Maybe a -untransformRangedMaybe tx x = x - & traverseOf (range . _Just . start) (untransformPosition tx) - >>= traverseOf (range . _Just . end) (untransformPosition tx) - --- | An inlay hint sits at a position rather than over a range, and carries its own edits for --- when the user accepts it. Drop the hint if its position isn't in the notebook; drop --- individual edits that aren't. -untransformInlayHint :: CppNotebookTransformer -> InlayHint -> Maybe InlayHint -untransformInlayHint tx hint = - hint - & traverseOf position (untransformPosition tx) - & fmap (over (textEdits . _Just) (mapMaybe (untransformRanged tx))) - --- * Orphan (wish this was in lsp-types) - -instance HasRange Range Range where - range = Prelude.id diff --git a/src/Transform/Config.hs b/src/Transform/Config.hs new file mode 100644 index 0000000..1dbe7ef --- /dev/null +++ b/src/Transform/Config.hs @@ -0,0 +1,39 @@ +{-# LANGUAGE RankNTypes #-} + +module Transform.Config (cppNotebookConfig) where + +import Data.String.Interpolate +import qualified Data.Text as T +import Language.LSP.Notebook +import Language.LSP.NotebookLanguageServer.Config +import Language.LSP.NotebookLanguageServer.Transform.ServerRsp.Hover (mkDocRegex) +import Language.LSP.NotebookLanguageServer.Transform.Util +import Language.LSP.Protocol.Types +import Language.LSP.Transformer (Doc) +import Text.Regex.PCRE.Light (Regex) +import UnliftIO.Exception + + +cppNotebookConfig :: Int -> NotebookConfig CppNotebookTransformer +cppNotebookConfig didSaveDebounceMs = NotebookConfig { + notebookConfigHandles = const True + , notebookConfigDidSaveDebounceMs = Just didSaveDebounceMs + , notebookConfigTransformerParamsFor = \u -> if isNotebook u then transformerParams else idTransformerParams + , notebookConfigMakeShadowDocument = makeShadowDocument + , notebookConfigUnopenedUri = addExtensionToUri ".cpp" + , notebookConfigOnDocumentsChanged = pure () + , notebookConfigTransformInitialize = pure + , notebookConfigCustomUriMethods = ["clangd/analyzerStatus"] + , notebookConfigIgnoreSymbol = const False + , notebookConfigLanguageName = "cpp" + } + +makeShadowDocument :: TransformerMonad CppNotebookTransformer n => Uri -> Doc -> n (Uri, FilePath, Regex) +makeShadowDocument origUri _projected = do + newUri <- if isNotebook origUri then addExtensionToUri ".cpp" origUri else pure origUri + + newPath <- case uriToFilePath newUri of + Nothing -> throwIO $ userError [i|Failed to convert new URI to file path: #{newUri}|] + Just x -> pure x + + pure (newUri, newPath, mkDocRegex (T.pack newPath)) diff --git a/src/Transform/ServerNot.hs b/src/Transform/ServerNot.hs deleted file mode 100644 index f88fa27..0000000 --- a/src/Transform/ServerNot.hs +++ /dev/null @@ -1,37 +0,0 @@ -{-# LANGUAGE PolyKinds #-} -{-# LANGUAGE TypeFamilies #-} - -module Transform.ServerNot where - -import Control.Lens hiding (List) -import Control.Monad -import Control.Monad.IO.Class -import Control.Monad.Logger -import Data.Aeson as A -import Data.Maybe -import Data.String.Interpolate -import Data.Time -import Language.LSP.Protocol.Lens -import Language.LSP.Protocol.Message -import Transform.Common -import Transform.Util - - -transformServerNot :: (TransformerMonad n, HasJSON (TNotificationMessage m)) => SMethod m -> TNotificationMessage m -> n (TNotificationMessage m) -transformServerNot meth msg = do - start <- liftIO getCurrentTime - p' <- transformServerNot' meth (msg ^. params) - stop <- liftIO getCurrentTime - let msg' = set params p' msg - when (msg' /= msg) $ logDebugN [i|Transforming server not #{meth} in #{diffUTCTime stop start}: (#{A.encode msg} --> #{A.encode msg'})|] - return msg' - - -transformServerNot' :: (TransformerMonad n) => SMethod m -> MessageParams m -> n (MessageParams m) - -transformServerNot' SMethod_TextDocumentPublishDiagnostics params = whenReverseLookupUri params $ \(DocumentState {transformer=tx, ..}) -> do - return $ params - & set uri origUri - & over diagnostics (mapMaybe (traverseOf range (untransformRange tx))) - -transformServerNot' _meth msg = pure msg diff --git a/src/Transform/ServerReq.hs b/src/Transform/ServerReq.hs deleted file mode 100644 index 6be86c7..0000000 --- a/src/Transform/ServerReq.hs +++ /dev/null @@ -1,9 +0,0 @@ -{-# LANGUAGE PolyKinds #-} - -module Transform.ServerReq where - -import Language.LSP.Protocol.Message - - -transformServerReq :: SMethod m -> TRequestMessage m -> TRequestMessage m -transformServerReq _meth msg = msg diff --git a/src/Transform/ServerRsp.hs b/src/Transform/ServerRsp.hs deleted file mode 100644 index 3bc098e..0000000 --- a/src/Transform/ServerRsp.hs +++ /dev/null @@ -1,119 +0,0 @@ -{-# LANGUAGE PolyKinds #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE GADTs #-} - -module Transform.ServerRsp where - -import Control.Lens hiding (List) -import Control.Monad -import Control.Monad.IO.Class -import Control.Monad.Logger -import Control.Monad.Reader -import Data.Aeson as A -import Data.Maybe -import Data.String.Interpolate -import Data.Time -import Language.LSP.Protocol.Lens as Lens -import Language.LSP.Protocol.Message -import Language.LSP.Protocol.Types as LSP -import Transform.Common -import Transform.ServerRsp.Hover -import Transform.Util -import UnliftIO.MVar - - -type ServerRspMethod m = SMethod (m :: Method 'ClientToServer 'Request) - -transformServerRsp :: ( - TransformerMonad n, HasJSON (TResponseMessage m) - ) => ServerRspMethod m -> MessageParams m -> TResponseMessage m -> n (TResponseMessage m) -transformServerRsp meth initialParams msg = case msg ^. result of - Left _err -> return msg - Right ret -> do - start <- liftIO getCurrentTime - p' <- transformServerRsp' meth initialParams ret - stop <- liftIO getCurrentTime - let msg' = set result (Right p') msg - when (msg' /= msg) $ logDebugN [i|Transforming server rsp #{meth} in #{diffUTCTime stop start}: (#{A.encode msg} --> #{A.encode msg'})|] - return $ set result (Right p') msg - -type ResponseResult m = Either ResponseError (MessageResult m) - -transformServerRsp' :: (TransformerMonad n) => ServerRspMethod m -> MessageParams m -> MessageResult m -> n (MessageResult m) - -transformServerRsp' SMethod_Initialize _initialParams result = do - initializeResultVar <- asks transformerInitializeResult - modifyMVar_ initializeResultVar (\_ -> return $ Just result) - return result - -transformServerRsp' SMethod_TextDocumentCompletion initialParams result = - whenAnythingByInitialParams initialParams result $ withTransformer result $ \(DocumentState {transformer=tx}) -> do - let fixupCompletionEdit Nothing = Nothing - fixupCompletionEdit (Just (InL edit)) = InL <$> (untransformRanged tx edit) - fixupCompletionEdit (Just (InR (InsertReplaceEdit newText insert replace))) = - InR <$> (InsertReplaceEdit newText <$> untransformRanged tx insert <*> untransformRanged tx replace) - - let fixupItem :: CompletionItem -> CompletionItem - fixupItem item = item - & over textEdit fixupCompletionEdit - & over (additionalTextEdits . _Just) (mapMaybe (untransformRanged tx)) - - case result of - (InL items) -> return (InL (fmap fixupItem items)) - (InR (InL completionList)) -> return (InR (InL (completionList & over items (fmap fixupItem)))) - (InR (InR null)) -> return $ InR $ InR null - -transformServerRsp' SMethod_TextDocumentDocumentHighlight initialParams result = - whenAnythingByInitialParams initialParams result $ withTransformer result $ \(DocumentState {transformer=tx}) -> - case result of - InL highlights -> return $ InL $ mapMaybe (untransformRanged tx) highlights - InR null -> return $ InR null - -transformServerRsp' SMethod_TextDocumentInlayHint initialParams result = - whenAnythingByInitialParams initialParams result $ withTransformer result $ \(DocumentState {transformer=tx}) -> - case result of - InL hints -> return $ InL $ mapMaybe (untransformInlayHint tx) hints - InR null -> return $ InR null - -transformServerRsp' SMethod_TextDocumentOnTypeFormatting initialParams result = - whenAnythingByInitialParams initialParams result $ withTransformer result $ \(DocumentState {transformer=tx}) -> - case result of - InL edits -> return $ InL $ mapMaybe (untransformRanged tx) edits - InR null -> return $ InR null - -transformServerRsp' SMethod_TextDocumentHover initialParams result = - whenAnythingByInitialParams initialParams result $ withTransformer result $ \(DocumentState {transformer=tx}) -> - case result of - InR null -> return $ InR null - InL hov -> do - hov' <- fixupHoverText hov - return $ case untransformRangedMaybe tx hov' of - Just ret -> InL ret - Nothing -> InR LSP.Null - -transformServerRsp' SMethod_TextDocumentDocumentSymbol initialParams result = - whenAnythingByInitialParams initialParams result $ withTransformer result $ \(DocumentState {transformer=tx}) -> - case result of - InL symbolInformations -> return $ InL (symbolInformations & filter (not . ignoreSymbol) - & mapMaybe (traverseOf (location . range) (untransformRange tx))) - InR (InL documentSymbols) -> return $ InR $ InL $ (documentSymbols & filter (not . ignoreSymbol) - & mapMaybe (traverseOf range (untransformRange tx)) - & mapMaybe (traverseOf selectionRange (untransformRange tx))) - InR (InR LSP.Null) -> return $ InR $ InR LSP.Null - where - ignoreSymbol _ = False - -transformServerRsp' SMethod_TextDocumentCodeAction _initialParams (InR null) = return $ InR null -transformServerRsp' SMethod_TextDocumentCodeAction initialParams (InL result) = (InL <$>) $ - whenAnythingByInitialParams initialParams result $ withTransformer result $ \(DocumentState {}) -> do - filterM (fmap not . isInternalReferringCodeAction) result - where - isInternalReferringCodeAction (InL _command) = pure False - isInternalReferringCodeAction (InR _codeAction) = pure False - -transformServerRsp' SMethod_TextDocumentCodeLens _initialParams (InR null) = return $ InR null -transformServerRsp' SMethod_TextDocumentCodeLens initialParams (InL result) = (InL <$>) $ - whenAnythingByInitialParams initialParams result $ withTransformer result $ \(DocumentState {transformer=tx}) -> do - return $ mapMaybe (untransformRanged tx) result - -transformServerRsp' _ _ result = return result diff --git a/src/Transform/ServerRsp/Hover.hs b/src/Transform/ServerRsp/Hover.hs deleted file mode 100644 index 9b69de4..0000000 --- a/src/Transform/ServerRsp/Hover.hs +++ /dev/null @@ -1,82 +0,0 @@ -{-# LANGUAGE PolyKinds #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE TypeOperators #-} -{-# LANGUAGE OverloadedLabels #-} -{-# OPTIONS_GHC -fno-warn-deprecations #-} - -module Transform.ServerRsp.Hover ( - fixupHoverText - - , mkDocRegex - - -- For testing - , fixupDocumentReferences' - ) where - -import Control.Lens hiding (List) -import Control.Lens.Regex.Text -import Control.Monad.Logger -import Control.Monad.Reader -import Data.Function -import qualified Data.List as L -import qualified Data.Map as M -import Data.String.Interpolate -import Data.Text (Text) -import qualified Data.Text as T -import Data.Text.Rope (Rope) -import Language.LSP.Notebook -import Language.LSP.Protocol.Lens hiding (id, trace) -import Language.LSP.Protocol.Types -import Language.LSP.Transformer -import Safe -import Text.Regex.PCRE.Light -import Transform.Util -import UnliftIO.MVar - - -fixupHoverText :: (TransformerMonad n) => Hover -> n Hover -fixupHoverText initialHover = do - documentsMap <- ask >>= (readMVar . transformerDocuments) - flip fix (initialHover, M.toList documentsMap) $ \loop args -> case args of - (paramsInProgress, []) -> return paramsInProgress - (paramsInProgress, (_, DocumentState {..}):xs) -> do - newParams <- traverseOf contents (fixupDocumentReferences referenceRegex transformer curLines) paramsInProgress - loop (newParams, xs) - -type HoverContents = MarkupContent |? (MarkedString |? [MarkedString]) - -fixupDocumentReferences :: forall n. MonadLogger n => Regex -> CppNotebookTransformer -> Rope -> HoverContents -> n HoverContents -fixupDocumentReferences docRegex transformer _curLines (InL (MarkupContent k t)) = (InL . MarkupContent k) <$> (fixupDocumentReferences' docRegex transformer t) -fixupDocumentReferences docRegex transformer _curLines (InR markedStrings) = (InR <$>) $ case markedStrings of - InL ms -> InL <$> transformMarkedString ms - InR mss -> InR <$> (mapM transformMarkedString mss) - where - transformMarkedString :: MarkedString -> n MarkedString - transformMarkedString (MarkedString (InL t)) = (MarkedString . InL) <$> (fixupDocumentReferences' docRegex transformer t) - transformMarkedString (MarkedString (InR thing)) = do - t' <- fixupDocumentReferences' docRegex transformer (thing ^. value) - return $ MarkedString $ InR (thing & set value t') - -fixupDocumentReferences' :: forall n. MonadLogger n => Regex -> CppNotebookTransformer -> Text -> n Text -fixupDocumentReferences' docRegex transformer t = - traverseOf ((regexing docRegex) . groups) (transformGroup transformer) t - - where - transformGroup :: CppNotebookTransformer -> [Text] -> n [Text] - transformGroup transformer orig@[(readMay . T.unpack) -> Just line, (readMay . T.unpack) -> Just ch] = do - case untransformPosition transformer (Position (line - 1) (ch - 1)) of - Nothing -> return orig - Just (Position line' ch') -> return [T.pack $ show (line' + 1), T.pack $ show (ch' + 1)] - - transformGroup _ matches = return matches - -mkDocRegex :: Text -> Regex -mkDocRegex docName = compile [i|#{escapedName}:(\\d+):(\\d+)|] [utf8] - where - escapedName = docName - & (\x -> if filePrefix `T.isPrefixOf` x then T.drop (T.length filePrefix) x else x) - & L.foldl' (.) id [T.replace c ("\\" <> c) | c <- pcreChars] - - filePrefix = "file://" - - pcreChars = [".", "^", "$", "*", "+", "?", "(", ")", "[", "{", "\\", "|"] diff --git a/src/Transform/Util.hs b/src/Transform/Util.hs deleted file mode 100644 index b811208..0000000 --- a/src/Transform/Util.hs +++ /dev/null @@ -1,191 +0,0 @@ -{-# LANGUAGE ConstraintKinds #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE PolyKinds #-} -{-# LANGUAGE RankNTypes #-} - -module Transform.Util where - -import Control.Lens hiding ((:>), (<.>)) -import Control.Lens.Regex.Text -import Control.Monad -import Control.Monad.IO.Class -import Control.Monad.IO.Unlift -import Control.Monad.Logger -import Control.Monad.Reader -import Data.Aeson as A -import qualified Data.Char as C -import Data.Function -import qualified Data.List as L -import qualified Data.Map as M -import Data.String.Interpolate -import Data.Text (Text) -import qualified Data.Text as T -import Data.UUID -import Language.LSP.Notebook -import Language.LSP.Protocol.Lens as Lens -import Language.LSP.Protocol.Message -import Language.LSP.Protocol.Types as LSP -import Language.LSP.Transformer -import Network.URI -import System.FilePath -import qualified System.Random as R -import UnliftIO.MVar - - -type SendExtraNotificationFn n = forall (o :: Method 'ClientToServer 'Notification). ToJSON (TNotificationMessage o) => TNotificationMessage o -> n () - --- * whenAnything - -whenAnything :: (MonadLoggerIO n, HasTextDocument a b, HasUri b Uri) => a -> (Uri -> n a) -> n a -whenAnything params = whenAnything' (params ^. (textDocument . uri)) params - -whenAnythingByInitialParams :: (MonadLoggerIO n, HasTextDocument a b, HasUri b Uri) => a -> c -> (Uri -> n c) -> n c -whenAnythingByInitialParams params = whenAnything' (params ^. (textDocument . uri)) - -whenAnything' :: (MonadLoggerIO n) => Uri -> a -> (Uri -> n a) -> n a -whenAnything' uri _params action = action uri - --- * whenNotebook - -whenNotebook :: (MonadLoggerIO n, HasTextDocument a b, HasUri b Uri) => a -> (Uri -> n a) -> n a -whenNotebook params = whenNotebook' (params ^. (textDocument . uri)) params - -whenNotebookUri :: (MonadLoggerIO n, HasUri a Uri) => a -> (Uri -> n a) -> n a -whenNotebookUri params = whenNotebook' (params ^. uri) params - -whenNotebookByInitialParams :: (MonadLoggerIO n, HasTextDocument a b, HasUri b Uri) => a -> c -> (Uri -> n c) -> n c -whenNotebookByInitialParams params = whenNotebook' (params ^. (textDocument . uri)) - -whenNotebook' :: (MonadLoggerIO n) => Uri -> a -> (Uri -> n a) -> n a -whenNotebook' uri params notebookParams = if - | isNotebook uri -> notebookParams uri - | otherwise -> pure params - -isNotebook :: Uri -> Bool -isNotebook uri = case parseURIReference (T.unpack (getUri uri)) of - Nothing -> False - Just (URI {..}) -> ".ipynb" `L.isSuffixOf` fmap C.toLower uriPath - --- * whenNotebookResult - -whenReverseLookupUri :: (TransformerMonad n, MonadLoggerIO n, HasUri a Uri) => a -> (DocumentState -> n a) -> n a -whenReverseLookupUri params = whenReverseLookupUri' (params ^. uri) params - --- | Note that this takes in server URIs and calls the callback with original URIs --- TODO: make this more efficient -whenReverseLookupUri' :: (TransformerMonad n) => Uri -> a -> (DocumentState -> n a) -> n a -whenReverseLookupUri' uri params cb = do - txDocumentsVar <- asks transformerDocuments - txDocuments <- readMVar txDocumentsVar - flip fix (M.toList txDocuments) $ \loop items -> case items of - [] -> return params - ((_, ds@(DocumentState {..})):rest) -> - if | newUri == uri -> cb ds - | otherwise -> loop rest - --- * TransformerMonad - -type TransformerMonad n = ( - MonadLoggerIO n - , MonadReader TransformerState n - , MonadUnliftIO n - , MonadFail n - ) - --- * TransformerState - -data DocumentState = DocumentState { - transformer :: CppNotebookTransformer - , curLines :: Doc - , curLines' :: Doc - , origUri :: Uri - , newUri :: Uri - , newPath :: FilePath - , referenceRegex :: Regex - , documentUuid :: UUID - , debouncedDidChange :: IO () - } - -data TransformerState = TransformerState { - transformerDocuments :: MVar (M.Map Text DocumentState) - , transformerInitializeParams :: MVar (Maybe InitializeParams) - , transformerInitializeResult :: MVar (Maybe InitializeResult) - , transformerDidSaveDebouncePeriodMs :: Int - } - --- * Transformers - -newTransformerState :: (MonadIO m) => Int -> m TransformerState -newTransformerState didSaveDebouncePeriodMs = TransformerState - <$> newMVar mempty - <*> newMVar Nothing - <*> newMVar Nothing - <*> pure didSaveDebouncePeriodMs - -lookupTransformer :: TransformerMonad m => Uri -> m (Maybe DocumentState) -lookupTransformer uri = do - TransformerState {..} <- ask - M.lookup (getUri uri) <$> readMVar transformerDocuments - -withTransformer :: (TransformerMonad n) => a -> (DocumentState -> n a) -> Uri -> n a -withTransformer def cb uri = do - lookupTransformer uri >>= \case - Nothing -> do - logWarnN [i|Couldn't find expected transformer for uri #{uri}|] - return def - Just tx -> cb tx - -modifyTransformer :: (TransformerMonad n) => a -> (DocumentState -> n (DocumentState, a)) -> Uri -> n a -modifyTransformer def cb uri = do - TransformerState {..} <- ask - modifyMVar transformerDocuments $ \m -> case M.lookup (getUri uri) m of - Nothing -> return (m, def) - Just tx -> do - (tx', ret) <- cb tx - return (M.insert (getUri uri) tx' m, ret) - --- * Checking server capabilities - -whenServerCapabilitiesSatisfy :: TransformerMonad n => (ServerCapabilities -> (Bool, a)) -> (a -> n ()) -> n () -whenServerCapabilitiesSatisfy cb action = do - initializeResultVar <- asks transformerInitializeResult - readMVar initializeResultVar >>= \case - Just ((cb . (^. capabilities)) -> (True, sideValue)) -> action sideValue - _ -> return () - -supportsWillSave :: ServerCapabilities -> (Bool, ()) -supportsWillSave (ServerCapabilities { _textDocumentSync=(Just (InL (TextDocumentSyncOptions {_willSave=(Just True)}))) }) = (True, ()) -supportsWillSave _ = (False, ()) - -supportsSave :: ServerCapabilities -> (Bool, Maybe SaveOptions) -supportsSave (ServerCapabilities { _textDocumentSync=(Just (InL (TextDocumentSyncOptions {_save}))) }) = case _save of - Nothing -> (False, Nothing) - Just (InL x) -> (x, Nothing) - Just (InR saveOptions) -> (True, Just saveOptions) -supportsSave _ = (False, Nothing) - --- * Random identifiers - --- Note: for a UUID to appear in a Kubernetes name, it needs to match this regex --- [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*' -uuidLetters :: [Char] -uuidLetters = ['a'..'z'] ++ ['A'..'Z'] - -numUUIDLetters :: Int -numUUIDLetters = L.length uuidLetters - -makeUUID' :: MonadIO m => Int -> m String -makeUUID' n = replicateM n ((uuidLetters L.!!) <$> R.randomRIO (0, numUUIDLetters - 1)) - --- * Misc - -addExtensionToUri :: (MonadLogger m) => String -> Uri -> m Uri -addExtensionToUri ext u@(Uri t) = case parseURIReference (T.unpack t) of - Nothing -> do - logErrorN [i|Couldn't parse URI: #{t}|] - return u - Just uri -> return $ Uri $ T.pack $ show $ uri { uriPath = uriPath uri <.> ext } - -flipTuple :: (b, a) -> (a, b) -flipTuple (x, y) = (y, x) diff --git a/stack.yaml b/stack.yaml index cf72afa..009a583 100644 --- a/stack.yaml +++ b/stack.yaml @@ -9,6 +9,9 @@ nix: system-ghc: true extra-deps: +- git: https://github.com/codedownio/notebook-language-server-core.git + commit: d8ca894fa723d805abaacb85f35503f72c3aec7f + - git: https://github.com/codedownio/sandwich # long-running-nodes branch commit: c3e16ccda384917fba1ccc5f3cf8f592415562c1 diff --git a/stack.yaml.lock b/stack.yaml.lock index e742167..f45d7bb 100644 --- a/stack.yaml.lock +++ b/stack.yaml.lock @@ -4,6 +4,17 @@ # https://docs.haskellstack.org/en/stable/topics/lock_files packages: +- completed: + commit: d8ca894fa723d805abaacb85f35503f72c3aec7f + git: https://github.com/codedownio/notebook-language-server-core.git + name: notebook-language-server-core + pantry-tree: + sha256: 1dbe24fb672a644b2664955e8a7d786688eea9c02c91af5a05b31490f7a7e29f + size: 1969 + version: 0.1.0.0 + original: + commit: d8ca894fa723d805abaacb85f35503f72c3aec7f + git: https://github.com/codedownio/notebook-language-server-core.git - completed: commit: c3e16ccda384917fba1ccc5f3cf8f592415562c1 git: https://github.com/codedownio/sandwich