packages feed

proteome 0.1.0.0 → 0.2.0.0

raw patch · 42 files changed

+1424/−52 lines, 42 filesdep +HTFdep +MissingHdep +ansi-terminaldep ~nvim-hssetup-changednew-component:exe:proteome-exe

Dependencies added: HTF, MissingH, ansi-terminal, containers, data-default-class, deepseq, directory, filepath, hslogger, lens, messagepack, mtl, pretty-terminal, prettyprinter, process, proteome, resourcet, split, stm, strings, text, time, unliftio, utf8-string

Dependency ranges changed: nvim-hs

Files

README.md view
@@ -1,3 +1,9 @@ # Intro -A neovim plugin that provides project-specific configuration file loading.+A neovim plugin that provides project-specific configuration file loading and performs runtime tasks on projects.++# Install++Use the [chromatin] plugin manager with the spec `hackage:proteome`.++[chromatin]: https://github.com/tek/chromatin.nvim
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,6 @@+import Neovim++import Proteome.Plugin (plugin)++main :: IO ()+main = neovim defaultConfig {plugins = [plugin]}
+ lib/Proteome/Config.hs view
@@ -0,0 +1,72 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE RecordWildCards #-}++module Proteome.Config(+  proReadConfig,+  readConfig,+  ProjectConfig (..),+)+where++import GHC.Generics (Generic)+import Control.DeepSeq (NFData)+import qualified Data.Map as Map+import Data.Foldable (traverse_)+import Data.Map.Strict (Map)+import Neovim (NvimObject, toObject, Dictionary, Object, vim_command')+import Ribosome.Data.Ribo (Ribo)+import qualified Ribosome.Data.Ribo as Ribo (inspect)+import qualified Proteome.Data.Env as Env (mainProject)+import Proteome.Data.Project (+  Project(Project, types),+  ProjectType(ProjectType, projectType),+  ProjectName(ProjectName),+  ProjectMetadata(DirProject),+  )+import Proteome.Data.Proteome++newtype ProjectConfig =+  ProjectConfig {+    projectTypes :: Map ProjectType [FilePath]+  }+  deriving (Generic, NFData)++instance NvimObject ProjectConfig where+  toObject ProjectConfig {..} =+    (toObject :: Dictionary -> Object) . Map.fromList $+    [+      ("projectTypes", toObject projectTypes)+    ]++runtime :: FilePath -> Ribo a ()+runtime path = vim_command' $ "runtime " ++ path ++ ".vim"++runtimeConf :: FilePath -> String -> Ribo a ()+runtimeConf confDir path = runtime(confDir ++ "/" ++ path)++typeProjectConf :: FilePath -> ProjectName -> ProjectType -> Ribo a ()+typeProjectConf confDir (ProjectName name') (ProjectType tpe') = do+  runtimeConf confDir tpe'+  runtimeConf confDir $ tpe' ++ "/" ++ name'++readConfigMeta :: String -> Project -> Ribo a ()+readConfigMeta confDir (Project (DirProject name' _ tpe') _ _ _) =+  traverse_ (typeProjectConf confDir name') tpe'+readConfigMeta _ _ = return ()++readConfigProject :: String -> Project -> Ribo a ()+readConfigProject confDir project = do+  readConfigMeta confDir project+  traverse_ (runtimeConf confDir) (fmap projectType (types project))++readConfig :: String -> Project -> Ribo a ()+readConfig confDir project = do+  runtimeConf confDir "all/*"+  readConfigProject confDir project++proReadConfig :: Proteome ()+proReadConfig = do+  main <- Ribo.inspect Env.mainProject+  readConfig "project" main
+ lib/Proteome/Data/Env.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE TemplateHaskell #-}++module Proteome.Data.Env (+  Env(..),+  _mainProject,+  _projects,+  _errors,+) where++import Control.Lens (makeClassy_)+import Data.Default.Class (Default(def))+import Ribosome.Data.Errors (Errors)+import Proteome.Data.Project++data Env = Env {+  mainProject :: Project,+  projects :: [Project],+  errors :: Errors+}++makeClassy_ ''Env++instance Default Env where+  def = Env def def def
+ lib/Proteome/Data/Project.hs view
@@ -0,0 +1,86 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveAnyClass #-}++module Proteome.Data.Project(+  ProjectMetadata (..),+  Project (..),+  ProjectType (..),+  ProjectLang (..),+  ProjectRoot (..),+  ProjectName (..),+  _meta,+  _lang,+  langOrType,+) where++import GHC.Generics (Generic)+import Control.Lens (makeClassy_)+import Control.DeepSeq+import Data.Default.Class (Default(def))+import Neovim.Classes (NvimObject(..))+import Ribosome.Internal.NvimObject (deriveString)++newtype ProjectName = ProjectName String+  deriving (Eq, Show, Generic, NFData)++instance NvimObject ProjectName where+  toObject (ProjectName s) = toObject s+  fromObject = deriveString ProjectName++newtype ProjectType =+  ProjectType {+    projectType :: String+  }+  deriving (Ord, Eq, Show, Generic, NFData)++instance NvimObject ProjectType where+  toObject (ProjectType s) = toObject s+  fromObject = deriveString ProjectType++newtype ProjectLang = ProjectLang String+  deriving (Eq, Show, Generic, NFData)++instance NvimObject ProjectLang where+  toObject (ProjectLang s) = toObject s+  fromObject = deriveString ProjectLang++newtype ProjectRoot = ProjectRoot FilePath+  deriving (Eq, Show, Generic, NFData)++instance NvimObject ProjectRoot where+  toObject (ProjectRoot s) = toObject s+  fromObject = deriveString ProjectRoot++data ProjectMetadata =+  DirProject {+    name :: ProjectName,+    root :: ProjectRoot,+    tpe :: Maybe ProjectType+  }+  | VirtualProject {+    name :: ProjectName+  }+  deriving (Eq, Show)++instance Default ProjectMetadata where+  def = VirtualProject (ProjectName "main")++data Project =+  Project {+    meta :: ProjectMetadata,+    types :: [ProjectType],+    lang :: Maybe ProjectLang,+    langs :: [ProjectLang]+  }+  deriving (Eq, Show)++makeClassy_ ''Project++instance Default Project where+  def = Project def def def def++langOrType :: Maybe ProjectLang -> Maybe ProjectType -> Maybe ProjectLang+langOrType (Just lang') _ = Just lang'+langOrType Nothing (Just (ProjectType tpe')) = Just (ProjectLang tpe')+langOrType _ _ = Nothing
+ lib/Proteome/Data/ProjectSpec.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE RecordWildCards #-}++module Proteome.Data.ProjectSpec(+  ProjectSpec (..),+) where++import GHC.Generics (Generic)+import Control.DeepSeq (NFData)+import qualified Data.Map as Map+import Data.Maybe (catMaybes)+import Data.MessagePack+import Neovim.Classes (NvimObject(..), Dictionary)+import Proteome.Data.Project (ProjectName, ProjectType, ProjectLang, ProjectRoot)++data ProjectSpec =+  ProjectSpec {+    name :: ProjectName,+    root :: ProjectRoot,+    tpe :: Maybe ProjectType,+    types :: [ProjectType],+    lang :: Maybe ProjectLang,+    langs :: [ProjectLang]+  }+  deriving (Generic, NFData)++instance NvimObject ProjectSpec where+  toObject ProjectSpec {..} =+    (toObject :: Dictionary -> Object) . Map.fromList $+    [+      ("name", toObject name),+      ("root", toObject root),+      ("types", toObject types),+      ("langs", toObject langs)+    ] ++ catMaybes+    [+      tpe >>= \a -> return ("tpe", toObject a),+      lang >>= \a -> return ("lang", toObject a)+    ]
+ lib/Proteome/Data/Proteome.hs view
@@ -0,0 +1,9 @@+module Proteome.Data.Proteome(+  Proteome+) where++import UnliftIO.STM (TVar)+import Ribosome.Data.Ribo (Ribo)+import Proteome.Data.Env (Env)++type Proteome a = Ribo (TVar Env) a
lib/Proteome/Init.hs view
@@ -1,15 +1,85 @@-{-# LANGUAGE TemplateHaskell #-}--module Proteome.Init-(+module Proteome.Init(   initialize,-  env+  proteomeStage2,+  proteomeStage4, ) where -import Neovim+import Control.Monad.Reader+import Data.Default.Class (Default(def))+import System.Directory (getCurrentDirectory, makeAbsolute)+import System.FilePath (takeFileName, takeDirectory)+import System.Log.Logger (updateGlobalLogger, setLevel, Priority(ERROR))+import Control.Monad.IO.Class (MonadIO)+import Neovim.Context.Internal (Neovim)+import UnliftIO.STM (TVar, newTVarIO)+import Ribosome.Config.Settings (setting, settingE, updateSetting)+import Ribosome.Data.Ribo+import Ribosome.Data.Ribosome (Ribosome(Ribosome))+import Ribosome.Internal.IO (retypeNeovim)+import Proteome.Data.Env (Env(Env))+import Proteome.Data.Proteome+import Proteome.Data.Project (+  Project(meta),+  ProjectName(..),+  ProjectRoot(..),+  ProjectType(..),+  ProjectMetadata(DirProject),+  )+import Proteome.Project.Resolve (resolveProject)+import qualified Proteome.Settings as S -env :: Neovim startupEnv ()-env = return ()+pathData :: MonadIO m => Either String FilePath -> m (ProjectRoot, ProjectName, ProjectType)+pathData override = do+  cwd <- liftIO $ either (const getCurrentDirectory) pure override+  absMainDir <- liftIO $ makeAbsolute cwd+  return (+    ProjectRoot absMainDir,+    ProjectName $ takeFileName absMainDir,+    ProjectType $ (takeFileName . takeDirectory) absMainDir+    ) -initialize :: Neovim startupEnv ()-initialize = return ()+mainProject :: Ribo e Project+mainProject = do+  mainDir <- settingE S.mainProjectDir+  (root, name, tpe) <- pathData mainDir+  baseDirs <- setting S.projectBaseDirs+  -- typeDirs <- setting S.projectTypeDirs+  explicit <- setting S.projects+  config <- setting S.projectConfig+  return $ resolveProject baseDirs explicit config root name (Just tpe)++loadPersistedBuffers :: Project -> Neovim e ()+loadPersistedBuffers _ = return ()++updateMainType :: Maybe ProjectType -> Ribo e ()+updateMainType (Just tpe) = updateSetting S.mainType tpe+updateMainType Nothing = return ()++setProjectVars :: ProjectMetadata -> Ribo e ()+setProjectVars (DirProject name _ tpe) = do+  updateSetting S.mainName name+  updateMainType tpe+setProjectVars _ = return ()++initWithMain :: Project -> Ribo e Env+initWithMain main = do+  loadPersistedBuffers main+  setProjectVars (meta main)+  return $ Env main [] def++initialize' :: Ribo e Env+initialize' = do+  main <- mainProject+  initWithMain main++initialize :: Neovim e (TVar Env)+initialize = do+  liftIO $ updateGlobalLogger "Neovim.Plugin" (setLevel ERROR)+  env <- retypeNeovim (Ribosome "proteome") initialize'+  newTVarIO env++proteomeStage2 :: Proteome ()+proteomeStage2 = return ()++proteomeStage4 :: Proteome ()+proteomeStage4 = return ()
+ lib/Proteome/Log.hs view
@@ -0,0 +1,21 @@+module Proteome.Log(+  debug,+  info,+  debugS,+  infoS,+) where++import Ribosome.Data.Ribo (Ribo)+import qualified Ribosome.Log as R (debug, info)++debug :: String -> Ribo e ()+debug = R.debug "proteome"++info :: String -> Ribo e ()+info = R.info "proteome"++debugS :: Show a => a -> Ribo e ()+debugS = R.debug "proteome"++infoS :: Show a => a -> Ribo e ()+infoS = R.info "proteome"
lib/Proteome/Plugin.hs view
@@ -1,23 +1,36 @@ {-# LANGUAGE TemplateHaskell #-}  module Proteome.Plugin(-  plugin+  plugin, ) where +import UnliftIO.STM (TVar) import Neovim--import Proteome.Init (initialize, env)+import Ribosome.Data.Ribosome (Ribosome(Ribosome))+import Proteome.Init+import Proteome.Data.Env import Proteome.Add (proAdd, proteomePoll)+import Proteome.Config (proReadConfig)+import Proteome.Tags (proTags)+import Proteome.Save (proSave) +plugin' :: TVar Env -> Plugin (Ribosome (TVar Env))+plugin' env =+  Plugin {+    environment = Ribosome "proteome" env,+    exports = [+      $(function' 'proteomePoll) Sync,+      $(function' 'proteomeStage2) Async,+      $(function' 'proteomeStage4) Async,+      $(function' 'proAdd) Async,+      $(function' 'proSave) Async,+      $(function' 'proTags) Async,+      $(function' 'proReadConfig) Sync+    ]+  }+ plugin :: Neovim (StartupConfig NeovimConfig) NeovimPlugin plugin = do-  initialize-  wrapPlugin-    Plugin {-      environment = env,-      exports = [-        $(function' 'proteomePoll) Sync,-        $(function' 'proAdd) Async-      ]-    }+  env <- initialize+  wrapPlugin $ plugin' env
+ lib/Proteome/Project/Resolve.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE NamedFieldPuns #-}++module Proteome.Project.Resolve(+  resolveProject,+) where++import Data.List (find)+import Data.Maybe (fromMaybe)+import System.FilePath (takeDirectory)+import Ribosome.Data.Maybe (orElse)+import Proteome.Config (ProjectConfig)+import Proteome.Data.Project (+  Project(Project),+  ProjectName(..),+  ProjectRoot(..),+  ProjectType(..),+  ProjectLang(..),+  ProjectMetadata(DirProject, VirtualProject),+  )+import Proteome.Data.ProjectSpec (ProjectSpec(ProjectSpec))+import qualified Proteome.Data.ProjectSpec as PS (ProjectSpec(..))++projectFromSegments :: ProjectType -> ProjectName -> ProjectRoot -> Project+projectFromSegments tpe name root =+  Project (DirProject name root (Just tpe)) [] (Just (ProjectLang (projectType tpe))) []++projectFromSpec :: ProjectSpec -> Project+projectFromSpec (ProjectSpec name root tpe types lang langs) =+  Project (DirProject name root tpe) types lang langs++hasProjectRoot :: ProjectRoot -> ProjectSpec -> Bool+hasProjectRoot root spec = root == PS.root spec++hasProjectTypeName :: ProjectType -> ProjectName -> ProjectSpec -> Bool+hasProjectTypeName tpe' name' (ProjectSpec name _ (Just tpe) _ _ _) =+  name' == name && tpe' == tpe+hasProjectTypeName _ _ _ = False++byProjectTypeName :: [ProjectSpec] -> ProjectName -> ProjectType -> Maybe ProjectSpec+byProjectTypeName specs name tpe = find (hasProjectTypeName tpe name) specs++byProjectBases :: [FilePath] -> ProjectRoot -> Bool+byProjectBases baseDirs (ProjectRoot root) = elem ((takeDirectory . takeDirectory) root) baseDirs++virtualProject :: ProjectName -> Project+virtualProject name = Project (VirtualProject name) [] Nothing []++resolveByType :: [FilePath] -> [ProjectSpec] -> ProjectRoot -> ProjectName -> ProjectType -> Maybe Project+resolveByType baseDirs explicit root name tpe =+  orElse (if byPath then Just (projectFromSegments tpe name root) else Nothing) (fmap projectFromSpec byTypeName)+  where+    byTypeName = byProjectTypeName explicit name tpe+    byPath = byProjectBases baseDirs root++resolveByRoot :: [ProjectSpec] -> ProjectRoot -> Maybe Project+resolveByRoot explicit root =+  fmap projectFromSpec byRoot+  where+    byRoot = find (hasProjectRoot root) explicit++resolveProject ::+  [FilePath] ->+  [ProjectSpec] ->+  ProjectConfig ->+  ProjectRoot ->+  ProjectName ->+  Maybe ProjectType ->+  Project+resolveProject baseDirs explicit _ root name tpe =+  fromMaybe byTypeOrVirtual byRoot+  where+    byTypeOrVirtual = fromMaybe (virtualProject name) byType+    byType = tpe >>= resolveByType baseDirs explicit root name+    byRoot = resolveByRoot explicit root
+ lib/Proteome/Save.hs view
@@ -0,0 +1,8 @@+module Proteome.Save(+  proSave,+) where++import Proteome.Data.Proteome (Proteome)++proSave :: Proteome ()+proSave = return ()
+ lib/Proteome/Settings.hs view
@@ -0,0 +1,52 @@+module Proteome.Settings(+  mainProjectDir,+  projects,+  projectTypeDirs,+  projectBaseDirs,+  projectConfig,+  mainName,+  mainType,+  tagsCommand,+  tagsArgs,+  tagsFork,+  tagsFileName,+) where++import qualified Data.Map as Map+import Ribosome.Config.Settings+import Proteome.Data.Project (ProjectName, ProjectType)+import Proteome.Data.ProjectSpec (ProjectSpec)+import Proteome.Config++mainProjectDir :: Setting String+mainProjectDir = Setting "main_project_dir" True Nothing++projects :: Setting [ProjectSpec]+projects = Setting "projects" True (Just [])++projectBaseDirs :: Setting [FilePath]+projectBaseDirs = Setting "project_base_dirs" True (Just [])++projectTypeDirs :: Setting [FilePath]+projectTypeDirs = Setting "project_type_dirs" True (Just [])++projectConfig :: Setting ProjectConfig+projectConfig = Setting "project_config" True (Just (ProjectConfig Map.empty))++mainName :: Setting ProjectName+mainName = Setting "main_name" True Nothing++mainType :: Setting ProjectType+mainType = Setting "main_type" True Nothing++tagsCommand :: Setting String+tagsCommand = Setting "tags_command" True (Just "ctags")++tagsArgs :: Setting String+tagsArgs = Setting "tags_args" True (Just "-R --languages={langsComma} -f {tagFile} {root}")++tagsFork :: Setting Bool+tagsFork = Setting "tags_fork" True (Just True)++tagsFileName :: Setting FilePath+tagsFileName = Setting "tags_file_name" True (Just ".tags")
+ lib/Proteome/Tags.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE OverloadedStrings #-}++module Proteome.Tags(+  proTags,+) where++import GHC.IO.Exception (ExitCode(..))+import Control.Monad (when)+import Control.Monad.IO.Class (liftIO)+import Control.Lens (over)+import Data.List (intercalate)+import qualified Data.Map.Strict as Map (adjust)+import Data.Maybe (maybeToList)+import Data.String.Utils (replace)+import System.Process (readProcessWithExitCode)+import System.FilePath ((</>))+import System.Directory (setCurrentDirectory, doesFileExist, removePathForcibly)+import Ribosome.Config.Settings (setting)+import qualified Ribosome.Data.Ribo as Ribo (inspect, modify)+import Ribosome.Data.Errors (Errors(Errors), Error(Error), ComponentName(ComponentName))+import Ribosome.Internal.IO (forkNeovim)+import Proteome.Data.Env (Env(mainProject))+import qualified Proteome.Data.Env as Env (_errors)+import Proteome.Data.Proteome (Proteome)+import Proteome.Data.Project (+  Project (Project),+  ProjectLang(ProjectLang),+  ProjectRoot(ProjectRoot),+  ProjectMetadata (DirProject),+  langOrType,+  )+import qualified Proteome.Settings as S (tagsCommand, tagsArgs, tagsFork, tagsFileName)++replaceFormatItem :: String -> (String, String) -> String+replaceFormatItem original (placeholder, replacement) =+  replace ("{" ++ placeholder ++ "}") replacement original++formatTagsArgs :: [ProjectLang] -> ProjectRoot -> FilePath -> String -> String+formatTagsArgs langs (ProjectRoot root) fileName formatString =+  foldl replaceFormatItem formatString formats+  where+    formats = [+      ("langsComma", intercalate "," $ fmap (\(ProjectLang l) -> l) langs),+      ("tagFile", fileName),+      ("root", root)+      ]++deleteTags :: ProjectRoot -> Proteome ()+deleteTags (ProjectRoot root) = do+  name <- setting S.tagsFileName+  let path = root </> name+  exists <- liftIO $ doesFileExist path+  when exists $ liftIO $ removePathForcibly path++storeError :: ComponentName -> [String] -> Errors -> Errors+storeError name msg (Errors errors) =+  Errors (Map.adjust (err:) name errors)+  where+    err = Error time msg+    time = 0++notifyError :: String -> Proteome ()+notifyError e =+  Ribo.modify $ over Env._errors (storeError (ComponentName "ctags") [e])++executeTags :: ProjectRoot -> String -> String -> Proteome ()+executeTags (ProjectRoot root) cmd args = do+  liftIO $ setCurrentDirectory root+  deleteTags (ProjectRoot root)+  (exitcode, _, stderr) <- liftIO $ readProcessWithExitCode cmd [args] ""+  case exitcode of+    ExitSuccess -> return ()+    ExitFailure _ -> notifyError stderr++regenerateTags :: ProjectRoot -> [ProjectLang] -> Proteome ()+regenerateTags root langs = do+  cmd <- setting S.tagsCommand+  args <- setting S.tagsArgs+  fileName <- setting S.tagsFileName+  let thunk = executeTags root cmd (formatTagsArgs langs root fileName args)+  fork <- setting S.tagsFork+  _ <- if fork then forkNeovim thunk else thunk+  return ()++proTags :: Proteome ()+proTags = do+  main <- Ribo.inspect mainProject+  case main of+    Project (DirProject _ root tpe) _ lang langs ->+      regenerateTags root (maybeToList (langOrType lang tpe) ++ langs)+    _ -> return ()
+ lib/Proteome/Test/Config.hs view
@@ -0,0 +1,13 @@+module Proteome.Test.Config(+  defaultTestConfigWith,+  defaultTestConfig,+) where++import Ribosome.Test.Embed (TestConfig, Vars)+import qualified Ribosome.Test.Embed as E (defaultTestConfig, defaultTestConfigWith)++defaultTestConfigWith :: Vars -> TestConfig+defaultTestConfigWith = E.defaultTestConfigWith "proteome"++defaultTestConfig :: TestConfig+defaultTestConfig = E.defaultTestConfig "proteome"
+ lib/Proteome/Test/Functional.hs view
@@ -0,0 +1,15 @@+module Proteome.Test.Functional(+  spec,+  specWith,+) where++import Ribosome.Data.Ribo (Ribo)+import Ribosome.Test.Embed (Vars)+import Ribosome.Test.Functional (functionalSpec)+import Proteome.Test.Config (defaultTestConfig, defaultTestConfigWith)++spec :: Ribo () () -> IO ()+spec = functionalSpec defaultTestConfig++specWith :: Vars -> Ribo () () -> IO ()+specWith vars = functionalSpec $ defaultTestConfigWith vars
+ lib/Proteome/Test/Unit.hs view
@@ -0,0 +1,27 @@+module Proteome.Test.Unit(+  spec,+  specWith,+  specWithDef,+) where++import Data.Default.Class (def)+import UnliftIO.STM (newTVarIO)+import Ribosome.Test.Embed (Vars)+import Ribosome.Test.Unit (unitSpec)+import Proteome.Data.Proteome (Proteome)+import Proteome.Data.Env (Env)+import Proteome.Test.Config (defaultTestConfig, defaultTestConfigWith)++spec :: Env -> Proteome () -> IO ()+spec e s = do+  t <- newTVarIO e+  unitSpec defaultTestConfig t s++specWith :: Env -> Proteome () -> Vars -> IO ()+specWith e s vars = do+  t <- newTVarIO e+  unitSpec (defaultTestConfigWith vars) t s++specWithDef :: Proteome () -> Vars -> IO ()+specWithDef s v = do+  specWith def s v
+ lib/Ribosome/Api/Option.hs view
@@ -0,0 +1,14 @@+module Ribosome.Api.Option(+  optionCat,+  rtpCat+) where++import Neovim++optionCat :: String -> String -> Neovim env ()+optionCat name extra = do+  current <- vim_get_option' name >>= fromObject'+  vim_set_option' name $ toObject $ current ++ "," ++ extra++rtpCat :: String -> Neovim env ()+rtpCat = optionCat "runtimepath"
+ lib/Ribosome/Api/Response.hs view
@@ -0,0 +1,27 @@+module Ribosome.Api.Response(+  nvimFatal,+  nvimResponseString,+  nvimResponseStringArray,+  nvimValidateFatal+)+where++import Data.ByteString.UTF8+import Neovim++nvimFatal :: Either NeovimException a -> Neovim env a+nvimFatal (Right a) = return a+nvimFatal (Left e) = (liftIO . fail . show) e++nvimResponseString :: Object -> Neovim env String+nvimResponseString (ObjectString a) = return $ toString a+nvimResponseString a = liftIO . fail $ "invalid nvim type for String: " ++ (show a)++nvimResponseStringArray :: Object -> Neovim env [String]+nvimResponseStringArray (ObjectArray a) = traverse nvimResponseString a+nvimResponseStringArray a = liftIO . fail $ "invalid nvim type for Array: " ++ (show a)++nvimValidateFatal :: (Object -> Neovim env a) -> (Either NeovimException Object) -> Neovim env a+nvimValidateFatal validate response = do+  result <- nvimFatal response+  validate result
+ lib/Ribosome/Config/Settings.hs view
@@ -0,0 +1,47 @@+module Ribosome.Config.Settings(+  Setting (..),+  setting,+  settingE,+  updateSetting,+  settingVariableName,+) where++import Neovim+import Ribosome.Data.Ribo (Ribo)+import qualified Ribosome.Data.Ribosome as R (name)++data Setting a =+  Setting {+    name :: String,+    prefix :: Bool,+    fallback :: Maybe a+  }++settingVariableName :: Setting a -> Ribo e String+settingVariableName (Setting n False _) = return n+settingVariableName (Setting n True _) = do+  pluginName <- fmap R.name $ ask+  return $ pluginName ++ "_" ++ n++settingE :: NvimObject a => Setting a -> Ribo e (Either String a)+settingE s @ (Setting _ _ fallback') = do+  varName <- settingVariableName s+  raw <- vim_get_var varName+  case raw of+    Right o -> fromObject' o+    Left a -> return $ case fallback' of+      Just fb -> Right fb+      Nothing -> Left $ show a++setting :: NvimObject a => Setting a -> Ribo e a+setting s = do+  raw <- settingE s+  case raw of+    Right o -> return o+    Left e -> fail e++updateSetting :: NvimObject a => Setting a -> a -> Ribo e ()+updateSetting s a = do+  varName <- settingVariableName s+  _ <- vim_set_var' varName (toObject a)+  return ()
+ lib/Ribosome/Data/Errors.hs view
@@ -0,0 +1,25 @@+module Ribosome.Data.Errors(+  ComponentName(..),+  Error(..),+  Errors(..),+) where++import qualified Data.Map as Map+import Data.Default.Class (Default(def))+import Data.Map.Strict (Map)++newtype ComponentName =+  ComponentName String+  deriving (Eq, Ord)++data Error =+  Error {+    errorTimestamp :: Int,+    errorMessage :: [String]+  }++newtype Errors =+  Errors (Map ComponentName [Error])++instance Default Errors where+  def = Errors Map.empty
+ lib/Ribosome/Data/Maybe.hs view
@@ -0,0 +1,7 @@+module Ribosome.Data.Maybe(+  orElse,+) where++orElse :: Maybe a -> Maybe a -> Maybe a+orElse fallback Nothing = fallback+orElse _ a = a
+ lib/Ribosome/Data/Ribo.hs view
@@ -0,0 +1,26 @@+module Ribosome.Data.Ribo(+  Ribo,+  state,+  inspect,+  modify,+) where++import Control.Concurrent.STM.TVar (modifyTVar)+import UnliftIO.STM (TVar, atomically, readTVarIO)+import Neovim (Neovim, ask)+import Ribosome.Data.Ribosome++type Ribo e = Neovim (Ribosome e)++state :: Ribo (TVar e) e+state = do+  Ribosome _ t <- ask+  readTVarIO t++inspect :: (e -> a) -> Ribo (TVar e) a+inspect f = fmap f state++modify :: (e -> e) -> Ribo (TVar e) ()+modify f = do+  Ribosome _ t <- ask+  atomically $ modifyTVar t f
+ lib/Ribosome/Data/Ribosome.hs view
@@ -0,0 +1,18 @@+module Ribosome.Data.Ribosome(+  Ribosome (..),+  newRibosome,+) where++import UnliftIO.STM (TVar, newTVarIO)+import Control.Monad.IO.Class (MonadIO)++data Ribosome e =+  Ribosome {+    name :: String,+    env :: e+  }++newRibosome :: MonadIO m => String -> e -> m (Ribosome (TVar e))+newRibosome name' env' = do+  tv <- newTVarIO env'+  return $ Ribosome name' tv
+ lib/Ribosome/Internal/IO.hs view
@@ -0,0 +1,25 @@+module Ribosome.Internal.IO(+  retypeNeovim,+  forkNeovim,+) where++import GHC.Conc.Sync (forkIO)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.Reader (ask, runReaderT, withReaderT)+import Control.Monad.Trans.Resource (runResourceT)+import Data.Functor (void)+import Neovim.Context.Internal (Neovim(..), Config(..), retypeConfig, runNeovim)++-- try using Contravariant with this+retypeNeovim :: (e0 -> e1) -> Neovim e1 a -> Neovim e0 a+retypeNeovim transform thunk = do+  env <- Neovim ask+  liftIO $ runReaderT (withReaderT (newEnv env) $ runResourceT $ unNeovim thunk) env+  where+    newEnv = retypeConfig . transform . customConfig++forkNeovim :: Neovim e () -> Neovim e ()+forkNeovim thunk = do+  env <- Neovim ask+  _ <- liftIO $ forkIO $ void $ runNeovim env thunk+  return ()
+ lib/Ribosome/Internal/NvimObject.hs view
@@ -0,0 +1,8 @@+module Ribosome.Internal.NvimObject(+  deriveString,+) where++import Neovim (Object, Doc, AnsiStyle, fromObject)++deriveString :: (String -> a) -> Object -> Either (Doc AnsiStyle) a+deriveString cons o = fmap cons ((fromObject o) :: Either (Doc AnsiStyle) String)
+ lib/Ribosome/Log.hs view
@@ -0,0 +1,14 @@+module Ribosome.Log(+  debug,+  info,+) where++import Control.Monad.IO.Class (liftIO)+import Neovim.Log (debugM, infoM)+import Ribosome.Data.Ribo (Ribo)++debug :: Show a => String -> a -> Ribo e ()+debug name message = liftIO $ debugM name $ show message++info :: Show a => String -> a -> Ribo e ()+info name message = liftIO $ infoM name $ show message
+ lib/Ribosome/Test/Embed.hs view
@@ -0,0 +1,47 @@+module Ribosome.Test.Embed(+  defaultTestConfig,+  defaultTestConfigWith,+  TestConfig (..),+  Vars(..),+  unsafeEmbeddedSpec,+  setVars,+  setupPluginEnv,+) where++import Data.Foldable (traverse_)+import Neovim (Neovim, Object, vim_set_var')+import Neovim.Test (testWithEmbeddedNeovim, Seconds(..))+import Ribosome.Data.Ribo (Ribo)+import Ribosome.Data.Ribosome (Ribosome(Ribosome))+import Ribosome.Api.Option (rtpCat)++type Runner env = TestConfig -> Neovim env () -> Neovim env ()++newtype Vars = Vars [(String, Object)]++data TestConfig =+  TestConfig {+    pluginName :: String,+    extraRtp :: String,+    logPath :: FilePath,+    variables :: Vars+  }++defaultTestConfigWith :: String -> Vars -> TestConfig+defaultTestConfigWith name = TestConfig name "test/f/fixtures/rtp" "test/f/temp/log"++defaultTestConfig :: String -> TestConfig+defaultTestConfig name = defaultTestConfigWith name (Vars [])++setVars :: Vars -> Neovim e ()+setVars (Vars vars) =+  traverse_ (uncurry vim_set_var') vars++setupPluginEnv :: TestConfig -> Neovim e ()+setupPluginEnv (TestConfig _ rtp _ vars) = do+  rtpCat rtp+  setVars vars++unsafeEmbeddedSpec :: Runner (Ribosome e) -> TestConfig -> e -> Ribo e () -> IO ()+unsafeEmbeddedSpec runner conf env spec =+  testWithEmbeddedNeovim Nothing (Seconds 5) (Ribosome (pluginName conf) env) $ runner conf spec
+ lib/Ribosome/Test/Exists.hs view
@@ -0,0 +1,44 @@+module Ribosome.Test.Exists(+  waitForPlugin,+  sleep,+) where++import Data.Time.Clock.POSIX+import Data.Strings (strCapitalize)+import Control.Monad.IO.Class+import Control.Concurrent (threadDelay)+import Neovim++epochSeconds :: MonadIO f => f Int+epochSeconds = liftIO $ fmap round getPOSIXTime++sleep :: MonadIO f => Double -> f ()+sleep seconds = liftIO $ threadDelay $ round $ seconds * 10e6++retry :: MonadIO f => Double -> Int -> f a -> (a -> f (Either String b)) -> f b+retry interval timeout thunk check = do+  start <- epochSeconds+  step start+  where+    step start = do+      result <- thunk+      checked <- check result+      recurse start checked+    recurse _ (Right b) = return b+    recurse start (Left e) = do+      current <- epochSeconds+      if (current - start) < timeout+      then do+        sleep interval+        step start+      else fail e++waitForPlugin :: String -> Double -> Int -> Neovim env ()+waitForPlugin name interval timeout = do+  retry interval timeout thunk check+  where+    thunk = vim_call_function "exists" [toObject $ "*" ++ (strCapitalize name) ++ "Poll"]+    check (Right (ObjectInt 1)) = return $ Right ()+    check (Right a) = return $ Left $ errormsg ++ "weird return type " ++ (show a)+    check (Left e) = return $ Left $ errormsg ++ (show e)+    errormsg = "plugin didn't start within " ++ (show timeout) ++ " seconds: "
+ lib/Ribosome/Test/File.hs view
@@ -0,0 +1,27 @@+module Ribosome.Test.File(+  tempDirIO,+  tempDir,+) where++import Control.Monad.IO.Class (liftIO)+import Neovim (Neovim)+import System.Directory (canonicalizePath, createDirectoryIfMissing, removePathForcibly)+import System.FilePath ((</>))++testDir :: String -> IO FilePath+testDir prefix = canonicalizePath $ "test" </> prefix++-- raises exception if cwd is not the package root so we don't damage anything+tempDirIO :: String -> FilePath -> IO FilePath+tempDirIO prefix path = do+  base <- testDir prefix+  let dir = base </> "temp"+  removePathForcibly dir+  createDirectoryIfMissing False dir+  let absPath = dir </> path+  createDirectoryIfMissing True absPath+  return absPath++tempDir :: String -> FilePath -> Neovim e FilePath+tempDir prefix path =+  liftIO $ tempDirIO prefix path
+ lib/Ribosome/Test/Functional.hs view
@@ -0,0 +1,60 @@+module Ribosome.Test.Functional(+  startPlugin,+  fSpec,+  functionalSpec,+) where++import Control.Monad (when)+import Control.Monad.IO.Class+import Control.Exception (finally)+import Data.Foldable (traverse_)+import System.Directory (getCurrentDirectory, createDirectoryIfMissing, removePathForcibly, doesFileExist)+import System.FilePath (takeDirectory)+import System.Console.ANSI (setSGR, SGR(SetColor, Reset), ConsoleLayer(Foreground), ColorIntensity(Dull), Color(Green))+import Neovim (Neovim, vim_command')+import Ribosome.Data.Ribo (Ribo)+import Ribosome.Test.Exists (waitForPlugin)+import Ribosome.Test.Embed (TestConfig(..), unsafeEmbeddedSpec, setupPluginEnv)++jobstart :: MonadIO f => String -> f String+jobstart cmd = do+  dir <- liftIO getCurrentDirectory+  return $ "call jobstart('" ++ cmd ++ "', { 'rpc': v:true, 'cwd': '" ++ dir ++ "' })"++logFile :: TestConfig -> FilePath+logFile conf = logPath conf ++ "-spec"++startPlugin :: TestConfig -> Neovim env ()+startPlugin conf = do+  liftIO $ createDirectoryIfMissing True (takeDirectory (logPath conf))+  liftIO $ removePathForcibly (logFile conf)+  setupPluginEnv conf+  cmd <- jobstart $ "stack run -- -l " ++ logFile conf ++ " -v INFO"+  vim_command' cmd+  waitForPlugin (pluginName conf) 0.1 3++fSpec :: TestConfig -> Neovim env () -> Neovim env ()+fSpec conf spec = startPlugin conf >> spec++showLog' :: String -> IO ()+showLog' output = do+  putStrLn ""+  setSGR [SetColor Foreground Dull Green]+  putStrLn "plugin output:"+  setSGR [Reset]+  traverse_ putStrLn (lines output)+  putStrLn ""++showLog :: TestConfig -> IO ()+showLog conf = do+  let file = logFile conf+  exists <- doesFileExist file+  when exists $ do+    output <- readFile file+    case output of+      [] -> return ()+      o -> showLog' o++functionalSpec :: TestConfig -> Ribo () () -> IO ()+functionalSpec conf spec =+  finally (unsafeEmbeddedSpec fSpec conf () spec) (showLog conf)
+ lib/Ribosome/Test/Unit.hs view
@@ -0,0 +1,32 @@+module Ribosome.Test.Unit(+  unitSpec,+  tempDir,+  tempFile,+  uPrefix,+) where++import System.FilePath (takeDirectory, takeFileName, (</>))+import Neovim (Neovim)+import Ribosome.Data.Ribo (Ribo)+import Ribosome.Test.Embed (TestConfig(..), setupPluginEnv, unsafeEmbeddedSpec)+import qualified Ribosome.Test.File as F (tempDir)++uPrefix :: String+uPrefix = "u"++uSpec :: TestConfig -> Neovim env () -> Neovim env ()+uSpec conf spec = do+  setupPluginEnv conf+  spec++unitSpec :: TestConfig -> e -> Ribo e () -> IO ()+unitSpec =+  unsafeEmbeddedSpec uSpec++tempDir :: FilePath -> Neovim e FilePath+tempDir = F.tempDir uPrefix++tempFile :: FilePath -> Neovim e FilePath+tempFile file = do+  absDir <- tempDir $ takeDirectory file+  return $ absDir </> takeFileName file
− nvim.hs
@@ -1,6 +0,0 @@-import Neovim--import Proteome.Plugin (plugin)--main :: IO ()-main = neovim defaultConfig {plugins = [plugin]}
proteome.cabal view
@@ -1,28 +1,205 @@-name: proteome-version: 0.1.0.0-synopsis: neovim project manager-description: neovim plugin for managing project-specific configuration and performing runtime tasks on projects-license: MIT-license-file: LICENSE-author: Torsten Schmits-maintainer: tek@tryp.io-copyright: 2018 Torsten Schmits-category: Neovim-build-type: Simple-extra-source-files: README.md-cabal-version: >=1.10-x-curation: uncurated-no-trustee-contact+cabal-version: 1.12 -executable proteome-  main-is: nvim.hs+-- This file has been generated from package.yaml by hpack version 0.31.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: aeb4f9fc4dda14d1efb906445e7ff9e84fd39b5cf2bcf984c395b23c604c14fb++name:           proteome+version:        0.2.0.0+synopsis:       neovim project manager+description:    Please see the README on GitHub at <https://github.com/tek/proteome-hs>+category:       Neovim+homepage:       https://github.com/tek/proteome-hs#readme+bug-reports:    https://github.com/tek/proteome-hs/issues+author:         Torsten Schmits+maintainer:     tek@tryp.io+copyright:      2018 Torsten Schmits+license:        MIT+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md++source-repository head+  type: git+  location: https://github.com/tek/proteome-hs++library+  exposed-modules:+      Proteome.Add+      Proteome.Config+      Proteome.Data.Env+      Proteome.Data.Project+      Proteome.Data.ProjectSpec+      Proteome.Data.Proteome+      Proteome.Init+      Proteome.Log+      Proteome.Plugin+      Proteome.Project.Resolve+      Proteome.Save+      Proteome.Settings+      Proteome.Tags+      Proteome.Test.Config+      Proteome.Test.Functional+      Proteome.Test.Unit+      Ribosome.Api.Option+      Ribosome.Api.Response+      Ribosome.Config.Settings+      Ribosome.Data.Errors+      Ribosome.Data.Maybe+      Ribosome.Data.Ribo+      Ribosome.Data.Ribosome+      Ribosome.Internal.IO+      Ribosome.Internal.NvimObject+      Ribosome.Log+      Ribosome.Test.Embed+      Ribosome.Test.Exists+      Ribosome.Test.File+      Ribosome.Test.Functional+      Ribosome.Test.Unit+  other-modules:+      Paths_proteome   hs-source-dirs:-    .,-    lib+      lib+  build-depends:+      MissingH+    , ansi-terminal+    , base >=4.7 && <5+    , containers+    , data-default-class+    , deepseq+    , directory+    , filepath+    , hslogger+    , lens+    , messagepack+    , mtl+    , nvim-hs+    , pretty-terminal+    , prettyprinter+    , process+    , resourcet+    , split+    , stm+    , strings+    , text+    , time+    , unliftio+    , utf8-string+  default-language: Haskell2010++executable proteome-exe+  main-is: Main.hs   other-modules:-    Proteome.Plugin,-    Proteome.Init,-    Proteome.Add+      Paths_proteome+  hs-source-dirs:+      app+  ghc-options: -threaded -rtsopts -with-rtsopts=-N   build-depends:-    base >= 4.7 && < 5,-    nvim-hs >= 1 && < 2+      MissingH+    , ansi-terminal+    , base >=4.7 && <5+    , containers+    , data-default-class+    , deepseq+    , directory+    , filepath+    , hslogger+    , lens+    , messagepack+    , mtl+    , nvim-hs+    , pretty-terminal+    , prettyprinter+    , process+    , proteome+    , resourcet+    , split+    , stm+    , strings+    , text+    , time+    , unliftio+    , utf8-string+  default-language: Haskell2010++test-suite proteome-functional+  type: exitcode-stdio-1.0+  main-is: SpecMain.hs+  other-modules:+      Config+      ConfigSpec+      InitSpec+      Paths_proteome+  hs-source-dirs:+      test/f+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      HTF+    , MissingH+    , ansi-terminal+    , base >=4.7 && <5+    , containers+    , data-default-class+    , deepseq+    , directory+    , filepath+    , hslogger+    , lens+    , messagepack+    , mtl+    , nvim-hs+    , pretty-terminal+    , prettyprinter+    , process+    , proteome+    , resourcet+    , split+    , stm+    , strings+    , text+    , time+    , unliftio+    , utf8-string+  default-language: Haskell2010++test-suite proteome-unit+  type: exitcode-stdio-1.0+  main-is: SpecMain.hs+  other-modules:+      Config+      TagsSpec+      Paths_proteome+  hs-source-dirs:+      test/u+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      HTF+    , MissingH+    , ansi-terminal+    , base >=4.7 && <5+    , containers+    , data-default-class+    , deepseq+    , directory+    , filepath+    , hslogger+    , lens+    , messagepack+    , mtl+    , nvim-hs+    , pretty-terminal+    , prettyprinter+    , process+    , proteome+    , resourcet+    , split+    , stm+    , strings+    , text+    , time+    , unliftio+    , utf8-string   default-language: Haskell2010
+ test/f/Config.hs view
@@ -0,0 +1,15 @@+module Config(+  vars,+) where++import Neovim (toObject)+import System.Directory (getCurrentDirectory)+import Ribosome.Test.Embed (Vars(..))++vars :: IO Vars+vars = do+  base <- getCurrentDirectory+  return $ Vars [+    ("proteome_project_base_dirs", toObject [base ++ "/test/f/fixtures/config/projects"]),+    ("proteome_main_project_dir", toObject $ base ++ "/test/f/fixtures/config/projects/haskell/flagellum")+    ]
+ test/f/ConfigSpec.hs view
@@ -0,0 +1,24 @@+{-# OPTIONS_GHC -F -pgmF htfpp #-}++module ConfigSpec(+  htf_thisModulesTests+) where++import Control.Monad.IO.Class (liftIO)+import Test.Framework+import Data.MessagePack (Object(ObjectInt))+import Neovim (vim_call_function, vim_get_var')+import Ribosome.Data.Ribo (Ribo)+import Proteome.Test.Functional (specWith)+import Config (vars)++configSpec :: Ribo env ()+configSpec = do+  _ <- vim_call_function "ProReadConfig" []+  value <- vim_get_var' "flag"+  liftIO $ assertEqual value (ObjectInt 1)++test_config :: IO ()+test_config = do+  v <- vars+  specWith v configSpec
+ test/f/InitSpec.hs view
@@ -0,0 +1,26 @@+{-# OPTIONS_GHC -F -pgmF htfpp #-}++module InitSpec(+  htf_thisModulesTests+) where++import Control.Monad.IO.Class (liftIO)+import Test.Framework+import Ribosome.Data.Ribo (Ribo)+import Ribosome.Config.Settings (setting)+import qualified Proteome.Settings as S+import Proteome.Data.Project (ProjectName(ProjectName), ProjectType(ProjectType))+import Proteome.Test.Functional (specWith)+import Config (vars)++initSpec :: Ribo env ()+initSpec = do+  tpe <- setting S.mainType+  name <- setting S.mainName+  liftIO $ assertEqual name (ProjectName "flagellum")+  liftIO $ assertEqual tpe (ProjectType "haskell")++test_init :: IO ()+test_init = do+  v <- vars+  specWith v initSpec
+ test/f/SpecMain.hs view
@@ -0,0 +1,12 @@+{-# OPTIONS_GHC -F -pgmF htfpp #-}++module Main where++import {-@ HTF_TESTS @-} ConfigSpec+import {-@ HTF_TESTS @-} InitSpec+import Test.Framework+import Test.Framework.BlackBoxTest ()++main :: IO ()+main = do+  htfMain htf_importedTests
+ test/u/Config.hs view
@@ -0,0 +1,15 @@+module Config(+  vars,+) where++import Neovim (toObject)+import System.Directory (getCurrentDirectory)+import Ribosome.Test.Embed (Vars(..))++vars :: IO Vars+vars = do+  base <- getCurrentDirectory+  return $ Vars [+    ("proteome_project_base_dirs", toObject [base ++ "/test/u/fixtures/projects"]),+    ("proteome_main_project_dir", toObject $ base ++ "/test/u/fixtures/projects/haskell/flagellum")+    ]
+ test/u/SpecMain.hs view
@@ -0,0 +1,10 @@+{-# OPTIONS_GHC -F -pgmF htfpp #-}++module Main where++import {-@ HTF_TESTS @-} TagsSpec+import Test.Framework+import Test.Framework.BlackBoxTest ()++main :: IO ()+main = htfMain htf_importedTests
+ test/u/TagsSpec.hs view
@@ -0,0 +1,48 @@+{-# OPTIONS_GHC -F -pgmF htfpp #-}++module TagsSpec(+  htf_thisModulesTests+) where++import Control.Monad.IO.Class (liftIO)+import Control.Lens (set)+import System.FilePath ((</>))+import System.Directory (doesFileExist)+import Test.Framework+import Ribosome.Config.Settings (updateSetting)+import qualified Ribosome.Data.Ribo as Ribo (modify)+import Ribosome.Test.Unit (tempDir)+import Proteome.Data.Proteome (Proteome)+import Proteome.Data.Env (_mainProject)+import Proteome.Data.Project (+  ProjectName(..),+  ProjectRoot(..),+  ProjectType(..),+  ProjectLang(..),+  ProjectMetadata(DirProject),+  _meta,+  _lang,+  )+import qualified Proteome.Settings as S (tagsCommand, tagsArgs, tagsFork)+import Proteome.Tags (proTags)+import Proteome.Test.Unit (specWithDef)+import Config (vars)++main :: FilePath -> ProjectMetadata+main root = DirProject (ProjectName "flagellum") (ProjectRoot root) (Just (ProjectType "haskell"))++tagsSpec :: Proteome ()+tagsSpec = do+  root <- tempDir "projects/haskell/flagellum"+  Ribo.modify $ set (_mainProject._meta) (main root)+  Ribo.modify $ set (_mainProject._lang) (Just (ProjectLang "idris"))+  updateSetting S.tagsCommand "touch"+  updateSetting S.tagsArgs "tags-{langsComma}-{tagFile}"+  updateSetting S.tagsFork False+  proTags+  let tagsFile = root </> "tags-idris-.tags"+  exists <- liftIO $ doesFileExist tagsFile+  liftIO $ assertBool exists++test_tags :: IO ()+test_tags = vars >>= specWithDef tagsSpec