hdevtools 0.1.2.2 → 0.1.3.0
raw patch · 10 files changed
+307/−73 lines, 10 files
Files
- CHANGELOG.md +6/−0
- LICENSE +1/−1
- README.md +230/−0
- hdevtools.cabal +15/−13
- src/Cabal.hs +1/−2
- src/CommandLoop.hs +13/−7
- src/Main.hs +6/−3
- src/Server.hs +12/−9
- src/Stack.hs +15/−32
- src/Types.hs +8/−6
CHANGELOG.md view
@@ -1,5 +1,11 @@ # Changelog +## 0.1.3.0 - 2016-02-29++ * Improved performance in stack projects: The stack configuration is+ updated only when the passed path implies a different `stack.yaml`.+ Performance improved substantially from roughly ~1s to ~0.3s.+ ## 0.1.2.2 - 2016-01-11 * Added type checking support for tests and benchmarks in stack projects.
LICENSE view
@@ -1,4 +1,4 @@-Copyright (C) 2015 The hdevtools Authors (see AUTHORS file)+Copyright (C) 2016 The hdevtools Authors (see AUTHORS file) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal
+ README.md view
@@ -0,0 +1,230 @@+hdevtools+=========++Persistent GHC powered background server for FAST Haskell development tools+++About+-----++`hdevtools` is a backend for text editor plugins, to allow for things such as+syntax and type checking of Haskell code, and retrieving type information, all+directly from within your text editor.++The advantage that `hdevtools` has over competitors, is that it runs silently+in a persistent background process, and therefore is able to keeps all of your+Haskell modules and dependent libraries loaded in memory. This way, when you+change only a single source file, only it needs to be reloaded and rechecked,+instead of having to reload everything.++This makes `hdevtools` very fast for checking syntax and type errors (runs just+as fast as the `:reload` command in GHCi).++In fact, syntax and type checking is so fast, that you can safely enable auto+checking on every save. Even for huge projects, checking is nearly instant.++Once you start using `hdevtools` and you get used to having your errors shown+to you instantly (without having to switch back and forth between GHCi and your+editor), and shown directly on your code, in your editor (without having to+wait forever for GHC to run) you will wonder how you ever lived without it.++In addition to checking Haskell source code for errors, `hdevtools` has tools+for getting info about identifiers, and getting type information for snippets+of code.++Text Editor Integration+-----------------------++`hdevtools` is designed to be integrated into text editors. The list of current+editor plugins that supply this integration is below.++But before you do anything, you must first install `hdevtools` itself. The+easiest way is from [Stackage][14] using [stack][15]:+```+$ stack install hdevtools+```++**Note:** As stack manages matching GHC versions, the appropriate compiler has+to be available to `hdevtools`. Thus hdevtools has to be started with `stack exec`,+for example using the alias:+```+$ alias hdevtools="stack exec --no-ghc-package-path hdevtools --"+```++Alternatively one can install from [Hackage][1] via cabal install:+```+$ cabal install hdevtools+```++Then you should install one or more of the following editor plugins:++### Vim - [Syntastic][2] ###++[Syntastic][2] is a popular syntax checking plugin for Vim, and is the+recommended Vim integration of `hdevtools` syntax and type checking. Recent+versions of Syntastic(since Sep. 2012) have builtin support for `hdevtools`.++Simply install `hdevtools` (as above) and [Syntastic][2], and it will+automatically check your Haskell files.++[Syntastic][2] will respect the `g:hdevtools_options` variable (the same one as+used by [vim-hdevtools][3], see below). See the section "Specifying GHC+Options" below for details how to use it.++### Vim - [vim-hdevtools][3] ###++In addition to Syntastic, it is recommended that you also use+[`vim-hdevtools`][3] for additional functionality.++[`vim-hdevtools`][3] offers integration with the rest of the `hdevtools` tools,+including retrieving info about the identifier under the cursor, and getting+the type of the code under the cursor. Refer to its documentation for more+details.++### Emacs - [flycheck][5] ###++[Flycheck][5] is a modern batteries-included+syntax checker for Emacs, and there is a [flycheck-hdevtools][6] checker available.++### Atom - [linter][8] ###++There are *two* packages for the [Atom](https://atom.io) editor:+++ [linter-hdevtools][8] quickly finds and underlines type errors in Haskell files,++ [hover-tooltips-hdevtools][9] displays the types of identifiers under the mouse.++### Sublime - [SublimeLinter][10]++[SublimeLinter][10] is a plugin for Sublime Text 3 that provides a framework+for linting code. The [SublimeLinter-contrib-hdevtools][11] plugin uses+`hdevtools` to typecheck Haskell code.++### Manual Editor Integration for any Editor ###++Most editors allow you to run a `make` command, and will then parse the output+for errors and show line numbers, allowing you to jump between errors.++The `hdevtools check` command is suitable for such usage.++For example, in Vim something like this will work:++ :let &makeprg='hdevtools check %'++(Vim will replace the `%` character with the name of the current file). Then+you can run++ :make++And Vim will invoke `hdevtools` to check the current file for errors, and then+show a list of them and allow jumping to them.++See the "Command Line Usage" section below for more information.++Command Line Usage+------------------++Note: When using one of the above editor plugins, you don't really need to know+this.++### Available Commands and Help ###++For the list of commands available, run:++ $ hdevtools --help++To get help for a specific command, run:++ $ hdevtools [COMMAND] --help++For example:++ $ hdevtools check --help++### The `hdevtools` background process ###++The first time `hdevtools` runs a command, it will spawn a background process+that will remain running forever. You can check the status of this background+process by running:++ $ hdevtools --status++You can shutdown the background process by running:++ $ hdevtools --stop-server++Communication with the background process is done through a unix socket file.+The default name is `.hdevtools.sock`, in the current directory. This allows+you to use `hdevtools` with multiple projects simultaneously, without the+background processes getting in the way of each other.++You can use a different socket file name with the `--socket` option, which+should be used for each invocation of `hdevtools`. Remember that when telling+`hdevtools` to check a Haskell file, paths are relative to the path of the+background process, not your current directory. This can cause problems, and+therefore it is recommended that you leave the socket file as the default, and+always run `hdevtools` from the same directory.++You can specify the path to a target file with the `--path` option. This is+useful for integration with IDEs that submit a *copy* of the original source+file (in a temporary directory) to `hdevtools` making it impossible to extract+the `.cabal` information for the file's project. In such cases, you can run as:++ $ hdevtools check -p /path/to/file.hs /tmp/file.hs++and `hdevtools` will use the given path to obtain the `.cabal` information.+++### Specifying GHC Options ###++For most non-trivial projects, you will need to tell `hdevtools` about+additional GHC options that your project requires.++All `hdevtools` commands accept a `-g` flag for this purpose.++For example:++* Your project source code is in the directory `src`+* You want to use the GHC option `-Wall`+* You want to hide the package `transformers` to prevent conflicts++Invoke `hdevtools` with something like this:++ $ hdevtools check -g -isrc -g -Wall -g -hide-package -g transformers Foo.hs++Notice that a `-g` flag is inserted before each GHC option. Don't try to string+multiple GHC options together after a single `-g` flag:++This won't work:++ $ hdevtools check -g '-hide-package transformers' Foo.hs++The Vim plugins allow setting GHC options in the `g:hdevtools_options`+variable. For example, for the above project, put the following in your+`.vimrc`:++ let g:hdevtools_options = '-g -isrc -g -Wall -g -hide-package -g transformers'++In general, you will need to pass to `hdevtools` the same GHC options that you+would pass to GHCi.++Credits+-------++* `hdevtools` was inspired by [ghcmod][4].+* development moved here from [bitc/hdevtools][12] and [schell/hdevtools][13].++[1]: http://hackage.haskell.org/package/hdevtools+[2]: https://github.com/scrooloose/syntastic+[3]: https://github.com/bitc/vim-hdevtools+[4]: http://www.mew.org/~kazu/proj/ghc-mod/en/+[5]: https://github.com/flycheck/flycheck+[6]: https://github.com/flycheck/flycheck-hdevtools+[7]: https://atom.io+[8]: https://atom.io/packages/linter-hdevtools+[9]: https://atom.io/packages/hover-tooltips-hdevtools+[10]: sublimelinter.com+[11]: https://packagecontrol.io/packages/SublimeLinter-contrib-hdevtools+[12]: https://github.com/bitc/hdevtools+[13]: https://github.com/schell/hdevtools+[14]: https://www.stackage.org/package/hdevtools+[15]: haskellstack.org
hdevtools.cabal view
@@ -1,6 +1,17 @@ name: hdevtools-version: 0.1.2.2+version: 0.1.3.0 synopsis: Persistent GHC powered background server for FAST haskell development tools+license: MIT+license-file: LICENSE+author: Bit Connor+maintainer: Sebastian Nagel <sebastian.nagel@ncoding.at>, + Ranjit Jhala <jhala@cs.ucsd.edu>+copyright: See AUTHORS file+category: Development+homepage: https://github.com/hdevtools/hdevtools/+bug-reports: https://github.com/hdevtools/hdevtools/issues/+build-type: Simple+cabal-version: >=1.8 description: 'hdevtools' is a backend for text editor plugins, to allow for things such as syntax and type checking of Haskell code, and retrieving type information, all@@ -27,18 +38,9 @@ for getting info about identifiers, and getting type information for snippets of code. -license: MIT-license-file: LICENSE-author: Bit Connor-maintainer: Sebastian Nagel <sebastian.nagel@ncoding.at>, - Ranjit Jhala <jhala@cs.ucsd.edu>-copyright: See AUTHORS file-category: Development-homepage: https://github.com/hdevtools/hdevtools/-bug-reports: https://github.com/hdevtools/hdevtools/issues/-build-type: Simple-cabal-version: >=1.8-extra-source-files: CHANGELOG.md+extra-source-files: + CHANGELOG.md+ README.md source-repository head type: git
src/Cabal.hs view
@@ -168,7 +168,6 @@ , ghcOptPackages = overNubListR (filter (\(_, pkgId, _) -> Just (pkgName pkgId) /= mbLibName)) $ (ghcOptPackages ghcOpts') , ghcOptSourcePath = overNubListR (map (baseDir </>)) (ghcOptSourcePath ghcOpts') }- putStrLn "configuring" (ghcInfo,_,_) <- GHC.configure silent Nothing Nothing defaultProgramConfiguration return $ Right $ renderGhcOptions ghcInfo ghcOpts@@ -219,7 +218,7 @@ where pkgDbKey = "package-db:" parse = head . filter (pkgDbKey `isPrefixOf`) . lines- extractValue = fst . break isSpace . dropWhile isSpace . drop (length pkgDbKey)+ extractValue = fst . break (`elem` "\n\r") . dropWhile isSpace . drop (length pkgDbKey) findCabalFile :: FilePath -> IO (Maybe FilePath)
src/CommandLoop.hs view
@@ -3,7 +3,7 @@ ( newCommandLoopState , Config(..) , CabalConfig(..)- , newConfig+ , updateConfig , startCommandLoop ) where @@ -49,7 +49,7 @@ , cabalConfigOpts :: [String] , cabalConfigLastUpdatedAt :: EpochTime }- deriving Eq+ deriving (Eq, Show) mkCabalConfig :: FilePath -> [String] -> IO CabalConfig mkCabalConfig path opts = do@@ -64,17 +64,23 @@ , configCabal :: Maybe CabalConfig , configStack :: Maybe StackConfig }- deriving Eq+ deriving (Eq, Show) -newConfig :: CommandExtra -> IO Config-newConfig cmdExtra = do- mbCabalConfig <- traverse (\path -> mkCabalConfig path (ceCabalOptions cmdExtra)) $ ceCabalConfig cmdExtra- mbStackConfig <- getStackConfig cmdExtra+updateConfig :: Maybe Config -> CommandExtra -> IO Config+updateConfig mConfig cmdExtra = do+ mbCabalConfig <- traverse (\path -> mkCabalConfig path (ceCabalOptions cmdExtra)) $+ ceCabalFilePath cmdExtra + mbStackConfig <- if (stackYaml <$> msc) == (ceStackYamlPath cmdExtra)+ then return msc+ else getStackConfig cmdExtra+ return $ Config { configGhcOpts = "-O0" : ceGhcOptions cmdExtra , configCabal = mbCabalConfig , configStack = mbStackConfig }+ where+ msc = mConfig >>= configStack type CommandObj = (Command, Config)
src/Main.hs view
@@ -17,6 +17,7 @@ import CommandArgs import Daemonize (daemonize) import Server (startServer, createListenSocket)+import Stack (findStackYaml) import Types (Command(..), CommandExtra(..), emptyCommandExtra) absoluteFilePath :: FilePath -> IO FilePath@@ -56,11 +57,13 @@ let argPath = pathArg args dir <- maybe getCurrentDirectory (return . takeDirectory) argPath mCabalFile <- findCabalFile dir >>= traverse absoluteFilePath+ mStackYaml <- findStackYaml dir let extra = emptyCommandExtra- { ceGhcOptions = ghcOpts args- , ceCabalConfig = mCabalFile- , cePath = argPath+ { cePath = argPath+ , ceGhcOptions = ghcOpts args+ , ceCabalFilePath = mCabalFile , ceCabalOptions = cabalOpts args+ , ceStackYamlPath = mStackYaml } let defaultSocketPath = maybe "" takeDirectory mCabalFile </> defaultSocketFile let sock = fromMaybe defaultSocketPath $ socket args
src/Server.hs view
@@ -10,7 +10,7 @@ import System.IO (Handle, hClose, hFlush, hGetLine, hPutStrLn) import System.IO.Error (ioeGetErrorType, isAlreadyInUseError, isDoesNotExistError) -import CommandLoop (newCommandLoopState, Config, newConfig, startCommandLoop)+import CommandLoop (newCommandLoopState, Config, updateConfig, startCommandLoop) import Types (ClientDirective(..), Command, emptyCommandExtra, ServerDirective(..)) import Util (readMaybe) @@ -38,8 +38,9 @@ go sock = do state <- newCommandLoopState currentClient <- newIORef Nothing- config <- newConfig emptyCommandExtra- startCommandLoop state (clientSend currentClient) (getNextCommand currentClient sock) config Nothing+ configRef <- newIORef Nothing+ config <- updateConfig Nothing emptyCommandExtra+ startCommandLoop state (clientSend currentClient) (getNextCommand currentClient sock configRef) config Nothing removeSocketFile :: IO () removeSocketFile = do@@ -60,8 +61,8 @@ ignoreEPipe = handleJust (guard . isEPipe) (const $ return ()) isEPipe = (==ResourceVanished) . ioeGetErrorType -getNextCommand :: IORef (Maybe Handle) -> Socket -> IO (Maybe (Command, Config))-getNextCommand currentClient sock = do+getNextCommand :: IORef (Maybe Handle) -> Socket -> IORef (Maybe Config) -> IO (Maybe (Command, Config))+getNextCommand currentClient sock config = do checkCurrent <- readIORef currentClient case checkCurrent of Just h -> hClose h@@ -74,16 +75,18 @@ Nothing -> do clientSend currentClient $ ClientUnexpectedError $ "The client sent an invalid message to the server: " ++ show msg- getNextCommand currentClient sock+ getNextCommand currentClient sock config Just (SrvCommand cmd cmdExtra) -> do- config <- newConfig cmdExtra- return $ Just (cmd, config)+ curConfig <- readIORef config+ config' <- updateConfig curConfig cmdExtra+ writeIORef config (Just config')+ return $ Just (cmd, config') Just SrvStatus -> do mapM_ (clientSend currentClient) $ [ ClientStdout "Server is running." , ClientExit ExitSuccess ]- getNextCommand currentClient sock+ getNextCommand currentClient sock config Just SrvExit -> do mapM_ (clientSend currentClient) $ [ ClientStdout "Shutting down server."
src/Stack.hs view
@@ -2,7 +2,7 @@ module Stack ( -- * The bits of information needed from `stack` StackConfig (..)- -- * Run `stack exec` to compute @StackConfig@+ , findStackYaml , getStackConfig ) where @@ -21,45 +21,28 @@ import Control.Exception import Types ---- | This module adds support for `stack`, as follows:--- 1. Figure out if the target-file is in a stack project,--- 2. If `stack` in available in PATH, run `stack exec` to extract--- `StackConfig`--- 3. The `StackConfig` is used to suitably alter the cabal ConfigFlags in--- Cabal.hs-- -- TODO: Move into Types?-data StackConfig = StackConfig { stackDist :: FilePath+data StackConfig = StackConfig { stackYaml :: FilePath+ , stackDist :: FilePath , stackDbs :: [FilePath] } deriving (Eq, Show) ----------------------------------------------------------------------------------getStackConfig :: CommandExtra -> IO (Maybe StackConfig)----------------------------------------------------------------------------------getStackConfig ce = case cePath ce of- Nothing -> return Nothing- Just p -> getStackConfig' p--getStackConfig' :: FilePath -> IO (Maybe StackConfig)-getStackConfig' p = do- mbYaml <- getStackYaml p- case mbYaml of- Nothing -> return Nothing- Just _ -> do mdbs <- getStackDbs p- mdst <- getStackDist p- return $ StackConfig <$> mdst <*> mdbs-----------------------------------------------------------------------------------getStackYaml :: FilePath -> IO (Maybe FilePath)----------------------------------------------------------------------------------getStackYaml p = listToMaybe <$> filterM doesFileExist paths+-- | Search for a @stack.yaml@ upwards in given file path tree.+findStackYaml :: FilePath -> IO (Maybe FilePath)+findStackYaml p = listToMaybe <$> filterM doesFileExist paths where paths = [ d </> "stack.yaml" | d <- pathsToRoot dir] dir = takeDirectory p +-- | Run @stack path@ to compute @StackConfig@+getStackConfig :: CommandExtra -> IO (Maybe StackConfig)+getStackConfig CommandExtra { ceStackYamlPath = Nothing } = return Nothing+getStackConfig CommandExtra { ceStackYamlPath = Just p } = do+ dbs <- getStackDbs p+ dist <- getStackDist p+ return $ StackConfig p <$> dist <*> dbs+ pathsToRoot :: FilePath -> [FilePath] pathsToRoot p | p == parent = [p]@@ -84,7 +67,7 @@ Just pp -> Just <$> extractDbs pp Nothing -> return Nothing where- cmd = "stack --verbosity quiet exec printenv GHC_PACKAGE_PATH"+ cmd = "stack path --ghc-package-path" extractDbs :: String -> IO [FilePath] extractDbs = filterM doesDirectoryExist . stringPaths
src/Types.hs view
@@ -9,17 +9,19 @@ import System.Exit (ExitCode) data CommandExtra = CommandExtra- { ceGhcOptions :: [String]- , ceCabalConfig :: Maybe FilePath- , cePath :: Maybe FilePath+ { cePath :: Maybe FilePath+ , ceGhcOptions :: [String]+ , ceCabalFilePath :: Maybe FilePath , ceCabalOptions :: [String]+ , ceStackYamlPath :: Maybe FilePath } deriving (Read, Show) emptyCommandExtra :: CommandExtra-emptyCommandExtra = CommandExtra { ceGhcOptions = []- , ceCabalConfig = Nothing- , cePath = Nothing+emptyCommandExtra = CommandExtra { cePath = Nothing+ , ceGhcOptions = []+ , ceCabalFilePath = Nothing , ceCabalOptions = []+ , ceStackYamlPath = Nothing } data ServerDirective