packages feed

nvim-hs-ghcid (empty) → 0.1.0

raw patch · 6 files changed

+387/−0 lines, 6 filesdep +basedep +bytestringdep +containerssetup-changed

Dependencies added: base, bytestring, containers, directory, filepath, ghcid, nvim-hs, nvim-hs-contrib, resourcet, yaml

Files

+ LICENSE view
@@ -0,0 +1,13 @@+Copyright 2015 Sebastian Witte <woozletoff@gmail.com>++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++    http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.
+ Neovim/Ghcid.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{- |+Module      :  Neovim.Ghcid+Description :  Ghcid plugin+Copyright   :  (c) Sebastian Witte+License     :  Apache-2.0++Maintainer  :  woozletoff@gmail.com+Stability   :  experimental+Portability :  GHC++-}+module Neovim.Ghcid+    where++import Neovim++import Neovim.Ghcid.Plugin+import qualified Data.Map as Map++plugin :: Neovim (StartupConfig NeovimConfig) () NeovimPlugin+plugin = do+    _ <- vim_command "sign define GhcidWarn text=>> texthl=Search"+    _ <- vim_command "sign define GhcidErr text=!! texthl=ErrorMsg"+    wrapPlugin Plugin+        { exports = []+        , statefulExports =+            [ ((), GhcidState Map.empty [],+                [ $(command' 'ghcidStart) ["async", "!"]+                , $(command' 'ghcidStop) ["async"]+                , $(command' 'ghcidRestart) ["async"]+                ])+            ]+        }
+ Neovim/Ghcid/Plugin.hs view
@@ -0,0 +1,249 @@+{-# LANGUAGE DeriveGeneric     #-}+{-# LANGUAGE LambdaCase        #-}+{-# LANGUAGE OverloadedStrings #-}+{- |+Module      :  Neovim.Ghcid.Plugin+Description :  Ghcid quickfix integration plugin+Copyright   :  (c) Sebastian Witte+License     :  Apache-2.0++Maintainer  :  woozletoff@gmail.com+Stability   :  experimental+Portability :  GHC++-}+module Neovim.Ghcid.Plugin+    where++import           Data.Yaml+import           GHC.Generics+import           Neovim+import           Neovim.Quickfix              as Q+import           Neovim.User.Choice           (yesOrNo)+import           Neovim.User.Input++import           Language.Haskell.Ghcid       as Ghcid++import           Control.Monad+import qualified Control.Monad.Trans.Resource as Resource+import qualified Data.ByteString              as BS+import           Data.Either                  (rights)+import           Data.List                    (isSuffixOf, groupBy, sort)+import           Data.Map                     (Map)+import qualified Data.Map                     as Map+import           Data.Maybe                   (mapMaybe)+import           System.Directory+import           System.FilePath+++-- | 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@).+        }+    deriving (Eq, Ord, Show, Generic)+++instance ToJSON ProjectSettings+++instance FromJSON ProjectSettings+++data GhcidState r = GhcidState+    { startedSessions :: Map FilePath (Ghci, Neovim r (GhcidState r) ())+    -- ^ 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   :: [QuickfixListItem String]+    }+++modifyStartedSessions :: (Map FilePath (Ghci, Neovim r (GhcidState r) ())+                          -> Map FilePath (Ghci, Neovim r (GhcidState r) ()))+                      -> Neovim r (GhcidState r) ()+modifyStartedSessions f = modify $ \s -> s { startedSessions = f (startedSessions s) }+++-- | 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.+ghcidStart :: CommandArguments -> Neovim r (GhcidState r) ()+ghcidStart copts = do+    currentBufferPath <- errOnInvalidResult $ 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."+        Just s -> case bang copts of+            Just True ->+                startOrReload s++            _ -> do+                d <- askForDirectory+                        "Specify directory from which ghcid should be started."+                        (Just (rootDir s))+                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'+                startOrReload s+++-- | Start a new ghcid session or reload the modules to update the quickfix+-- list.+startOrReload :: ProjectSettings -> Neovim r (GhcidState r) ()+startOrReload s@(ProjectSettings d c) = Map.lookup d <$> gets startedSessions >>= \case+    Nothing -> do+        (g, ls) <- liftIO $ startGhci c (Just d) (\_ _ -> return ())+        applyQuickfixActions $ loadToQuickfix ls+        void $ vim_command "cwindow"+        ra <- addAutocmd "BufWritePost" 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))++    Just (ghci, _) -> do+        applyQuickfixActions =<< loadToQuickfix <$> liftIO (reload ghci)+        void $ vim_command "cwindow"+++applyQuickfixActions :: [QuickfixListItem String] -> Neovim r (GhcidState r) ()+applyQuickfixActions qs = do+    fqs <- (nub' . rights . map bufOrFile) <$> gets quickfixItems+    modify $ \s -> s { quickfixItems = qs }+    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 r st ()+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.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+            ]++-- | Stop a ghcid session associated to the currently active buffer.+ghcidStop :: CommandArguments -> Neovim r (GhcidState r) ()+ghcidStop _ = do+    d <- errOnInvalidResult $ vim_call_function "expand" [ObjectBinary "%:p:h"]+    Map.lookupLE d <$> gets startedSessions >>= \case+        Nothing ->+            return ()+        Just (p,(_, releaseAction)) -> do+            modifyStartedSessions $ Map.delete p+            releaseAction+++-- | Same as @:GhcidStop@ followed by @:GhcidStart!@. Note the bang!+ghcidRestart :: CommandArguments -> Neovim r (GhcidState r) ()+ghcidRestart _ = do+    ghcidStop def+    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 = Just $ ((snd . loadFilePos) m, True)+                    , 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'+++-- | Calculate the list of all parent directories for the given directory. This+-- function also returns the initially specified directory.+allParentDirectories :: FilePath -> [FilePath]+allParentDirectories dir+    | parentDir == dir = [dir]+    | otherwise = dir : allParentDirectories parentDir+  where+    parentDir = takeDirectory dir+++-- | 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 = do+    msum <$> sequence [ customSettings, stack, plainCabal, cabalSandbox ]+  where+    searchDirectories = allParentDirectories dir++    cabalSandbox = fmap (\p -> (ProjectSettings (takeDirectory p) "cabal repl"))+        <$> findFile searchDirectories "cabal.sandbox.config"++    stack = fmap (\p -> (ProjectSettings (takeDirectory p) "stack ghci"))+        <$> findFile searchDirectories "stack.yaml"++    plainCabal = fmap msum <$> forM searchDirectories $ \d -> do+        filter (".cabal" `isSuffixOf`) <$> getDirectoryContents d >>= \case+            [x] -> do+                isDir <- doesDirectoryExist (d </> x) -- ignore directoroies like /home/user/.cabal+                return $ if isDir then Nothing else Just (ProjectSettings d "cabal repl")+            _ ->+                return Nothing++    customSettings = findFile searchDirectories "ghcid.yaml" >>= \case+        Just cfg ->+            decode <$> BS.readFile cfg++        Nothing ->+            return Nothing+
+ README.md view
@@ -0,0 +1,36 @@+# nvim-hs-ghcid++Ghcid integration plugin for [`nvim-hs`](https://github.com/neovimhaskell/nvim-hs).++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++Add the plugin to your `nvim-hs` config file.++Sample configuration file:++```haskell++import Neovim++import qualified Neovim.Ghcid as Ghcid++main :: IO ()+main = neovim defaultConfig+    { plugins = defaultPlugins defaultConfig ++ [ Ghcid.plugin ]+    }+```+# Usage++> :GhcidStart++To initialize a Ghcid session which will fill the quickfix list on errors/warnings. If you add a bang, it will not ask you for the configuration and uses the last saved configuration for the project or guesses a configuration.++> :GhcidStop ++Stop the Ghcid session for the project in which the current file resides.++> :GhcidRestart++Same as `:GhcidStop` followed by `:GhcidStart!`.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ nvim-hs-ghcid.cabal view
@@ -0,0 +1,52 @@+name:                nvim-hs-ghcid+version:             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+                     warnings that ghcid determines.+                     .+                     The only limitation for this tool is that it needs a .cabal+                     file to work.+                     .+                     This plugin provides 3 commands:+                     .+                     @:GhcidStart@ will prompt you for the configuration you+                     want to use. It should guess a reasonable option based on+                     the files present in your project directory and so you will+                     just have to press enter all the time. If you provide a+                     bang, these questions will not be asked.+                     .+                     @:GhcidStop@ stops the current ghcid process.+                     .+                     @:GhcidRestart@ combines the two previous commands.+                     .+                     Simply import the @plugin@ definition from "Neovim.Ghcid"+                     and add it to your plugin list.++homepage:            https://github.com/saep/nvim-hs-ghcid+license:             Apache-2.0+license-file:        LICENSE+author:              Sebastian Witte+maintainer:          woozletoff@gmail.com+copyright:           Sebastian Witte <woozletoff@gmail.com>+category:            Editor+build-type:          Simple+extra-source-files:  README.md+cabal-version:       >=1.10++library+  exposed-modules:     Neovim.Ghcid, Neovim.Ghcid.Plugin+  -- other-modules:+  other-extensions:    OverloadedStrings, TemplateHaskell, DeriveGeneric, LambdaCase+  build-depends:       base >=4.6 && <5+                     , nvim-hs >=0.1.0+                     , nvim-hs-contrib >=0.1.0+                     , containers >=0.5+                     , yaml+                     , ghcid >=0.6.1+                     , resourcet+                     , bytestring+                     , directory+                     , filepath+  -- hs-source-dirs:+  default-language:    Haskell2010