packages feed

haskell-lsp 0.17.0.0 → 0.18.0.0

raw patch · 10 files changed

+283/−169 lines, 10 filesdep ~haskell-lsp-typesPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: haskell-lsp-types

API changes (from Hackage documentation)

- Language.Haskell.LSP.Core: [codeActionProvider] :: Options -> Maybe CodeActionOptions
- Language.Haskell.LSP.Core: [codeLensProvider] :: Options -> Maybe CodeLensOptions
- Language.Haskell.LSP.Core: [colorProvider] :: Options -> Maybe ColorOptions
- Language.Haskell.LSP.Core: [completionProvider] :: Options -> Maybe CompletionOptions
- Language.Haskell.LSP.Core: [documentLinkProvider] :: Options -> Maybe DocumentLinkOptions
- Language.Haskell.LSP.Core: [documentOnTypeFormattingProvider] :: Options -> Maybe DocumentOnTypeFormattingOptions
- Language.Haskell.LSP.Core: [documentTypeFormattingHandler] :: Handlers -> !Maybe (Handler DocumentOnTypeFormattingRequest)
- Language.Haskell.LSP.Core: [executeCommandProvider] :: Options -> Maybe ExecuteCommandOptions
- Language.Haskell.LSP.Core: [foldingRangeProvider] :: Options -> Maybe FoldingRangeOptions
- Language.Haskell.LSP.Core: [implementationProvider] :: Options -> Maybe GotoOptions
- Language.Haskell.LSP.Core: [renameProvider] :: Options -> Maybe RenameOptions
- Language.Haskell.LSP.Core: [signatureHelpProvider] :: Options -> Maybe SignatureHelpOptions
- Language.Haskell.LSP.Core: [typeDefinitionProvider] :: Options -> Maybe GotoOptions
- Language.Haskell.LSP.VFS: [$sel:_tmp_file:VirtualFile] :: VirtualFile -> Maybe FilePath
- Language.Haskell.LSP.VFS: type VFS = Map NormalizedUri VirtualFile
+ Language.Haskell.LSP.Core: VFSData :: !VFS -> !Map FilePath FilePath -> VFSData
+ Language.Haskell.LSP.Core: [codeActionKinds] :: Options -> Maybe [CodeActionKind]
+ Language.Haskell.LSP.Core: [completionAllCommitCharacters] :: Options -> Maybe [Char]
+ Language.Haskell.LSP.Core: [completionTriggerCharacters] :: Options -> Maybe [Char]
+ Language.Haskell.LSP.Core: [documentOnTypeFormattingHandler] :: Handlers -> !Maybe (Handler DocumentOnTypeFormattingRequest)
+ Language.Haskell.LSP.Core: [documentOnTypeFormattingTriggerCharacters] :: Options -> Maybe (NonEmpty Char)
+ Language.Haskell.LSP.Core: [executeCommandCommands] :: Options -> Maybe [Text]
+ Language.Haskell.LSP.Core: [signatureHelpRetriggerCharacters] :: Options -> Maybe [Char]
+ Language.Haskell.LSP.Core: [signatureHelpTriggerCharacters] :: Options -> Maybe [Char]
+ Language.Haskell.LSP.Core: [vfsData] :: VFSData -> !VFS
+ Language.Haskell.LSP.Core: data VFSData
+ Language.Haskell.LSP.VFS: VFS :: Map NormalizedUri VirtualFile -> FilePath -> VFS
+ Language.Haskell.LSP.VFS: [$sel:vfsMap:VFS] :: VFS -> Map NormalizedUri VirtualFile
+ Language.Haskell.LSP.VFS: [$sel:vfsTempDir:VFS] :: VFS -> FilePath
+ Language.Haskell.LSP.VFS: data VFS
+ Language.Haskell.LSP.VFS: initVFS :: (VFS -> IO r) -> IO r
+ Language.Haskell.LSP.VFS: instance GHC.Show.Show Language.Haskell.LSP.VFS.VFS
+ Language.Haskell.LSP.VFS: updateVFS :: (VFSMap -> VFSMap) -> VFS -> VFS
- Language.Haskell.LSP.Core: LanguageContextData :: !Int -> !Handlers -> !Options -> !SendFunc -> !VFS -> !Map FilePath FilePath -> !DiagnosticStore -> !Maybe config -> !TVar Int -> LspFuncs config -> !Maybe FilePath -> ![WorkspaceFolder] -> !ProgressData -> LanguageContextData config
+ Language.Haskell.LSP.Core: LanguageContextData :: !Int -> !Handlers -> !Options -> !SendFunc -> !VFSData -> !DiagnosticStore -> !Maybe config -> !TVar Int -> LspFuncs config -> !Maybe FilePath -> ![WorkspaceFolder] -> !ProgressData -> LanguageContextData config
- Language.Haskell.LSP.Core: Options :: Maybe TextDocumentSyncOptions -> Maybe CompletionOptions -> Maybe SignatureHelpOptions -> Maybe GotoOptions -> Maybe GotoOptions -> Maybe CodeActionOptions -> Maybe CodeLensOptions -> Maybe DocumentOnTypeFormattingOptions -> Maybe RenameOptions -> Maybe DocumentLinkOptions -> Maybe ColorOptions -> Maybe FoldingRangeOptions -> Maybe ExecuteCommandOptions -> Options
+ Language.Haskell.LSP.Core: Options :: Maybe TextDocumentSyncOptions -> Maybe [Char] -> Maybe [Char] -> Maybe [Char] -> Maybe [Char] -> Maybe [CodeActionKind] -> Maybe (NonEmpty Char) -> Maybe [Text] -> Options
- Language.Haskell.LSP.Core: [resVFS] :: LanguageContextData config -> !VFS
+ Language.Haskell.LSP.Core: [resVFS] :: LanguageContextData config -> !VFSData
- Language.Haskell.LSP.Core: [reverseMap] :: LanguageContextData config -> !Map FilePath FilePath
+ Language.Haskell.LSP.Core: [reverseMap] :: VFSData -> !Map FilePath FilePath
- Language.Haskell.LSP.Core: defaultLanguageContextData :: Handlers -> Options -> LspFuncs config -> TVar Int -> SendFunc -> Maybe FilePath -> LanguageContextData config
+ Language.Haskell.LSP.Core: defaultLanguageContextData :: Handlers -> Options -> LspFuncs config -> TVar Int -> SendFunc -> Maybe FilePath -> VFS -> LanguageContextData config
- Language.Haskell.LSP.VFS: VirtualFile :: Int -> Rope -> Maybe FilePath -> VirtualFile
+ Language.Haskell.LSP.VFS: VirtualFile :: Int -> Rope -> VirtualFile
- Language.Haskell.LSP.VFS: changeFromClientVFS :: VFS -> DidChangeTextDocumentNotification -> IO VFS
+ Language.Haskell.LSP.VFS: changeFromClientVFS :: VFS -> DidChangeTextDocumentNotification -> (VFS, [String])
- Language.Haskell.LSP.VFS: closeVFS :: VFS -> DidCloseTextDocumentNotification -> IO VFS
+ Language.Haskell.LSP.VFS: closeVFS :: VFS -> DidCloseTextDocumentNotification -> (VFS, [String])
- Language.Haskell.LSP.VFS: openVFS :: VFS -> DidOpenTextDocumentNotification -> IO VFS
+ Language.Haskell.LSP.VFS: openVFS :: VFS -> DidOpenTextDocumentNotification -> (VFS, [String])
- Language.Haskell.LSP.VFS: persistFileVFS :: VFS -> NormalizedUri -> IO (FilePath, VFS)
+ Language.Haskell.LSP.VFS: persistFileVFS :: VFS -> NormalizedUri -> (FilePath, IO ())

Files

ChangeLog.md view
@@ -1,5 +1,17 @@ # Revision history for haskell-lsp +## 0.18.0.0 -- 2019-11-17++* Explain the use of NonEmpty in+  documentOnTypeFormattingTriggerCharacters (@bubba)+* Fix response type for CodeLensResolve, add the ContentModified error+  code (@SquidDev)+* Virtual file fixes, removing race conditions and other cleanups (@mpickering)+* Add missing fmClientPrepareRenameRequest to MessageFuncs export (@alanz)+* Make explicit GHC 8.6.5 stack file (@alanz)+* Rework Core.Options and infer all server capabilities from handlers (@bubba)+* Generate lenses for WorkDoneProgress data types (@alanz)+ ## 0.17.0.0 -- 2019-10-18  * Update progress reporting to match the LSP 3.15 specification (@cocreature)
haskell-lsp.cabal view
@@ -1,5 +1,5 @@ name:                haskell-lsp-version:             0.17.0.0+version:             0.18.0.0 synopsis:            Haskell library for the Microsoft Language Server Protocol  description:         An implementation of the types, and basic message server to@@ -46,7 +46,7 @@                      , filepath                      , hslogger                      , hashable-                     , haskell-lsp-types == 0.17.*+                     , haskell-lsp-types == 0.18.*                      , lens >= 4.15.2                      , mtl                      , network-uri
src/Language/Haskell/LSP/Capture.hs view
@@ -18,7 +18,7 @@ captureFromServer msg (Just fp) = do   time <- getCurrentTime   let entry = FromServer time msg-  +   BSL.appendFile fp $ BSL.append (encode entry) "\n"  captureFromClient :: FromClientMessage -> Maybe FilePath -> IO ()@@ -26,5 +26,5 @@ captureFromClient msg (Just fp) = do   time <- getCurrentTime   let entry = FromClient time msg-  +   BSL.appendFile fp $ BSL.append (encode entry) "\n"
src/Language/Haskell/LSP/Control.hs view
@@ -31,6 +31,7 @@ import           Language.Haskell.LSP.Capture import qualified Language.Haskell.LSP.Core as Core import           Language.Haskell.LSP.Messages+import           Language.Haskell.LSP.VFS import           Language.Haskell.LSP.Utility import           System.IO import           System.FilePath@@ -83,10 +84,10 @@   let lf = error "LifeCycle error, ClientCapabilities not set yet via initialize maessage"    tvarId <- atomically $ newTVar 0--  tvarDat <- atomically $ newTVar $ Core.defaultLanguageContextData h o lf tvarId sendFunc timestampCaptureFp+  initVFS $ \vfs -> do+    tvarDat <- atomically $ newTVar $ Core.defaultLanguageContextData h o lf tvarId sendFunc timestampCaptureFp vfs -  ioLoop hin initializeCallbacks tvarDat+    ioLoop hin initializeCallbacks tvarDat    return 1 
src/Language/Haskell/LSP/Core.hs view
@@ -9,6 +9,7 @@ module Language.Haskell.LSP.Core (     handleMessage   , LanguageContextData(..)+  , VFSData(..)   , Handler   , InitializeCallbacks(..)   , LspFuncs(..)@@ -28,18 +29,19 @@   , reverseSortEdit   ) where -import           Control.Concurrent.STM import           Control.Concurrent.Async+import           Control.Concurrent.STM import qualified Control.Exception as E import           Control.Monad import           Control.Monad.IO.Class-import           Control.Lens ( (<&>), (^.) )+import           Control.Lens ( (<&>), (^.), (^?), _Just ) import qualified Data.Aeson as J import qualified Data.ByteString.Lazy as BSL import qualified Data.ByteString.Lazy.Char8 as B import           Data.Default import qualified Data.HashMap.Strict as HM import qualified Data.List as L+import           Data.List.NonEmpty (NonEmpty(..)) import qualified Data.Map as Map import           Data.Maybe import           Data.Monoid@@ -79,8 +81,7 @@   , resHandlers            :: !Handlers   , resOptions             :: !Options   , resSendResponse        :: !SendFunc-  , resVFS                 :: !VFS-  , reverseMap             :: !(Map.Map FilePath FilePath)+  , resVFS                 :: !VFSData   , resDiagnostics         :: !DiagnosticStore   , resConfig              :: !(Maybe config)   , resLspId               :: !(TVar Int)@@ -93,31 +94,46 @@ data ProgressData = ProgressData { progressNextId :: !Int                                  , progressCancel :: !(Map.Map J.ProgressToken (IO ())) } +data VFSData =+  VFSData+    { vfsData :: !VFS+    , reverseMap :: !(Map.Map FilePath FilePath)+    }+ -- --------------------------------------------------------------------- --- | Language Server Protocol options supported by the given language server.--- These are automatically turned into capabilities reported to the client--- during initialization.+-- | Language Server Protocol options that the server may configure.+-- If you set handlers for some requests, you may need to set some of these options. data Options =   Options     { textDocumentSync                 :: Maybe J.TextDocumentSyncOptions-    , completionProvider               :: Maybe J.CompletionOptions-    , signatureHelpProvider            :: Maybe J.SignatureHelpOptions-    , typeDefinitionProvider           :: Maybe J.GotoOptions-    , implementationProvider           :: Maybe J.GotoOptions-    , codeActionProvider               :: Maybe J.CodeActionOptions-    , codeLensProvider                 :: Maybe J.CodeLensOptions-    , documentOnTypeFormattingProvider :: Maybe J.DocumentOnTypeFormattingOptions-    , renameProvider                   :: Maybe J.RenameOptions-    , documentLinkProvider             :: Maybe J.DocumentLinkOptions-    , colorProvider                    :: Maybe J.ColorOptions-    , foldingRangeProvider             :: Maybe J.FoldingRangeOptions-    , executeCommandProvider           :: Maybe J.ExecuteCommandOptions+    -- |  The characters that trigger completion automatically.+    , completionTriggerCharacters      :: Maybe [Char]+    -- | The list of all possible characters that commit a completion. This field can be used+    -- if clients don't support individual commmit characters per completion item. See+    -- `_commitCharactersSupport`.+    , completionAllCommitCharacters    :: Maybe [Char]+    -- | The characters that trigger signature help automatically.+    , signatureHelpTriggerCharacters   :: Maybe [Char]+    -- | List of characters that re-trigger signature help.+    -- These trigger characters are only active when signature help is already showing. All trigger characters+    -- are also counted as re-trigger characters.+    , signatureHelpRetriggerCharacters :: Maybe [Char]+    -- | CodeActionKinds that this server may return.+    -- The list of kinds may be generic, such as `CodeActionKind.Refactor`, or the server+    -- may list out every specific kind they provide.+    , codeActionKinds                  :: Maybe [J.CodeActionKind]+    -- | The list of characters that triggers on type formatting.+    -- If you set `documentOnTypeFormattingHandler`, you **must** set this.+    -- The first character is mandatory, so a 'NonEmpty' should be passed.+    , documentOnTypeFormattingTriggerCharacters :: Maybe (NonEmpty Char)+    -- | The commands to be executed on the server.+    -- If you set `executeCommandHandler`, you **must** set this.+    , executeCommandCommands           :: Maybe [Text]     }  instance Default Options where   def = Options Nothing Nothing Nothing Nothing Nothing-                Nothing Nothing Nothing Nothing Nothing                 Nothing Nothing Nothing  -- | A function to publish diagnostics. It aggregates all diagnostics pertaining@@ -159,6 +175,8 @@       -- server-provided function.     , sendFunc                     :: !SendFunc     , getVirtualFileFunc           :: !(J.NormalizedUri -> IO (Maybe VirtualFile))+      -- ^ Function to return the 'VirtualFile' associated with a+      -- given 'NormalizedUri', if there is one.     , persistVirtualFileFunc       :: !(J.NormalizedUri -> IO FilePath)     , reverseFileMapFunc           :: !(IO (FilePath -> FilePath))     , publishDiagnosticsFunc       :: !PublishDiagnosticsFunc@@ -236,7 +254,7 @@     , colorPresentationHandler       :: !(Maybe (Handler J.ColorPresentationRequest))     , documentFormattingHandler      :: !(Maybe (Handler J.DocumentFormattingRequest))     , documentRangeFormattingHandler :: !(Maybe (Handler J.DocumentRangeFormattingRequest))-    , documentTypeFormattingHandler  :: !(Maybe (Handler J.DocumentOnTypeFormattingRequest))+    , documentOnTypeFormattingHandler :: !(Maybe (Handler J.DocumentOnTypeFormattingRequest))     , renameHandler                  :: !(Maybe (Handler J.RenameRequest))     , prepareRenameHandler           :: !(Maybe (Handler J.PrepareRenameRequest))     , foldingRangeHandler            :: !(Maybe (Handler J.FoldingRangeRequest))@@ -297,8 +315,8 @@                               Nothing Nothing Nothing Nothing Nothing  -- ----------------------------------------------------------------------nop :: a -> b -> IO a-nop = const . return+nop :: Maybe (a -> b -> (a,[String]))+nop = Nothing   helper :: J.FromJSON a => (TVar (LanguageContextData config) -> a -> IO ()) -> (TVar (LanguageContextData config) -> J.Value -> IO ())@@ -316,8 +334,9 @@           _ -> failLog         _ -> failLog -handlerMap :: (Show config) => InitializeCallbacks config-           -> Handlers -> J.ClientMethod -> (TVar (LanguageContextData config) -> J.Value -> IO ())+handlerMap :: (Show config)+           => InitializeCallbacks config -> Handlers -> J.ClientMethod+           -> (TVar (LanguageContextData config) -> J.Value -> IO ()) -- General handlerMap i h J.Initialize                      = handleInitialConfig i (initializeRequestHandler h) handlerMap _ h J.Initialized                     = hh nop NotInitialized $ initializedHandler h@@ -341,12 +360,12 @@ handlerMap _ h J.WorkspaceSymbol                 = hh nop ReqWorkspaceSymbols $ workspaceSymbolHandler h handlerMap _ h J.WorkspaceExecuteCommand         = hh nop ReqExecuteCommand $ executeCommandHandler h -- Document-handlerMap _ h J.TextDocumentDidOpen             = hh openVFS NotDidOpenTextDocument $ didOpenTextDocumentNotificationHandler h-handlerMap _ h J.TextDocumentDidChange           = hh changeFromClientVFS NotDidChangeTextDocument $ didChangeTextDocumentNotificationHandler h+handlerMap _ h J.TextDocumentDidOpen             = hh (Just openVFS) NotDidOpenTextDocument $ didOpenTextDocumentNotificationHandler h+handlerMap _ h J.TextDocumentDidChange           = hh (Just changeFromClientVFS) NotDidChangeTextDocument $ didChangeTextDocumentNotificationHandler h handlerMap _ h J.TextDocumentWillSave            = hh nop NotWillSaveTextDocument $ willSaveTextDocumentNotificationHandler h handlerMap _ h J.TextDocumentWillSaveWaitUntil   = hh nop ReqWillSaveWaitUntil $ willSaveWaitUntilTextDocHandler h handlerMap _ h J.TextDocumentDidSave             = hh nop NotDidSaveTextDocument $ didSaveTextDocumentNotificationHandler h-handlerMap _ h J.TextDocumentDidClose            = hh closeVFS NotDidCloseTextDocument $ didCloseTextDocumentNotificationHandler h+handlerMap _ h J.TextDocumentDidClose            = hh (Just closeVFS) NotDidCloseTextDocument $ didCloseTextDocumentNotificationHandler h handlerMap _ h J.TextDocumentCompletion          = hh nop ReqCompletion $ completionHandler h handlerMap _ h J.CompletionItemResolve           = hh nop ReqCompletionItemResolve $ completionResolveHandler h handlerMap _ h J.TextDocumentHover               = hh nop ReqHover $ hoverHandler h@@ -359,7 +378,7 @@ handlerMap _ h J.TextDocumentDocumentSymbol      = hh nop ReqDocumentSymbols $ documentSymbolHandler h handlerMap _ h J.TextDocumentFormatting          = hh nop ReqDocumentFormatting $ documentFormattingHandler h handlerMap _ h J.TextDocumentRangeFormatting     = hh nop ReqDocumentRangeFormatting $ documentRangeFormattingHandler h-handlerMap _ h J.TextDocumentOnTypeFormatting    = hh nop ReqDocumentOnTypeFormatting $ documentTypeFormattingHandler h+handlerMap _ h J.TextDocumentOnTypeFormatting    = hh nop ReqDocumentOnTypeFormatting $ documentOnTypeFormattingHandler h handlerMap _ h J.TextDocumentCodeAction          = hh nop ReqCodeAction $ codeActionHandler h handlerMap _ h J.TextDocumentCodeLens            = hh nop ReqCodeLens $ codeLensHandler h handlerMap _ h J.CodeLensResolve                 = hh nop ReqCodeLensResolve $ codeLensResolveHandler h@@ -384,14 +403,19 @@ -- | Adapter from the normal handlers exposed to the library users and the -- internal message loop hh :: forall b config. (J.FromJSON b)-   => (VFS -> b -> IO VFS) -> (b -> FromClientMessage) -> Maybe (Handler b) -> TVar (LanguageContextData config) -> J.Value -> IO ()-hh getVfs wrapper mh tvarDat json = do+   => Maybe (VFS -> b -> (VFS, [String])) -> (b -> FromClientMessage) -> Maybe (Handler b)+   -> TVar (LanguageContextData config) -> J.Value -> IO ()+hh mVfs wrapper mh tvarDat json = do       case J.fromJSON json of         J.Success req -> do-          ctx <- readTVarIO tvarDat-          vfs' <- getVfs (resVFS ctx) req-          atomically $ modifyTVar' tvarDat (\c -> c {resVFS = vfs'})+          case mVfs of+            Just modifyVfs -> do+              join $ atomically $ modifyVFSData tvarDat $ \(VFSData vfs rm) ->+                let (vfs', ls) = modifyVfs vfs req+                in (VFSData vfs' rm, mapM_ logs ls)+            Nothing -> return () +          ctx <- readTVarIO tvarDat           captureFromClient (wrapper req) (resCaptureFile ctx)            case mh of@@ -455,10 +479,8 @@               T.pack $ unwords                 ["haskell-lsp:configuration parse error.", show req, show err]           sendErrorLog tvarDat msg-        Right newConfig -> do-          -- logs $ "haskell-lsp:hc DidChangeConfigurationNotification got newConfig:" ++ show newConfig-          let ctx' = ctx { resConfig = Just newConfig }-          atomically $ modifyTVar' tvarDat (const ctx')+        Right newConfig ->+          atomically $ modifyTVar' tvarDat (\ctx' -> ctx' { resConfig = Just newConfig })       case mh of         Just h  -> h req         Nothing -> return ()@@ -488,18 +510,28 @@  -- --------------------------------------------------------------------- +modifyVFSData :: TVar (LanguageContextData config) -> (VFSData -> (VFSData, a)) -> STM a+modifyVFSData tvarDat f = do+  (vfs', a) <- f . resVFS <$> readTVar tvarDat+  modifyTVar tvarDat $ \vd -> vd { resVFS = vfs' }+  return a++-- ---------------------------------------------------------------------++-- | Return the 'VirtualFile' associated with a given 'NormalizedUri', if there is one. getVirtualFile :: TVar (LanguageContextData config) -> J.NormalizedUri -> IO (Maybe VirtualFile)-getVirtualFile tvarDat uri = Map.lookup uri . resVFS <$> readTVarIO tvarDat+getVirtualFile tvarDat uri = Map.lookup uri . vfsMap . vfsData . resVFS <$> readTVarIO tvarDat  -- | Dump the current text for a given VFS file to a temporary file, -- and return the path to the file. persistVirtualFile :: TVar (LanguageContextData config) -> J.NormalizedUri -> IO FilePath-persistVirtualFile tvarDat uri = do-  st <- readTVarIO tvarDat-  let vfs = resVFS st-      revMap = reverseMap st+persistVirtualFile tvarDat uri = join $ atomically $ do+  st <- readTVar tvarDat+  let vfs_data = resVFS st+      cur_vfs = vfsData vfs_data+      revMap = reverseMap vfs_data -  (fn, new_vfs) <- persistFileVFS vfs uri+  let (fn, write) = persistFileVFS cur_vfs uri   let revMap' =         -- TODO: Does the VFS make sense for URIs which are not files?         -- The reverse map should perhaps be (FilePath -> URI)@@ -507,9 +539,8 @@           Just uri_fp -> Map.insert fn uri_fp revMap           Nothing -> revMap -  atomically $ modifyTVar' tvarDat (\d -> d { resVFS = new_vfs-                                            , reverseMap = revMap' })-  return fn+  modifyVFSData tvarDat (\d -> (d { reverseMap = revMap' }, ()))+  return (fn <$ write)  -- TODO: should this function return a URI? -- | If the contents of a VFS has been dumped to a temporary file, map@@ -517,9 +548,9 @@ reverseFileMap :: TVar (LanguageContextData config)                -> IO (FilePath -> FilePath) reverseFileMap tvarDat = do-  revMap <- reverseMap <$> readTVarIO tvarDat-  let f fp = fromMaybe fp $ Map.lookup fp revMap-  return f+    vfs <- resVFS <$> readTVarIO tvarDat+    let f fp = fromMaybe fp . Map.lookup fp . reverseMap $ vfs+    return f  -- --------------------------------------------------------------------- @@ -558,9 +589,9 @@ -- | -- ---defaultLanguageContextData :: Handlers -> Options -> LspFuncs config -> TVar Int -> SendFunc -> Maybe FilePath -> LanguageContextData config-defaultLanguageContextData h o lf tv sf cf =-  LanguageContextData _INITIAL_RESPONSE_SEQUENCE h o sf mempty mempty mempty+defaultLanguageContextData :: Handlers -> Options -> LspFuncs config -> TVar Int -> SendFunc -> Maybe FilePath -> VFS -> LanguageContextData config+defaultLanguageContextData h o lf tv sf cf vfs =+  LanguageContextData _INITIAL_RESPONSE_SEQUENCE h o sf (VFSData vfs mempty) mempty                       Nothing tv lf cf mempty defaultProgressData  defaultProgressData :: ProgressData@@ -758,7 +789,7 @@                     -> ((Progress -> IO ()) -> IO a) -> IO a)       withProgressBase indefinite title cancellable f         | clientSupportsProgress = do-          sf <- liftIO $ resSendResponse <$> readTVarIO tvarCtx+          let sf = sendResponse tvarCtx            progId <- getNewProgressId @@ -821,9 +852,10 @@                             (getWfs tvarCtx)                             withProgress'                             withIndefiniteProgress'-    let ctx = ctx0 { resLspFuncs = lspFuncs }-    atomically $ writeTVar tvarCtx ctx+    atomically $ modifyTVar tvarCtx (\cur_ctx -> cur_ctx { resLspFuncs = lspFuncs }) +    ctx <- readTVarIO tvarCtx+     initializationResult <- onStartup lspFuncs      case initializationResult of@@ -831,68 +863,106 @@         sendResponse tvarCtx $ RspError $ makeResponseError (J.responseId origId) errResp        Nothing -> do+        let capa = serverCapabilities (getCapabilities params) (resOptions ctx) (resHandlers ctx)+            -- TODO: wrap this up into a fn to create a response message+            res  = J.ResponseMessage "2.0" (J.responseId origId) (Just $ J.InitializeResponseCapabilities capa) Nothing -        let-          h = resHandlers ctx-          o = resOptions  ctx+        sendResponse tvarCtx $ RspInitialize res -          supported (Just _) = Just True-          supported Nothing   = Nothing+-- | Infers the capabilities based on registered handlers, and sets the appropriate options.+-- A provider should be set to Nothing if the server does not support it, unless it is a+-- static option.+serverCapabilities :: C.ClientCapabilities -> Options -> Handlers -> J.InitializeResponseCapabilitiesInner+serverCapabilities clientCaps o h =+  J.InitializeResponseCapabilitiesInner+    { J._textDocumentSync                 = sync+    , J._hoverProvider                    = supported (hoverHandler h)+    , J._completionProvider               = completionProvider+    , J._signatureHelpProvider            = signatureHelpProvider+    , J._definitionProvider               = supported (definitionHandler h)+    , J._typeDefinitionProvider           = Just $ J.GotoOptionsStatic $ isJust $ typeDefinitionHandler h+    , J._implementationProvider           = Just $ J.GotoOptionsStatic $ isJust $ typeDefinitionHandler h+    , J._referencesProvider               = supported (referencesHandler h)+    , J._documentHighlightProvider        = supported (documentHighlightHandler h)+    , J._documentSymbolProvider           = supported (documentSymbolHandler h)+    , J._workspaceSymbolProvider          = supported (workspaceSymbolHandler h)+    , J._codeActionProvider               = codeActionProvider+    , J._codeLensProvider                 = supported' (codeLensHandler h) $ J.CodeLensOptions $+                                              supported (codeLensResolveHandler h)+    , J._documentFormattingProvider       = supported (documentFormattingHandler h)+    , J._documentRangeFormattingProvider  = supported (documentRangeFormattingHandler h)+    , J._documentOnTypeFormattingProvider = documentOnTypeFormattingProvider+    , J._renameProvider                   = Just $ J.RenameOptionsStatic $ isJust $ renameHandler h+    , J._documentLinkProvider             = supported' (documentLinkHandler h) $ J.DocumentLinkOptions $+                                              Just $ isJust $ documentLinkResolveHandler h+    , J._colorProvider                    = Just $ J.ColorOptionsStatic $ isJust $ documentColorHandler h+    , J._foldingRangeProvider             = Just $ J.FoldingRangeOptionsStatic $ isJust $ foldingRangeHandler h+    , J._executeCommandProvider           = executeCommandProvider+    , J._workspace                        = Just workspace+    -- TODO: Add something for experimental+    , J._experimental                     = Nothing :: Maybe J.Value+    }+  where+    supported x = supported' x True -          -- If a dynamic setting is provided use it, else set a-          -- static True if there is a handler.-          static (Just d) _ = Just d-          static _ (Just _) = Just (J.GotoOptionsStatic True)-          static _ Nothing  = Nothing-          -          static' (Just d) (Just _) = Just d-          static' _ (Just _) = Just (J.CodeActionOptionsStatic True)-          static' _ _  = Nothing+    supported' (Just _) = Just+    supported' Nothing = const Nothing -          sync = case textDocumentSync o of-                  Just x -> Just (J.TDSOptions x)-                  Nothing -> Nothing+    singleton :: a -> [a]+    singleton x = [x] -          workspace = J.WorkspaceOptions workspaceFolder-          workspaceFolder = case didChangeWorkspaceFoldersNotificationHandler h of-            Just _ -> Just $-              -- sign up to receive notifications-              J.WorkspaceFolderOptions (Just True) (Just (J.WorkspaceFolderChangeNotificationsBool True))-            Nothing -> Nothing+    completionProvider+      | isJust $ completionHandler h = Just $+          J.CompletionOptions+            (Just $ isJust $ completionResolveHandler h)+            (map singleton <$> completionTriggerCharacters o)+            (map singleton <$> completionAllCommitCharacters o)+      | otherwise = Nothing -          capa =-            J.InitializeResponseCapabilitiesInner-              { J._textDocumentSync                 = sync-              , J._hoverProvider                    = supported (hoverHandler h)-              , J._completionProvider               = completionProvider o-              , J._signatureHelpProvider            = signatureHelpProvider o-              , J._definitionProvider               = supported (definitionHandler h)-              , J._typeDefinitionProvider           = static (typeDefinitionProvider o) (typeDefinitionHandler h)-              , J._implementationProvider           = implementationProvider o-              , J._referencesProvider               = supported (referencesHandler h)-              , J._documentHighlightProvider        = supported (documentHighlightHandler h)-              , J._documentSymbolProvider           = supported (documentSymbolHandler h)-              , J._workspaceSymbolProvider          = supported (workspaceSymbolHandler h)-              , J._codeActionProvider               = static' (codeActionProvider o) (codeActionHandler h)-              , J._codeLensProvider                 = codeLensProvider o-              , J._documentFormattingProvider       = supported (documentFormattingHandler h)-              , J._documentRangeFormattingProvider  = supported (documentRangeFormattingHandler h)-              , J._documentOnTypeFormattingProvider = documentOnTypeFormattingProvider o-              , J._renameProvider                   = renameProvider o-              , J._documentLinkProvider             = documentLinkProvider o-              , J._colorProvider                    = colorProvider o-              , J._foldingRangeProvider             = foldingRangeProvider o-              , J._executeCommandProvider           = executeCommandProvider o-              , J._workspace                        = Just workspace-              -- TODO: Add something for experimental-              , J._experimental                     = Nothing :: Maybe J.Value-              }+    clientSupportsCodeActionKinds = isJust $+      clientCaps ^? J.textDocument . _Just . J.codeAction . _Just . J.codeActionLiteralSupport -          -- TODO: wrap this up into a fn to create a response message-          res  = J.ResponseMessage "2.0" (J.responseId origId) (Just $ J.InitializeResponseCapabilities capa) Nothing+    codeActionProvider+      | clientSupportsCodeActionKinds+      , isJust $ codeActionHandler h = Just $ maybe (J.CodeActionOptionsStatic True) (J.CodeActionOptions . Just) (codeActionKinds o)+      | isJust $ codeActionHandler h = Just (J.CodeActionOptionsStatic True)+      | otherwise = Just (J.CodeActionOptionsStatic False) -        sendResponse tvarCtx $ RspInitialize res+    signatureHelpProvider+      | isJust $ signatureHelpHandler h = Just $+          J.SignatureHelpOptions+            (map singleton <$> signatureHelpTriggerCharacters o)+            (map singleton <$> signatureHelpRetriggerCharacters o)+      | otherwise = Nothing +    documentOnTypeFormattingProvider+      | isJust $ documentOnTypeFormattingHandler h+      , Just (first :| rest) <- documentOnTypeFormattingTriggerCharacters o = Just $+          J.DocumentOnTypeFormattingOptions (T.pack [first]) (Just (map (T.pack . singleton) rest))+      | isJust $ documentOnTypeFormattingHandler h+      , Nothing <- documentOnTypeFormattingTriggerCharacters o =+          error "documentOnTypeFormattingTriggerCharacters needs to be set if a documentOnTypeFormattingHandler is set"+      | otherwise = Nothing++    executeCommandProvider+      | isJust $ executeCommandHandler h+      , Just cmds <- executeCommandCommands o = Just (J.ExecuteCommandOptions (J.List cmds))+      | isJust $ executeCommandHandler h+      , Nothing <- executeCommandCommands o =+          error "executeCommandCommands needs to be set if a executeCommandHandler is set"+      | otherwise = Nothing++    sync = case textDocumentSync o of+            Just x -> Just (J.TDSOptions x)+            Nothing -> Nothing++    workspace = J.WorkspaceOptions workspaceFolder+    workspaceFolder = case didChangeWorkspaceFoldersNotificationHandler h of+      Just _ -> Just $+        -- sign up to receive notifications+        J.WorkspaceFolderOptions (Just True) (Just (J.WorkspaceFolderChangeNotificationsBool True))+      Nothing -> Nothing+ progressCancelHandler :: TVar (LanguageContextData config) -> J.WorkDoneProgressCancelNotification -> IO () progressCancelHandler tvarCtx (J.NotificationMessage _ _ (J.WorkDoneProgressCancelParams tid)) = do   mact <- Map.lookup tid . progressCancel . resProgressData <$> readTVarIO tvarCtx@@ -916,28 +986,29 @@ -- and version, and publish the total to the client. publishDiagnostics :: TVar (LanguageContextData config) -> PublishDiagnosticsFunc publishDiagnostics tvarDat maxDiagnosticCount uri version diags = do-  ctx <- readTVarIO tvarDat-  let ds = updateDiagnostics (resDiagnostics ctx) uri version diags-  atomically $ writeTVar tvarDat $ ctx{resDiagnostics = ds}-  let mdp = getDiagnosticParamsFor maxDiagnosticCount ds uri-  case mdp of-    Nothing -> return ()-    Just params -> do-      resSendResponse ctx $ NotPublishDiagnostics-        $ J.NotificationMessage "2.0" J.TextDocumentPublishDiagnostics params+  join $ atomically $ do+    ctx <- readTVar tvarDat+    let ds = updateDiagnostics (resDiagnostics ctx) uri version diags+    writeTVar tvarDat $ ctx{resDiagnostics = ds}+    let mdp = getDiagnosticParamsFor maxDiagnosticCount ds uri+    return $ case mdp of+      Nothing -> return ()+      Just params ->+        resSendResponse ctx $ NotPublishDiagnostics+          $ J.NotificationMessage "2.0" J.TextDocumentPublishDiagnostics params  -- ---------------------------------------------------------------------  -- | Take the new diagnostics, update the stored diagnostics for the given file -- and version, and publish the total to the client. flushDiagnosticsBySource :: TVar (LanguageContextData config) -> FlushDiagnosticsBySourceFunc-flushDiagnosticsBySource tvarDat maxDiagnosticCount msource = do+flushDiagnosticsBySource tvarDat maxDiagnosticCount msource = join $ atomically $ do   -- logs $ "haskell-lsp:flushDiagnosticsBySource:source=" ++ show source-  ctx <- readTVarIO tvarDat+  ctx <- readTVar tvarDat   let ds = flushBySource (resDiagnostics ctx) msource-  atomically $ writeTVar tvarDat $ ctx {resDiagnostics = ds}+  writeTVar tvarDat $ ctx {resDiagnostics = ds}   -- Send the updated diagnostics to the client-  forM_ (Map.keys ds) $ \uri -> do+  return $ forM_ (Map.keys ds) $ \uri -> do     -- logs $ "haskell-lsp:flushDiagnosticsBySource:uri=" ++ show uri     let mdp = getDiagnosticParamsFor maxDiagnosticCount ds uri     case mdp of
src/Language/Haskell/LSP/VFS.hs view
@@ -4,6 +4,7 @@ {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE NamedFieldPuns #-}  {-| Handles the "Language.Haskell.LSP.Types.TextDocumentDidChange" \/@@ -14,13 +15,15 @@ -} module Language.Haskell.LSP.VFS   (-    VFS+    VFS(..)   , VirtualFile(..)+  , initVFS   , openVFS   , changeFromClientVFS   , changeFromServerVFS   , persistFileVFS   , closeVFS+  , updateVFS    -- * manipulating the file contents   , rangeLinesFromVfs@@ -40,10 +43,6 @@ import qualified Data.Text as T import           Data.List import           Data.Ord-#if __GLASGOW_HASKELL__ < 804-import           Data.Monoid-#endif-import           System.IO.Temp import qualified Data.HashMap.Strict as HashMap import qualified Data.Map as Map import           Data.Maybe@@ -52,6 +51,10 @@ import qualified Language.Haskell.LSP.Types           as J import qualified Language.Haskell.LSP.Types.Lens      as J import           Language.Haskell.LSP.Utility+import           System.FilePath+import           Data.Hashable+import           System.Directory+import           System.IO.Temp  -- --------------------------------------------------------------------- {-# ANN module ("hlint: ignore Eta reduce" :: String) #-}@@ -62,35 +65,50 @@   VirtualFile {       _version :: Int     , _text    :: Rope-    , _tmp_file :: Maybe FilePath     } deriving (Show) -type VFS = Map.Map J.NormalizedUri VirtualFile+type VFSMap = Map.Map J.NormalizedUri VirtualFile +data VFS = VFS { vfsMap :: Map.Map J.NormalizedUri VirtualFile+               , vfsTempDir :: FilePath -- ^ This is where all the temporary files will be written to+               } deriving Show++---++initVFS :: (VFS -> IO r) -> IO r+initVFS k = withSystemTempDirectory "haskell-lsp" $ \temp_dir -> k (VFS mempty temp_dir)+ -- --------------------------------------------------------------------- -openVFS :: VFS -> J.DidOpenTextDocumentNotification -> IO VFS-openVFS vfs (J.NotificationMessage _ _ params) = do+openVFS :: VFS -> J.DidOpenTextDocumentNotification -> (VFS, [String])+openVFS vfs (J.NotificationMessage _ _ params) =   let J.DidOpenTextDocumentParams          (J.TextDocumentItem uri _ version text) = params-  return $ Map.insert (J.toNormalizedUri uri) (VirtualFile version (Rope.fromText text) Nothing) vfs+  in (updateVFS (Map.insert (J.toNormalizedUri uri) (VirtualFile version (Rope.fromText text))) vfs+     , []) + -- --------------------------------------------------------------------- -changeFromClientVFS :: VFS -> J.DidChangeTextDocumentNotification -> IO VFS-changeFromClientVFS vfs (J.NotificationMessage _ _ params) = do+changeFromClientVFS :: VFS -> J.DidChangeTextDocumentNotification -> (VFS,[String])+changeFromClientVFS vfs (J.NotificationMessage _ _ params) =   let     J.DidChangeTextDocumentParams vid (J.List changes) = params     J.VersionedTextDocumentIdentifier (J.toNormalizedUri -> uri) version = vid-  case Map.lookup uri vfs of-    Just (VirtualFile _ str _) -> do-      let str' = applyChanges str changes-      -- the client shouldn't be sending over a null version, only the server.-      return $ Map.insert uri (VirtualFile (fromMaybe 0 version) str' Nothing) vfs-    Nothing -> do-      logs $ "haskell-lsp:changeVfs:can't find uri:" ++ show uri-      return vfs+  in+    case Map.lookup uri (vfsMap vfs) of+      Just (VirtualFile _ str) ->+        let str' = applyChanges str changes+        -- the client shouldn't be sending over a null version, only the server.+        in (updateVFS (Map.insert uri (VirtualFile (fromMaybe 0 version) str')) vfs, [])+      Nothing ->+        -- logs $ "haskell-lsp:changeVfs:can't find uri:" ++ show uri+        -- return vfs+        (vfs, ["haskell-lsp:changeVfs:can't find uri:" ++ show uri]) +updateVFS :: (VFSMap -> VFSMap) -> VFS -> VFS+updateVFS f vfs@VFS{vfsMap} = vfs { vfsMap = f vfsMap }+ -- ---------------------------------------------------------------------  changeFromServerVFS :: VFS -> J.ApplyWorkspaceEditRequest -> IO VFS@@ -110,8 +128,11 @@     changeToTextDocumentEdit acc uri edits =       acc ++ [J.TextDocumentEdit (J.VersionedTextDocumentIdentifier uri (Just 0)) edits] +    -- applyEdits :: [J.TextDocumentEdit] -> VFS+    applyEdits :: [J.TextDocumentEdit] -> IO VFS     applyEdits = foldM f initVfs . sortOn (^. J.textDocument . J.version) +    f :: VFS -> J.TextDocumentEdit -> IO VFS     f vfs (J.TextDocumentEdit vid (J.List edits)) = do       -- all edits are supposed to be applied at once       -- so apply from bottom up so they don't affect others@@ -119,29 +140,34 @@           changeEvents = map editToChangeEvent sortedEdits           ps = J.DidChangeTextDocumentParams vid (J.List changeEvents)           notif = J.NotificationMessage "" J.TextDocumentDidChange ps-      changeFromClientVFS vfs notif+      let (vfs',ls) = changeFromClientVFS vfs notif+      mapM_ logs ls+      return vfs'      editToChangeEvent (J.TextEdit range text) = J.TextDocumentContentChangeEvent (Just range) Nothing text  -- ---------------------------------------------------------------------+virtualFileName :: FilePath -> J.NormalizedUri -> VirtualFile -> FilePath+virtualFileName prefix uri (VirtualFile ver _) =+  prefix </> show (hash (J.fromNormalizedUri uri)) ++ "-" ++ show ver ++ ".hs" -persistFileVFS :: VFS -> J.NormalizedUri -> IO (FilePath, VFS)+persistFileVFS :: VFS -> J.NormalizedUri -> (FilePath, IO ()) persistFileVFS vfs uri =-  case Map.lookup uri vfs of+  case Map.lookup uri (vfsMap vfs) of     Nothing -> error ("File not found in VFS: " ++ show uri ++ show vfs)-    Just (VirtualFile v txt tfile) ->-      case tfile of-        Just tfn -> return (tfn, vfs)-        Nothing  -> do-          fn <- writeSystemTempFile "VFS.hs" (Rope.toString txt)-          return (fn, Map.insert uri (VirtualFile v txt (Just fn)) vfs)+    Just vf@(VirtualFile _v txt) ->+      let tfn = virtualFileName (vfsTempDir vfs) uri vf+          action = do+            exists <- doesFileExist tfn+            unless exists (writeFile tfn (Rope.toString txt))+      in (tfn, action)  -- --------------------------------------------------------------------- -closeVFS :: VFS -> J.DidCloseTextDocumentNotification -> IO VFS-closeVFS vfs (J.NotificationMessage _ _ params) = do+closeVFS :: VFS -> J.DidCloseTextDocumentNotification -> (VFS, [String])+closeVFS vfs (J.NotificationMessage _ _ params) =   let J.DidCloseTextDocumentParams (J.TextDocumentIdentifier uri) = params-  return $ Map.delete (J.toNormalizedUri uri) vfs+  in (updateVFS (Map.delete (J.toNormalizedUri uri)) vfs,[])  -- --------------------------------------------------------------------- {-@@ -208,7 +234,7 @@   } deriving (Show,Eq)  getCompletionPrefix :: (Monad m) => J.Position -> VirtualFile -> m (Maybe PosPrefixInfo)-getCompletionPrefix pos@(J.Position l c) (VirtualFile _ yitext _) =+getCompletionPrefix pos@(J.Position l c) (VirtualFile _ ropetext) =       return $ Just $ fromMaybe (PosPrefixInfo "" "" "" pos) $ do -- Maybe monad         let headMaybe [] = Nothing             headMaybe (x:_) = Just x@@ -216,7 +242,7 @@             lastMaybe xs = Just $ last xs          curLine <- headMaybe $ T.lines $ Rope.toText-                             $ fst $ Rope.splitAtLine 1 $ snd $ Rope.splitAtLine l yitext+                             $ fst $ Rope.splitAtLine 1 $ snd $ Rope.splitAtLine l ropetext         let beforePos = T.take c curLine         curWord <- case T.last beforePos of                      ' ' -> return "" -- don't count abc as the curword in 'abc '@@ -235,10 +261,9 @@ -- ---------------------------------------------------------------------  rangeLinesFromVfs :: VirtualFile -> J.Range -> T.Text-rangeLinesFromVfs (VirtualFile _ yitext _) (J.Range (J.Position lf _cf) (J.Position lt _ct)) = r+rangeLinesFromVfs (VirtualFile _ ropetext) (J.Range (J.Position lf _cf) (J.Position lt _ct)) = r   where-    (_ ,s1) = Rope.splitAtLine lf yitext+    (_ ,s1) = Rope.splitAtLine lf ropetext     (s2, _) = Rope.splitAtLine (lt - lf) s1     r = Rope.toText s2- -- ---------------------------------------------------------------------
test/InitialConfigurationSpec.hs view
@@ -8,12 +8,13 @@ import           Data.Default import           Language.Haskell.LSP.Core import           Language.Haskell.LSP.Types+import           Language.Haskell.LSP.VFS import           Language.Haskell.LSP.Types.Capabilities import           Test.Hspec  spec :: Spec spec =-  describe "initial configuration" $ it "stores initial configuration data" $ do+  describe "initial configuration" $ it "stores initial configuration data" $ initVFS $ \vfs -> do      lfVar <- newEmptyMVar @@ -40,6 +41,7 @@                                                         tvarLspId                                                         (const $ return ())                                                         Nothing+                                                        vfs      let putMsg msg =           let jsonStr = encode msg in handleMessage initCb tvarCtx jsonStr
test/JsonSpec.hs view
@@ -96,6 +96,7 @@       , ServerNotInitialized       , UnknownErrorCode       , RequestCancelled+      , ContentModified       ]  -- | make lists of maximum length 3 for test performance
test/VspSpec.hs view
@@ -31,7 +31,7 @@ mkRange ls cs le ce = Just $ J.Range (J.Position ls cs) (J.Position le ce)  vfsFromText :: T.Text -> VirtualFile-vfsFromText text = VirtualFile 0 (Rope.fromText text) Nothing+vfsFromText text = VirtualFile 0 (Rope.fromText text)  -- --------------------------------------------------------------------- 
test/WorkspaceFoldersSpec.hs view
@@ -8,12 +8,13 @@ import Data.Default import Language.Haskell.LSP.Core import Language.Haskell.LSP.Types+import Language.Haskell.LSP.VFS import Language.Haskell.LSP.Types.Capabilities import Test.Hspec  spec :: Spec spec =-  describe "workspace folders" $ it "keeps track of open workspace folders" $ do+  describe "workspace folders" $ it "keeps track of open workspace folders" $ initVFS $ \vfs -> do      lfVar <- newEmptyMVar @@ -31,6 +32,7 @@                                                         tvarLspId                                                         (const $ return ())                                                         Nothing+                                                        vfs      let putMsg msg =           let jsonStr = encode msg