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 +13/−0
- Neovim/Ghcid.hs +35/−0
- Neovim/Ghcid/Plugin.hs +249/−0
- README.md +36/−0
- Setup.hs +2/−0
- nvim-hs-ghcid.cabal +52/−0
+ 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