packages feed

hsinspect-lsp 0.0.1 → 0.0.2

raw patch · 6 files changed

+200/−114 lines, 6 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- HsInspect.LSP.Context: DiscoverContext :: (FilePath -> m FilePath) -> (FilePath -> m FilePath) -> (FilePath -> m FilePath) -> (FilePath -> m FilePath) -> DiscoverContext m
- HsInspect.LSP.Context: [discoverGhcflags] :: DiscoverContext m -> FilePath -> m FilePath
- HsInspect.LSP.Context: [discoverGhcpath] :: DiscoverContext m -> FilePath -> m FilePath
- HsInspect.LSP.Context: [discoverHsInspect] :: DiscoverContext m -> FilePath -> m FilePath
- HsInspect.LSP.Context: [discoverPackageDir] :: DiscoverContext m -> FilePath -> m FilePath
- HsInspect.LSP.Context: data DiscoverContext m
- HsInspect.LSP.Context: mkDiscoverContext :: BuildTool -> DiscoverContext (ExceptT String IO)
- HsInspect.LSP.HsInspect: HsInspect :: (Context -> FilePath -> m [Import]) -> (Context -> m [Package]) -> HsInspect m
- HsInspect.LSP.HsInspect: [$sel:imports:HsInspect] :: HsInspect m -> Context -> FilePath -> m [Import]
- HsInspect.LSP.HsInspect: [$sel:index:HsInspect] :: HsInspect m -> Context -> m [Package]
- HsInspect.LSP.HsInspect: data HsInspect m
- HsInspect.LSP.HsInspect: mkHsInspect :: HsInspect (ExceptT String IO)
- HsInspect.LSP.Impl: signatureHelpProvider :: Caches -> BuildTool -> FilePath -> (Int, Int) -> ExceptT String IO [Text]
- HsInspect.LSP.Util: locateDominating :: (String -> Bool) -> FilePath -> ExceptT String IO FilePath
+ HsInspect.LSP.Context: [srcdir] :: Context -> FilePath
+ HsInspect.LSP.Context: discoverGhcflags :: FilePath -> ExceptT String IO FilePath
+ HsInspect.LSP.Context: discoverGhcpath :: FilePath -> ExceptT String IO FilePath
+ HsInspect.LSP.Context: discoverHsInspect :: FilePath -> BuildTool -> ExceptT String IO FilePath
+ HsInspect.LSP.Context: discoverPackageDir :: FilePath -> ExceptT String IO FilePath
+ HsInspect.LSP.Context: failWithM :: Applicative m => e -> m (Maybe a) -> ExceptT e m a
+ HsInspect.LSP.Context: help_ghcflags :: String
+ HsInspect.LSP.Context: help_hsinspect :: String
+ HsInspect.LSP.HsInspect: hsinspect_imports :: Context -> FilePath -> ExceptT String IO [Import]
+ HsInspect.LSP.HsInspect: hsinspect_index :: Context -> ExceptT String IO [Package]
+ HsInspect.LSP.HsInspect: hsinspect_raw :: FromJSON a => Context -> [String] -> ExceptT String IO a
+ HsInspect.LSP.Impl: Span :: Int -> Int -> Int -> Int -> Span
+ HsInspect.LSP.Impl: cachedIndex :: Caches -> Context -> ExceptT String IO [Package]
+ HsInspect.LSP.Impl: cachedIndex' :: Caches -> Context -> ExceptT String IO [Package]
+ HsInspect.LSP.Impl: data Span
+ HsInspect.LSP.Impl: findType :: Text -> [Package] -> Maybe Text
+ HsInspect.LSP.Impl: hoverProvider :: Caches -> BuildTool -> FilePath -> (Int, Int) -> ExceptT String IO (Maybe (Span, Text))
+ HsInspect.LSP.Util: locateDominatingDir :: (String -> Bool) -> FilePath -> IO (Maybe FilePath)
+ HsInspect.LSP.Util: locateDominatingFile :: (String -> Bool) -> FilePath -> IO (Maybe FilePath)
- HsInspect.LSP.Context: Context :: FilePath -> FilePath -> [String] -> String -> Context
+ HsInspect.LSP.Context: Context :: FilePath -> FilePath -> [String] -> String -> FilePath -> Context
- HsInspect.LSP.Context: findContext :: MonadIO m => DiscoverContext m -> FilePath -> m Context
+ HsInspect.LSP.Context: findContext :: FilePath -> BuildTool -> ExceptT String IO Context
- HsInspect.LSP.Impl: Caches :: Cache FilePath Context -> Cache FilePath [Import] -> Caches
+ HsInspect.LSP.Impl: Caches :: Cache FilePath Context -> Cache FilePath [Import] -> Cache FilePath [Package] -> Caches
- HsInspect.LSP.Impl: symbolAtPoint :: FilePath -> (Int, Int) -> ExceptT String IO Text
+ HsInspect.LSP.Impl: symbolAtPoint :: FilePath -> (Int, Int) -> ExceptT String IO (Maybe (Span, Text))

Files

exe/Main.hs view
@@ -14,6 +14,7 @@ import Control.Monad.STM import qualified Data.Cache as C import Data.Default+import qualified Data.Text as T import Data.Typeable (typeOf) import HsInspect.LSP.Context (BuildTool(..)) import HsInspect.LSP.Impl@@ -78,35 +79,66 @@     ioExcept   (e :: E.IOException) = print e >> return 1     someExcept (e :: E.SomeException) = print e >> return 1 +-- supported requests are duplicated here, in the reactor, and lspHandlers+supported :: [J.ClientMethod]+supported = [J.TextDocumentHover]+ reactor :: BuildTool -> Core.LspFuncs () -> TChan FromClientMessage -> IO () reactor tool lf inp = do   U.logs "reactor:entered"-  caches <- Caches <$> C.newCache Nothing <*> C.newCache Nothing+  caches <- Caches <$> C.newCache Nothing <*> C.newCache Nothing <*> C.newCache Nothing   forever $ do     inval <- atomically $ readTChan inp     case inval of -      (ReqSignatureHelp req@(J.RequestMessage _ _ _ params)) -> do-        U.logs $ "reactor:SignatureHelp:" ++ show params+      NotInitialized _notification -> do+        U.logs "reactor:init"+        let reg cmd = J.Registration "hsinspect-lsp" cmd Nothing+            regs = J.RegistrationParams (J.List $ reg <$> supported)++        rid <- Core.getNextReqId lf+        Core.sendFunc lf . ReqRegisterCapability $ fmServerRegisterCapabilityRequest rid regs++      (ReqHover req@(J.RequestMessage _ _ _ params)) -> do+        U.logs $ "reactor:hover:" ++ show params         let J.TextDocumentPositionParams (J.TextDocumentIdentifier doc) (J.Position line col) _ = params             Just file = J.uriToFilePath doc-        res <- runExceptT $ signatureHelpProvider caches tool file (line + 1, col + 1)+        res <- runExceptT $ hoverProvider caches tool file (line + 1, col + 1)         case res of           Left err -> do-            U.logs $ "reactor:signatureHelpProvider:err:" ++ err-            -- FIXME how to send an error to the client?-          Right sigs -> do-            let render s = J.SignatureInformation s Nothing Nothing-                halp = J.SignatureHelp (J.List $ render <$> sigs) Nothing Nothing-            Core.sendFunc lf . RspSignatureHelp $ Core.makeResponseMessage req halp+            U.logs $ "reactor:hover:err:" ++ err+            -- the only way to get a popup on the user's screen is to use a show+            -- notification, the ErrorReq ends up being rendered exactly the+            -- same as a success, so useless.+            Core.sendFunc lf . RspHover $ Core.makeResponseMessage req Nothing+            Core.sendFunc lf . NotShowMessage $+              J.NotificationMessage "2.0" J.WindowShowMessage (J.ShowMessageParams J.MtWarning $ T.pack err) -      -- TODO completionProvider-      -- TODO definitionProvider+          Right Nothing -> do+            Core.sendFunc lf . RspHover $ Core.makeResponseMessage req Nothing -      -- TODO DidOpenTextDocument is a good opportunity to preemptively populate caches+          Right (Just (Span line' col' line'' col'', txt)) -> do+            let halp = J.Hover+                         (J.HoverContents . J.unmarkedUpContent $ txt)+                         (Just $ J.Range (J.Position line' col') (J.Position line'' col''))+            Core.sendFunc lf . RspHover $ Core.makeResponseMessage req (Just halp) -      -- TODO hygienic registration of supported commands+      -- preemptively populate caches+      NotDidOpenTextDocument (J.NotificationMessage _ _ params) -> do+        U.logs "reactor:open"+        let (J.DidOpenTextDocumentParams (J.TextDocumentItem uri _ _ _)) = params+            Just file = J.uriToFilePath uri+        -- TODO forkIO+        void . runExceptT $ do+          ctx <- cachedContext caches tool file+          void $ cachedImports caches ctx file+          void $ cachedIndex caches ctx +      -- TODO completionProvider+      -- TODO definitionProvider+      -- TODO signatureHelpProvider+      -- TODO import symbol at point (CodeActionQuickFix?)+       om -> do         U.logs $ "reactor:HandlerRequest:" ++ (show $ typeOf om) @@ -114,10 +146,12 @@ lspHandlers rin =   let passHandler :: (a -> FromClientMessage) -> Core.Handler a       passHandler c notification = atomically $ writeTChan rin (c notification)-  in def { Core.signatureHelpHandler = Just $ passHandler ReqSignatureHelp+  in def { Core.hoverHandler = Just $ passHandler ReqHover          , Core.completionHandler = Just $ passHandler ReqCompletion          , Core.definitionHandler = Just $ passHandler ReqDefinition          , Core.initializedHandler = Just $ passHandler NotInitialized-         , Core.cancelNotificationHandler = Just $ passHandler NotCancelRequestFromClient          , Core.didOpenTextDocumentNotificationHandler = Just $ passHandler NotDidOpenTextDocument+         -- Emacs lsp-mode sends these, even though we don't ask for them...+         , Core.cancelNotificationHandler = Just $ passHandler NotCancelRequestFromClient+         , Core.responseHandler = Just $ \_ -> pure ()          }
hsinspect-lsp.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name:          hsinspect-lsp-version:       0.0.1+version:       0.0.2 synopsis:      LSP interface over the hsinspect binary. license:       GPL-3.0-or-later license-file:  LICENSE
library/HsInspect/LSP/Context.hs view
@@ -9,7 +9,7 @@ module HsInspect.LSP.Context where  import Control.Monad.IO.Class (liftIO)-import Control.Monad.IO.Class (MonadIO)+import Control.Monad.Trans.Except (withExceptT) import Control.Monad.Trans.Except (ExceptT(..)) import Data.List (isSuffixOf) import Data.List.Extra (trim)@@ -21,54 +21,62 @@   , package_dir :: FilePath   , ghcflags :: [String]   , ghcpath :: String+  , srcdir :: FilePath   } -findContext :: MonadIO m => DiscoverContext m -> FilePath -> m Context-findContext DiscoverContext{..} src = do+data BuildTool = Cabal | Stack++findContext :: FilePath -> BuildTool -> ExceptT String IO Context+findContext src tool = do   ghcflags' <- discoverGhcflags src   ghcpath' <- discoverGhcpath src   let readWords file = words <$> readFile' file       readFile' = liftIO . readFile-  Context <$> discoverHsInspect src <*> discoverPackageDir src <*> readWords ghcflags' <*> readFile' ghcpath'+  Context <$> discoverHsInspect src tool <*> discoverPackageDir src <*> readWords ghcflags' <*> readFile' ghcpath' <*> pure (takeDirectory ghcflags') -data DiscoverContext m = DiscoverContext-  { discoverHsInspect :: FilePath -> m FilePath-  , discoverPackageDir :: FilePath -> m FilePath-  , discoverGhcflags :: FilePath -> m FilePath-  , discoverGhcpath :: FilePath -> m FilePath-  }+discoverHsInspect :: FilePath -> BuildTool -> ExceptT String IO FilePath+discoverHsInspect file tool = do+  let dir = takeDirectory file+  dir' <- discoverPackageDir dir+  withExceptT (\err -> help_hsinspect ++ "\n\n" ++ err) $ case tool of+    Cabal -> do+      _ <- shell "cabal" ["build", "-v0", ":pkg:hsinspect:exe:hsinspect"] (Just dir') Nothing []+      trim <$> shell "cabal" ["exec", "-v0", "which", "--", "hsinspect"] (Just dir') Nothing []+    Stack -> do+      _ <- shell "stack" ["build", "--silent", "hsinspect"] (Just dir') Nothing []+      trim <$> shell "stack" ["exec", "--silent", "which", "--", "hsinspect"] (Just dir') Nothing [] -data BuildTool = Cabal | Stack+-- c.f. haskell-tng--compile-dominating-package+discoverPackageDir :: FilePath -> ExceptT String IO FilePath+discoverPackageDir file = do+  let dir = takeDirectory file+      isCabal = (".cabal" `isSuffixOf`)+      isHpack = ("package.yaml" ==)+  failWithM "There must be a .cabal or package.yaml" $+    locateDominatingDir (\f -> isCabal f || isHpack f) dir -mkDiscoverContext :: BuildTool -> DiscoverContext (ExceptT String IO)-mkDiscoverContext tool = DiscoverContext {..}-  where-    discoverHsInspect :: FilePath -> ExceptT String IO FilePath-    discoverHsInspect file = do-      let dir = takeDirectory file-      dir' <- discoverPackageDir dir-      case tool of-        Cabal -> do-          _ <- shell "cabal" ["build", "-v0", ":pkg:hsinspect:exe:hsinspect"] (Just dir') Nothing []-          trim <$> shell "cabal" ["exec", "-v0", "which", "--", "hsinspect"] (Just dir') Nothing []-        Stack -> do-          _ <- shell "stack" ["build", "--silent", "hsinspect"] (Just dir') Nothing []-          trim <$> shell "stack" ["exec", "--silent", "which", "--", "hsinspect"] (Just dir') Nothing []+discoverGhcflags :: FilePath -> ExceptT String IO FilePath+discoverGhcflags file = do+  let dir = takeDirectory file+  failWithM ("There must be a .ghc.flags file. " ++ help_ghcflags) $+   locateDominatingFile (".ghc.flags" ==) dir -    -- c.f. haskell-tng--compile-dominating-package-    discoverPackageDir :: FilePath -> ExceptT String IO FilePath-    discoverPackageDir file = do-      let dir = takeDirectory file-          isCabal = (".cabal" `isSuffixOf`)-          isHpack = ("package.yaml" ==)-      locateDominating (\f -> isCabal f || isHpack f) dir+discoverGhcpath :: FilePath -> ExceptT String IO FilePath+discoverGhcpath file = do+  let dir = takeDirectory file+  failWithM ("There must be a .ghc.path file. " ++ help_ghcflags) $+    locateDominatingFile (".ghc.path" ==) dir -    discoverGhcflags :: FilePath -> ExceptT String IO FilePath-    discoverGhcflags file = do-      let dir = takeDirectory file-      (</> ".ghc.flags") <$> locateDominating (".ghc.flags" ==) dir+-- note that any formatting in these messages are stripped+help_ghcflags :: String+help_ghcflags = "The cause of this error could be that this package has not been compiled yet, \+                \or the ghcflags compiler plugin has not been installed for this package. \+                \See https://gitlab.com/tseenshe/hsinspect#installation for more details." -    discoverGhcpath :: FilePath -> ExceptT String IO FilePath-    discoverGhcpath file = do-      let dir = takeDirectory file-      (</> ".ghc.path") <$> locateDominating (".ghc.path" ==) dir+help_hsinspect :: String+help_hsinspect = "The hsinspect binary has not been installed for this package. \+                 \See https://gitlab.com/tseenshe/hsinspect#installation for more details."++-- from Control.Error.Util+failWithM :: Applicative m => e -> m (Maybe a) -> ExceptT e m a+failWithM e ma = ExceptT $ (maybe (Left e) Right) <$> ma
library/HsInspect/LSP/HsInspect.hs view
@@ -23,24 +23,17 @@ import HsInspect.LSP.Util import qualified System.Log.Logger as L -data HsInspect m = HsInspect-  { imports :: Context -> FilePath -> m [Import]-  , index :: Context -> m [Package]-  }+hsinspect_imports :: Context -> FilePath -> ExceptT String IO [Import]+hsinspect_imports ctx hs = hsinspect_raw ctx ["imports", hs] -mkHsInspect :: HsInspect (ExceptT String IO)-mkHsInspect = HsInspect {..}-  where-    imports :: Context -> FilePath -> ExceptT String IO [Import]-    imports ctx hs = call ctx ["imports", hs]-    index :: Context -> ExceptT String IO [Package]-    index ctx = call ctx ["index"]+hsinspect_index :: Context -> ExceptT String IO [Package]+hsinspect_index ctx = hsinspect_raw ctx ["index"] -    call :: FromJSON a => Context -> [String] -> ExceptT String IO a-    call Context{hsinspect, package_dir, ghcflags, ghcpath} args = do-      liftIO $ L.debugM "haskell-lsp" $ "hsinspect-lsp:cwd:" <> package_dir-      stdout <- shell hsinspect (args <> ["--json", "--"] <> ghcflags) (Just package_dir) (Just ghcpath) [("GHC_ENVIRONMENT", "-")]-      ExceptT . pure . eitherDecodeStrict' $ C.pack stdout+hsinspect_raw :: FromJSON a => Context -> [String] -> ExceptT String IO a+hsinspect_raw Context{hsinspect, package_dir, ghcflags, ghcpath} args = do+  liftIO $ L.debugM "haskell-lsp" $ "hsinspect-lsp:cwd:" <> package_dir+  stdout <- shell hsinspect (args <> ["--json", "--"] <> ghcflags) (Just package_dir) (Just ghcpath) [("GHC_ENVIRONMENT", "-")]+  ExceptT . pure . eitherDecodeStrict' $ C.pack stdout  data Import = Import   { _local :: Maybe Text
library/HsInspect/LSP/Impl.hs view
@@ -1,3 +1,6 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE ViewPatterns #-}  -- Features of https://microsoft.github.io/language-server-protocol/specification.html module HsInspect.LSP.Impl where@@ -8,10 +11,12 @@ import Control.Monad.Trans.Except (throwE) import Data.Cache (Cache) import qualified Data.Cache as C-import qualified Data.List as L-import Data.Maybe (mapMaybe)+import Data.List.Extra (firstJust)+import Data.Maybe (listToMaybe)+import Data.Maybe (fromMaybe) import Data.Text (Text) import qualified Data.Text as T+import Debug.Trace (traceShowId) import qualified FastString as GHC import qualified GHC as GHC import GHC.Paths (libdir)@@ -20,50 +25,84 @@ import qualified Lexer as GHC import qualified SrcLoc as GHC import qualified StringBuffer as GHC+import System.FilePath (takeDirectory) --- TODO consider more advanced cache keys, e.g. Import could be cached on a---      checksum of the file's header plus a checksum of the .ghc.* files.+-- TODO consider invalidation strategies, e.g. Import could use the file's+--      header, and the Context use the .ghc.* files. The index might also want+--      to hash the .ghc.flags contents plus any changes to exported symbols in+--      the current package. But beware that often it is better to have a stale+--      cache and respond with *something* than to be slow and redo the work. data Caches = Caches-  (Cache FilePath Context) -- ^ by the package_dir+  (Cache FilePath Context) -- ^ by source root   (Cache FilePath [Import]) -- ^ by source filename+  (Cache FilePath [Package]) -- ^ by source root +-- TODO the index could use a data structure that is faster to search+ cachedContext :: Caches -> BuildTool -> FilePath -> ExceptT String IO Context-cachedContext (Caches cache _) tool file = do-  let discover = mkDiscoverContext tool-  key <- discoverPackageDir discover file+cachedContext (Caches cache _ _) tool file = do+  key <- takeDirectory <$> discoverGhcflags file   let work = do-        ctx <- findContext discover file+        ctx <- findContext file tool         liftIO $ C.insert cache key ctx         pure ctx   fromMaybeM work . liftIO $ C.lookup cache key  cachedImports :: Caches -> Context -> FilePath -> ExceptT String IO [Import]-cachedImports (Caches _ cache) ctx file =-  C.fetchWithCache cache file $ imports mkHsInspect ctx+cachedImports (Caches _ cache _) ctx file =+  C.fetchWithCache cache file $ hsinspect_imports ctx --- FIXME return all hits, not just the first one--- TODO docs / parameter info-signatureHelpProvider :: Caches -> BuildTool -> FilePath -> (Int, Int) -> ExceptT String IO [Text]-signatureHelpProvider caches tool file position = do-  ctx <- cachedContext caches tool file-  symbols <- cachedImports caches ctx file-  sym <- symbolAtPoint file position+cachedIndex :: Caches -> Context -> ExceptT String IO [Package]+cachedIndex (Caches _ _ cache) ctx =+  C.fetchWithCache cache (srcdir ctx) $ \_ -> hsinspect_index ctx -  -- TODO include type information by consulting the index+-- only lookup the index, don't try to populate it+cachedIndex' :: Caches -> Context -> ExceptT String IO [Package]+cachedIndex' (Caches _ _ cache) ctx = liftIO $+  fromMaybe [] <$> C.lookup cache (srcdir ctx) -  let-    matcher imp =-      if _local imp == Just sym || _qual imp == Just sym || _full imp == sym-      then Just $ _full imp-      else Nothing+-- zero indexed+data Span = Span Int Int Int Int -- line, col, line, col -  pure $ mapMaybe matcher symbols+findType :: Text -> [Package] -> Maybe Text+findType qual pkgs = listToMaybe $ do+  let (T.init -> module', sym) = T.breakOnEnd "." qual+  let flatten Nothing = []+      flatten (Just as) = as+  pkg <- pkgs+  Module module'' entries <- flatten . _modules $ pkg+  if module'' /= module'+  then []+  else do+    e <- traceShowId $ flatten entries+    let matcher name typ = if name == sym && name /= typ then [typ] else []+    case e of+      Id _ name typ -> matcher name typ+      Con _ name typ -> matcher name typ+      Pat _ name typ -> matcher name typ+      TyCon _ _ _ -> [] +hoverProvider :: Caches -> BuildTool -> FilePath -> (Int, Int) -> ExceptT String IO (Maybe (Span, Text))+hoverProvider caches tool file position = do+  ctx <- cachedContext caches tool file+  symbols <- cachedImports caches ctx file+  index <- cachedIndex' caches ctx+  found <- symbolAtPoint file position+  pure $ case found of+    Nothing -> Nothing+    Just (range, sym) ->+      let matcher imp = if _local imp == Just sym || _qual imp == Just sym || _full imp == sym+                        then Just $ case findType (_full imp) index of+                          Just typ -> _full imp <> " :: " <> typ+                          Nothing -> _full imp+                        else Nothing+       in (range,) <$> firstJust matcher symbols+ -- c.f. haskell-tng--hsinspect-symbol-at-point -- -- TODO consider replacing this (inefficient) ghc api usage with a regexp or -- calling the specific lexer functions directly for the symbols we support.-symbolAtPoint :: FilePath -> (Int, Int) -> ExceptT String IO Text+symbolAtPoint :: FilePath -> (Int, Int) -> ExceptT String IO (Maybe (Span, Text)) symbolAtPoint file (line, col) = do   buf' <- liftIO $ GHC.hGetStringBuffer file   -- TODO for performance, and language extension reliability, find out how to@@ -81,11 +120,16 @@   -- lexTokenStream :: StringBuffer -> RealSrcLoc -> DynFlags -> ParseResult [Located Token]   case GHC.lexTokenStream buf startLoc dflags of     GHC.POk _ ts  ->-      let containsPoint :: GHC.SrcSpan -> Bool-          containsPoint (GHC.UnhelpfulSpan _) = False-          containsPoint (GHC.RealSrcSpan s) = GHC.containsSpan s point-       in maybe (throwE "could not find a token") (pure . T.pack . snd) .-            L.find (containsPoint . GHC.getLoc . fst) $-            GHC.addSourceToTokens startLoc buf ts+      let containsPoint :: (GHC.Located GHC.Token, String) -> Maybe (Span, Text)+          containsPoint ((GHC.L (GHC.UnhelpfulSpan _) _), _) = Nothing+          containsPoint ((GHC.L (GHC.RealSrcSpan s) _), txt) =+            if GHC.containsSpan s point then Just (toSpan s, T.pack txt) else Nothing+          toSpan src = Span+            (GHC.srcSpanStartLine src - 1)+            (GHC.srcSpanStartCol src - 1)+            (GHC.srcSpanEndLine src - 1)+            (GHC.srcSpanEndCol src - 1)++       in pure . firstJust containsPoint $ GHC.addSourceToTokens startLoc buf ts      _ -> throwE "lexer error" -- TODO getErrorMessages
library/HsInspect/LSP/Util.hs view
@@ -2,9 +2,9 @@ module HsInspect.LSP.Util where  import Control.Monad.IO.Class (liftIO)-import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Except (ExceptT(..)) import Data.List (intercalate)+import qualified Data.List as L import System.Directory (listDirectory) import System.Environment (getEnvironment) import System.Exit (ExitCode(..))@@ -13,15 +13,22 @@ import qualified System.Process as P  -- the first parent directory where a file or directory name matches the predicate-locateDominating :: (String -> Bool) -> FilePath -> ExceptT String IO FilePath-locateDominating p dir = do-  files <- lift $ listDirectory dir+locateDominatingDir :: (String -> Bool) -> FilePath -> IO (Maybe FilePath)+locateDominatingDir p dir = do+  file' <- locateDominatingFile p dir+  pure $ takeDirectory <$> file'++-- same as locateDominating but returns the first file that matches the predicate+locateDominatingFile :: (String -> Bool) -> FilePath -> IO (Maybe FilePath)+locateDominatingFile p dir = do+  files <- listDirectory dir   let parent = takeDirectory dir-  if any p $ takeFileName <$> files-  then pure dir-  else if parent == dir-       then ExceptT . pure . Left $ "locateDominating"-       else locateDominating p parent+  case L.find p $ takeFileName <$> files of+    Just file -> pure . Just $ dir </> file+    Nothing ->+      if parent == dir+       then pure Nothing+       else locateDominatingFile p parent  shell :: String -> [String] -> Maybe FilePath -> Maybe String -> [(String, String)] -> ExceptT String IO String shell command args cwd path env_extra = ExceptT $ do