nvim-hs-ghcid 2.0.0.0 → 2.0.1.0
raw patch · 6 files changed
+139/−149 lines, 6 filesdep ~nvim-hssetup-changedPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: nvim-hs
API changes (from Hackage documentation)
Files
- Neovim/Ghcid.hs +11/−11
- Neovim/Ghcid/Plugin.hs +115/−125
- README.md +9/−10
- Setup.hs +1/−0
- executable/Main.hs +2/−2
- nvim-hs-ghcid.cabal +1/−1
Neovim/Ghcid.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE TemplateHaskell #-}+ {- | Module : Neovim.Ghcid Description : Ghcid plugin@@ -8,10 +9,8 @@ Maintainer : woozletoff@gmail.com Stability : experimental Portability : GHC- -}-module Neovim.Ghcid- where+module Neovim.Ghcid where import Neovim import Neovim.API.String@@ -23,11 +22,12 @@ _ <- vim_command "sign define GhcidWarn text=>> texthl=Search" _ <- vim_command "sign define GhcidErr text=!! texthl=ErrorMsg" env <- initGhcidEnv- wrapPlugin Plugin- { environment = env- , exports =- [ $(command' 'ghcidStart) ["async", "!"]- , $(command' 'ghcidStop) ["async"]- , $(command' 'ghcidRestart) ["async"]- ]- }+ wrapPlugin+ Plugin+ { environment = env+ , exports =+ [ $(command' 'ghcidStart) ["async", "!"]+ , $(command' 'ghcidStop) ["async"]+ , $(command' 'ghcidRestart) ["async"]+ ]+ }
Neovim/Ghcid/Plugin.hs view
@@ -1,4 +1,5 @@-{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveGeneric #-}+ {- | Module : Neovim.Ghcid.Plugin Description : Ghcid quickfix integration plugin@@ -8,235 +9,224 @@ Maintainer : woozletoff@gmail.com Stability : experimental Portability : GHC- -}-module Neovim.Ghcid.Plugin- where+module Neovim.Ghcid.Plugin where -import Data.Yaml-import GHC.Generics-import Neovim-import Neovim.API.String-import Neovim.BuildTool-import Neovim.Quickfix as Q-import Neovim.User.Choice (yesOrNo)-import Neovim.User.Input+import Data.Yaml+import GHC.Generics+import Neovim+import Neovim.API.String+import Neovim.BuildTool+import Neovim.Quickfix as Q+import Neovim.User.Choice (yesOrNo)+import Neovim.User.Input -import Language.Haskell.Ghcid as Ghcid+import Language.Haskell.Ghcid as Ghcid -import Control.Monad-import Control.Monad.IO.Class-import Control.Monad.Trans.Maybe+import Control.Monad+import Control.Monad.IO.Class+import Control.Monad.Trans.Maybe import qualified Control.Monad.Trans.Resource as Resource-import qualified Data.ByteString as BS-import Data.Either (rights)-import Data.List (groupBy, sort)-import Data.Map.Strict (Map)-import qualified Data.Map.Strict as Map-import Data.Maybe (mapMaybe)-import System.FilePath-import UnliftIO.Exception (SomeException (..), catch)-import UnliftIO.STM-+import qualified Data.ByteString as BS+import Data.Either (rights)+import Data.List (groupBy, sort)+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map+import Data.Maybe (mapMaybe)+import System.FilePath+import UnliftIO.Exception (SomeException (..), catch)+import UnliftIO.STM -- | Simple data type containing a few information on how to start ghcid. data ProjectSettings = ProjectSettings- { rootDir :: FilePath- -- ^ Project directory from which ghcid can be started successfully.- --- , cmd :: String- -- ^ Command to start a ghci session (usually @cabal repl@ or- -- @stack ghci@).- }+ { -- | Project directory from which ghcid can be started successfully.+ rootDir :: FilePath+ , -- | Command to start a ghci session (usually @cabal repl@ or+ -- @stack ghci@).+ cmd :: String+ } deriving (Eq, Ord, Show, Generic) - instance ToJSON ProjectSettings - instance FromJSON ProjectSettings - data GhcidEnv = GhcidEnv- { startedSessions :: TVar (Map FilePath (Ghci, Neovim GhcidEnv ()))- -- ^ A map from the root directory (see 'rootDir') to a 'Ghci' session and a- -- release function which unregisters some autocmds and stops the ghci- -- session.-- , quickfixItems :: TVar [QuickfixListItem String]+ { -- | A map from the root directory (see 'rootDir') to a 'Ghci' session and a+ -- release function which unregisters some autocmds and stops the ghci+ -- session.+ startedSessions :: TVar (Map FilePath (Ghci, Neovim GhcidEnv ()))+ , quickfixItems :: TVar [QuickfixListItem String] } - initGhcidEnv :: MonadIO m => m GhcidEnv initGhcidEnv = GhcidEnv <$> newTVarIO mempty <*> newTVarIO mempty -modifyStartedSessions :: (Map FilePath (Ghci, Neovim GhcidEnv ())- -> Map FilePath (Ghci, Neovim GhcidEnv ()))- -> Neovim GhcidEnv ()+modifyStartedSessions ::+ ( Map FilePath (Ghci, Neovim GhcidEnv ()) ->+ Map FilePath (Ghci, Neovim GhcidEnv ())+ ) ->+ Neovim GhcidEnv () modifyStartedSessions f = do atomically . flip modifyTVar' f =<< asks startedSessions +{- | Start or update a ghcid session. --- | Start or update a ghcid session.------ This will call 'determineProjectSettings' and ask you to confirm or overwrite--- its proposed settings. If you prepend a bang, it acts as if you have--- confirmed all settings.+ This will call 'determineProjectSettings' and ask you to confirm or overwrite+ its proposed settings. If you prepend a bang, it acts as if you have+ confirmed all settings.+-} ghcidStart :: CommandArguments -> Neovim GhcidEnv () ghcidStart copts = do currentBufferPath <- fromObjectUnsafe <$> vim_call_function "expand" [ObjectBinary "%:p:h"] liftIO (determineProjectSettings' currentBufferPath) >>= \case- Nothing -> void $- yesOrNo "Could not determine project settings. This plugin needs a project with a .cabal file to work."+ Nothing ->+ void $+ yesOrNo "Could not determine project settings. This plugin needs a project with a .cabal file to work." Just s -> case bang copts of Just True -> startOrReload s- _ -> do- d <- askForDirectory+ d <-+ askForDirectory "Specify directory from which ghcid should be started." (Just (rootDir s))- c <- askForString+ c <-+ askForString "Specify the command to execute (e.g. \"ghci\")." (Just (cmd s)) let s' = ProjectSettings d c- whenM (yesOrNo "Save settings to file?") .- liftIO . BS.writeFile (d </> "ghcid.yaml") $ encode s'+ whenM (yesOrNo "Save settings to file?")+ . liftIO+ . BS.writeFile (d </> "ghcid.yaml")+ $ encode s' startOrReload s ---- | Start a new ghcid session or reload the modules to update the quickfix--- list.+{- | Start a new ghcid session or reload the modules to update the quickfix+ list.+-} startOrReload :: ProjectSettings -> Neovim GhcidEnv () startOrReload s@(ProjectSettings d c) = do sessions <- atomically . readTVar =<< asks startedSessions case Map.lookup d sessions of Nothing -> do- (g, ls) <- liftIO (startGhci c (Just d) (\_ _ -> return ()))- `catch` \(SomeException e) -> err . pretty $ "Failed to start ghcid session: " <> show e+ (g, ls) <-+ liftIO (startGhci c (Just d) (\_ _ -> return ()))+ `catch` \(SomeException e) -> err . pretty $ "Failed to start ghcid session: " <> show e applyQuickfixActions $ loadToQuickfix ls void $ vim_command "cwindow"- ra <- addAutocmd "BufWritePost" Sync def (startOrReload s) >>= \case- Nothing ->- return $ return ()-- Just (Left a) ->- return a-- Just (Right rk) ->- return $ Resource.release rk-- modifyStartedSessions $ Map.insert d (g,ra >> liftIO (stopGhci g))+ addAutocmd "BufWritePost" Sync def (startOrReload s) + modifyStartedSessions $ Map.insert d (g, liftIO (stopGhci g)) Just (ghci, _) -> do applyQuickfixActions =<< loadToQuickfix <$> liftIO (reload ghci) void $ vim_command "cwindow" - applyQuickfixActions :: [QuickfixListItem String] -> Neovim GhcidEnv () applyQuickfixActions qs = do qfItems <- asks quickfixItems fqs <- (nub' . rights . map bufOrFile) <$> atomically (readTVar qfItems) atomically $ modifyTVar' qfItems (const qs)- forM_ fqs $ \f -> void . vim_command $ "sign unplace * file=" <> f+ forM_ fqs $ \f -> void $ vim_command $ "sign unplace * file=" <> f setqflist qs Replace placeSigns qs where nub' = map head . groupBy (==) . sort - placeSigns :: [QuickfixListItem String] -> Neovim env ()-placeSigns qs = forM_ (zip [(1::Integer)..] qs) $ \(i, q) -> case (lnumOrPattern q, bufOrFile q) of+placeSigns qs = forM_ (zip [(1 :: Integer) ..] qs) $ \(i, q) -> case (lnumOrPattern q, bufOrFile q) of (Right _, _) -> -- Patterns not handled as they are not produced return ()- (_, Left _) -> -- Buffer type not handled because i don't know how to pass that here -- and it is not produced. return ()- (Left lnum, Right f) -> do let signType = case errorType q of- Q.Error -> "GhcidErr"+ Q.Error -> "GhcidErr" Q.Warning -> "GhcidWarn" -- TODO What if the file name contains spaces?- void . vim_command $ unwords- [ "sign place", show i, "line=" <> show lnum- , "name=" <> signType, "file=" <> f- ]+ void $+ vim_command $+ unwords+ [ "sign place"+ , show i+ , "line=" <> show lnum+ , "name=" <> signType+ , "file=" <> f+ ] -- | Stop a ghcid session associated to the currently active buffer. ghcidStop :: CommandArguments -> Neovim GhcidEnv () ghcidStop _ = do d <- fromObjectUnsafe <$> vim_call_function "expand" [ObjectBinary "%:p:h"]- sessions <- atomically .readTVar =<< asks startedSessions+ sessions <- atomically . readTVar =<< asks startedSessions case Map.lookupLE d sessions of Nothing -> return ()- Just (p,(_, releaseAction)) -> do+ Just (p, (_, releaseAction)) -> do modifyStartedSessions $ Map.delete p releaseAction - -- | Same as @:GhcidStop@ followed by @:GhcidStart!@. Note the bang! ghcidRestart :: CommandArguments -> Neovim GhcidEnv () ghcidRestart _ = do ghcidStop def- ghcidStart def { bang = Just True }-+ ghcidStart def{bang = Just True} loadToQuickfix :: [Load] -> [QuickfixListItem String] loadToQuickfix = dropWarningsIfErrorsArePresent . mapMaybe f where f m@Message{} =- Just $ (quickfixListItem- ((Right . loadFile) m)- ((Left . fst . loadFilePos) m))- { col = VisualColumn . snd $ loadFilePos m- , Q.text = (unlines . loadMessage) m- , errorType = case loadSeverity m of- Ghcid.Warning -> Q.Warning- _ -> Q.Error- }+ Just $+ ( quickfixListItem+ ((Right . loadFile) m)+ ((Left . fst . loadFilePos) m)+ )+ { col = VisualColumn . snd $ loadFilePos m+ , Q.text = (unlines . loadMessage) m+ , errorType = case loadSeverity m of+ Ghcid.Warning -> Q.Warning+ _ -> Q.Error+ } f _ = Nothing dropWarningsIfErrorsArePresent xs = case filter ((== Q.Error) . errorType) xs of- [] -> xs+ [] -> xs xs' -> xs' - maybePluginConfig :: MonadIO io => Directory -> io (Maybe BuildTool)-maybePluginConfig d = fmap (const Custom)- <$> mkFile (Just d) "ghcid.yaml"+maybePluginConfig d =+ fmap (const Custom)+ <$> mkFile (Just d) "ghcid.yaml" --- | Determine project settings for a directory.------ This will traverse through all parent directories and search for a hint on--- how to start the ghcid background process. The following configurations will--- be tried in this order:------ * A @ghcid.yaml@ file which can be created with the @GhcidStart@ command--- * A @stack.yaml@ file--- * A @cabal.sandbox.config@ file--- * A @\*.cabal@ file------ Note that 'ghcidStart' prompts for confirmation unless you prepend a bang.--- So, if you want to use your preferred settings, simply save them to the--- @ghcid.yaml@ file and you're done.+{- | Determine project settings for a directory.++ This will traverse through all parent directories and search for a hint on+ how to start the ghcid background process. The following configurations will+ be tried in this order:++ * A @ghcid.yaml@ file which can be created with the @GhcidStart@ command+ * A @stack.yaml@ file+ * A @cabal.sandbox.config@ file+ * A @\*.cabal@ file++ Note that 'ghcidStart' prompts for confirmation unless you prepend a bang.+ So, if you want to use your preferred settings, simply save them to the+ @ghcid.yaml@ file and you're done.+-} determineProjectSettings' :: FilePath -> IO (Maybe ProjectSettings) determineProjectSettings' dir = runMaybeT $ do ds <- MaybeT $ fmap thisAndParentDirectories <$> mkDirectory dir buildTool <- MaybeT $ determineProjectSettings (maybePluginConfig : defaultProjectIdentifiers) ds case buildTool of- (Stack, d) -> return $ ProjectSettings (getDirectory d) "stack ghci"- (Cabal _, d) -> return $ ProjectSettings (getDirectory d) "cabal repl"- (Custom, d) -> do- f <- MaybeT $ mkFile (Just d) "ghcid.yaml"- MaybeT $ decodeThrow <$> BS.readFile (getFile f)- _ -> MaybeT $ return Nothing--+ (Stack, d) -> return $ ProjectSettings (getDirectory d) "stack ghci"+ (Cabal _, d) -> return $ ProjectSettings (getDirectory d) "cabal repl"+ (Custom, d) -> do+ f <- MaybeT $ mkFile (Just d) "ghcid.yaml"+ MaybeT $ decodeThrow <$> BS.readFile (getFile f)+ _ -> MaybeT $ return Nothing
README.md view
@@ -4,22 +4,21 @@ This plugin fills the quickfix list with location of compiler errors and warnings. Warnings are only added to the quickfix list if no errors are present. -# Installation+# Disclaimer -Add the plugin to your `nvim-hs` config file.+I don't know if anyone uses this plugin, but I (saep) have not been using it for years. I usually compile it and test it shortly if it stops compiling on stackage, but that's about it. It can still be a useful template to write your own plugin as it is as complicated as it gets for a plugin. -Sample configuration file:+If you want to have a development environment to program haskell in, try [haskell-language-server](https://github.com/haskell/haskell-language-server) which handles different compiler versions and has far more features. -```haskell+# Installation -import Neovim+You need [stack](https://github.com/commercialhaskell/stack) for the automatic compilation and starting of the plugin to work. -import qualified Neovim.Ghcid as Ghcid+Then add the plugin and its dependency to your `neovim` config file (Example uses [vim-plug](https://github.com/junegunn/vim-plug)): -main :: IO ()-main = neovim defaultConfig- { plugins = defaultPlugins defaultConfig ++ [ Ghcid.plugin ]- }+```viml+Plug 'neovimhaskell/nvim-hs.vim'+Plug 'saep/nvim-hs-ghcid' ``` # Usage
Setup.hs view
@@ -1,2 +1,3 @@ import Distribution.Simple+ main = defaultMain
executable/Main.hs view
@@ -1,7 +1,7 @@ module Main where -import Neovim+import Neovim import qualified Neovim.Ghcid as Ghcid main :: IO ()-main = neovim defaultConfig { plugins = [ Ghcid.plugin ] }+main = neovim defaultConfig{plugins = [Ghcid.plugin]}
nvim-hs-ghcid.cabal view
@@ -1,5 +1,5 @@ name: nvim-hs-ghcid-version: 2.0.0.0+version: 2.0.1.0 synopsis: Neovim plugin that runs ghcid to update the quickfix list description: This plugin uses the nvim-hs plugin backend for neovim and fills the quickfix list on file-saves with the errors and