miv (empty) → 0.3.0
raw patch · 14 files changed
+1267/−0 lines, 14 filesdep +aesondep +asyncdep +basesetup-changed
Dependencies added: aeson, async, base, concurrent-output, directory, ghc-prim, hashable, hspec, monad-parallel, process, text, time, unordered-containers, yaml
Files
- LICENSE +21/−0
- Setup.hs +2/−0
- miv.cabal +60/−0
- src/Command.hs +115/−0
- src/Git.hs +38/−0
- src/Main.hs +466/−0
- src/Mapping.hs +50/−0
- src/Mode.hs +50/−0
- src/Plugin.hs +63/−0
- src/ReadText.hs +10/−0
- src/Setting.hs +30/−0
- src/ShowText.hs +10/−0
- src/VimScript.hs +351/−0
- test/Spec.hs +1/−0
+ LICENSE view
@@ -0,0 +1,21 @@+The MIT License (MIT)++Copyright (c) 2014-2016 itchyny <https://github.com/itchyny>++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ miv.cabal view
@@ -0,0 +1,60 @@+name: miv+version: 0.3.0+author: itchyny <https://github.com/itchyny>+maintainer: itchyny <https://github.com/itchyny>+license: MIT+license-file: LICENSE+category: Compiler+build-type: Simple+cabal-version: >=1.8+synopsis: Manage Vim plugins with command+description: The miv command is a cli tool to manage Vim plugins.++executable miv+ hs-source-dirs: src+ main-is: Main.hs+ ghc-options: -threaded -Wall+ other-modules: Plugin+ , Setting+ , Mode+ , Command+ , Mapping+ , ShowText+ , ReadText+ , VimScript+ , Git+ , Paths_miv+ build-depends: base >= 4.9 && < 5+ , ghc-prim+ , process+ , async+ , concurrent-output+ , time+ , directory+ , hashable+ , aeson+ , yaml+ , text+ , unordered-containers+ , monad-parallel++test-suite spec+ hs-source-dirs: test+ main-is: Spec.hs+ type: exitcode-stdio-1.0+ build-depends: base >= 4.9 && < 5+ , ghc-prim+ , process+ , time+ , directory+ , hashable+ , hspec+ , aeson+ , yaml+ , text+ , unordered-containers+ , monad-parallel++source-repository head+ type: git+ location: git@github.com:itchyny/miv.git
+ src/Command.hs view
@@ -0,0 +1,115 @@+{-# LANGUAGE OverloadedStrings #-}+module Command where++import Data.Monoid ((<>))+import qualified Data.Text as T+import Data.Text (Text, unwords)+import Prelude hiding (show, unwords)+import ShowText++data CmdBang = CmdBang+ | CmdNoBang+ deriving Eq++instance ShowText CmdBang where+ show CmdBang = "-bang"+ show CmdNoBang = ""++data CmdBar = CmdBar+ | CmdNoBar+ deriving Eq++instance ShowText CmdBar where+ show CmdBar = "-bar"+ show CmdNoBar = ""++data CmdRegister = CmdRegister+ | CmdNoRegister+ deriving Eq++instance ShowText CmdRegister where+ show CmdRegister = "-register"+ show CmdNoRegister = ""++data CmdBuffer = CmdBuffer+ | CmdNoBuffer+ deriving Eq++instance ShowText CmdBuffer where+ show CmdBuffer = "-buffer"+ show CmdNoBuffer = ""++data CmdRange = CmdRange+ | CmdRangeWhole+ | CmdRangeN Int+ | CmdRangeCount Int+ | CmdNoRange+ deriving Eq++instance ShowText CmdRange where+ show CmdRange = "-range"+ show CmdRangeWhole = "-range=%"+ show (CmdRangeN n) = "-range=" <> show n+ show (CmdRangeCount n) = "-count=" <> show n+ show CmdNoRange = ""++data CmdArg = CmdNonNegArg+ | CmdZeroOneArg+ | CmdPositiveArg+ | CmdOneArg+ | CmdNoArg+ deriving Eq++instance ShowText CmdArg where+ show CmdNonNegArg = "-nargs=*"+ show CmdZeroOneArg = "-nargs=?"+ show CmdPositiveArg = "-nargs=+"+ show CmdOneArg = "-nargs=1"+ show CmdNoArg = "-nargs=0"++data CmdComplete = CmdComplete Text+ deriving Eq++instance ShowText CmdComplete where+ show (CmdComplete "") = ""+ show (CmdComplete complete) = "-complete=" <> complete++data Command =+ Command { cmdName :: Text+ , cmdRepText :: Text+ , cmdBang :: CmdBang+ , cmdBar :: CmdBar+ , cmdRegister :: CmdRegister+ , cmdBuffer :: CmdBuffer+ , cmdRange :: CmdRange+ , cmdArg :: CmdArg+ , cmdComplete :: CmdComplete+ } deriving Eq++instance ShowText Command where+ show cmd = unwords (filter (not . T.null)+ [ "command!"+ , show (cmdBang cmd)+ , show (cmdBar cmd)+ , show (cmdRegister cmd)+ , show (cmdBuffer cmd)+ , show (cmdRange cmd)+ , show (cmdArg cmd)+ , show (cmdComplete cmd)+ , cmdName cmd+ , cmdRepText cmd+ ])++defaultCommand :: Command+defaultCommand+ = Command { cmdName = ""+ , cmdRepText = ""+ , cmdBang = CmdBang+ , cmdBar = CmdNoBar+ , cmdRegister = CmdNoRegister+ , cmdBuffer = CmdNoBuffer+ , cmdRange = CmdRange+ , cmdArg = CmdNonNegArg+ , cmdComplete = CmdComplete ""+ }+
+ src/Git.hs view
@@ -0,0 +1,38 @@+module Git+ ( clone+ , cloneSubmodule+ , pull+ , pullSubmodule+ , lastUpdate+ , gitStatus+ , gitUrl+ )+ where++import Data.Monoid ((<>))+import System.Exit (ExitCode(..))+import System.Process (system, readProcess)++clone :: String -> FilePath -> String+clone repo path = unwords ["git", "clone", gitUrl repo, singleQuote path]++cloneSubmodule :: String -> FilePath -> String+cloneSubmodule repo path = unwords ["git", "clone", gitUrl repo, singleQuote path, "&&", "git", "-C", singleQuote path, "submodule", "update", "--init", "--recursive"]++pull :: FilePath -> String+pull path = unwords ["git", "-C", singleQuote path, "pull", "--rebase", "--prune"]++pullSubmodule :: FilePath -> String+pullSubmodule path = unwords ["git", "-C", singleQuote path, "pull", "--rebase", "--prune", "&&", "git", "-C", singleQuote path, "submodule", "update", "--init", "--recursive"]++lastUpdate :: FilePath -> IO Integer+lastUpdate path = read <$> readProcess "sh" ["-c", unwords ["git", "-C", singleQuote path, "show", "-s", "--format=%ct", "2>/dev/null", "||", "echo", "0"]] []++gitStatus :: FilePath -> IO ExitCode+gitStatus path = system $ unwords ["sh", "-c", "\"git", "-C", singleQuote path, "status", ">/dev/null 2>&1\""]++gitUrl :: String -> String+gitUrl = ("https://github.com/" <>)++singleQuote :: String -> String+singleQuote str = "'" <> str <> "'"
+ src/Main.hs view
@@ -0,0 +1,466 @@+{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}+module Main where++import Control.Concurrent (threadDelay, newEmptyMVar, forkIO, putMVar, takeMVar)+import Control.Concurrent.Async+import Control.Exception+import Control.Monad (filterM, forM_, unless, void, when, guard)+import qualified Control.Monad.Parallel as P+import Data.List ((\\), foldl', isPrefixOf, nub, transpose, unfoldr)+import Data.Maybe (listToMaybe, fromMaybe, isNothing)+import Data.Monoid ((<>))+import Data.Text (Text, unlines, pack, unpack)+import qualified Data.Text as T+import Data.Text.IO (putStrLn, putStr, writeFile, hGetContents)+import Data.Time (getZonedTime)+import Data.Version (showVersion)+import Data.Yaml (decodeFileEither, prettyPrintParseException)+import GHC.Conc (getNumProcessors, setNumCapabilities)+import Prelude hiding (readFile, writeFile, unlines, putStrLn, putStr, show)+import System.Console.Concurrent ()+import System.Console.Regions+import System.Directory+import System.Environment (getArgs)+import System.Exit (ExitCode(..))+import System.Info (os)+import System.IO (openFile, IOMode(..), hClose, hFlush, stdout, hGetLine)+import System.IO.Error (isDoesNotExistError, tryIOError, isEOFError)+import System.Process++import Git+import Paths_miv (version)+import Plugin+import Setting+import ShowText+import VimScript++nameversion :: Text+nameversion = "miv " <> pack (showVersion version)++expandHomeDirectory :: FilePath -> IO FilePath+expandHomeDirectory ('~':path) = (<>path) <$> getHomeDirectory+expandHomeDirectory path = return path++getSettingFile :: IO (Maybe FilePath)+getSettingFile+ = fmap listToMaybe $ filterM doesFileExist =<< mapM expandHomeDirectory+ [ "~/.vimrc.yaml"+ , "~/.vim/.vimrc.yaml"+ , "~/vimrc.yaml"+ , "~/.vim/vimrc.yaml"+ , "~/_vimrc.yaml"+ , "~/.vim/_vimrc.yaml"+ , "~/_vim/_vimrc.yaml"+ , "~/vimfiles/.vimrc.yaml"+ , "~/vimfiles/vimrc.yaml"+ , "~/vimfiles/_vimrc.yaml"+ ]++getSetting :: IO Setting+getSetting = do+ maybeFile <- getSettingFile+ case maybeFile of+ Just file -> do+ maybeSetting <- decodeFileEither file+ case maybeSetting of+ Right setting -> return setting+ Left err -> error $ prettyPrintParseException err+ Nothing -> error "No setting file: ~/.vimrc.yaml"++pluginDirectory :: IO FilePath+pluginDirectory = do+ dir <- expandHomeDirectory "~/.vim/miv/"+ windir <- expandHomeDirectory "~/vimfiles/miv/"+ exists <- doesDirectoryExist dir+ return (if exists then dir else (if os `elem` ["windows", "mingw"] then windir else dir))++createPluginDirectory :: IO ()+createPluginDirectory =+ createDirectoryIfMissing True =<< pluginDirectory++printUsage :: IO ()+printUsage = mapM_ putStrLn usage++commandHelp :: IO ()+commandHelp = mapM_ (putStrLn . show) arguments++data Argument = Argument (Text, Text)+ deriving (Eq, Ord)+instance ShowText Argument where+ show (Argument (x, y)) = x <> T.replicate (10 - T.length x) " " <> y++arguments :: [Argument]+arguments = map Argument+ [ ("install" , "Installs the uninstalled plugins.")+ , ("update" , "Updates the plugins.")+ , ("generate", "Generate the miv files.")+ , ("ftdetect", "Gather ftdetect scripts.")+ , ("helptags", "Generate the help tags file.")+ , ("each" , "Execute command at each plugin directory.")+ , ("path" , "Print the paths of each plugins.")+ , ("list" , "List the plugins.")+ , ("clean" , "Clean up unused plugins.")+ , ("edit" , "Edit the configuration file.")+ , ("command" , "Show the sub-commands.")+ , ("version" , "Show the version of miv.")+ , ("help" , "Show this help.")+ ]++usage :: [Text]+usage = [ nameversion+ , ""+ , "Usage: miv COMMAND"+ , ""+ , "Commands:"+ ]+ <> map (T.append " " . show) arguments+ <> [ ""+ , "You can install the plugins by the following command:"+ , " miv install"+ , ""+ , "You can update the plugins by the following command:"+ , " miv update"+ , ""+ , "You can specify the name of plugins:"+ , " miv update plugin1 plugin2"+ , ""+ , "Normally, outdated plugins are ignored but"+ , "you can update all the plugins with trailing !:"+ , " miv update!"+ , ""+ , "You can use `miv each' to execute some commands at each plugin directory."+ , " miv each pwd"+ , " miv each git gc"+ , " miv each git diff"+ , " miv each 'echo $(du -sh .) $(basename $(pwd))'"+ , " miv each 'git diff --quiet || echo ${PWD##*/}'"+ , ""+ , "This software is released under the MIT License."+ , "This software is distributed at https://github.com/itchyny/miv."+ , "Report a bug of this software at https://github.com/itchyny/miv/issues."+ , "The author is itchyny <https://github.com/itchyny>."+ ]++levenshtein :: Eq a => [a] -> [a] -> Int+levenshtein a b = last $ foldl' f [0..length a] b+ where+ f [] _ = []+ f xs@(x:xs') c = scanl (g c) (x + 1) (zip3 a xs xs')+ g c z (d, x, y) = minimum [y + 1, z + 1, x + fromEnum (c /= d)]++suggestCommand :: String -> IO ()+suggestCommand arg = do+ let distcommands = [(levenshtein arg (unpack x), Argument (x, y)) | (Argument (x, y)) <- arguments]+ mindist = fst (minimum distcommands)+ mincommands = [y | (x, y) <- distcommands, x == mindist]+ prefixcommands = [y | y@(Argument (x, _)) <- arguments, arg `isPrefixOf` unpack x || unpack x `isPrefixOf` arg]+ containedcommands = [y | y@(Argument (x, _)) <- arguments, length arg > 1 && arg `isContainedIn` unpack x]+ putStrLn $ "Unknown command: " <> pack arg+ putStrLn "Probably:"+ mapM_ (putStrLn . (" "<>) . show) (if null prefixcommands then nub (containedcommands <> mincommands) else prefixcommands)++isContainedIn :: Eq a => [a] -> [a] -> Bool+isContainedIn xxs@(x:xs) (y:ys) = x == y && xs `isContainedIn` ys || xxs `isContainedIn` ys+isContainedIn [] _ = True+isContainedIn _ [] = False++suggestPlugin :: [Plugin] -> String -> IO ()+suggestPlugin plugin arg = do+ let distplugins = [(levenshtein arg (rtpName p), p) | p <- plugin]+ mindist = fst (minimum distplugins)+ minplugins = [y | (x, y) <- distplugins, x == mindist]+ putStrLn $ "Unknown plugin: " <> pack arg+ putStrLn "Probably:"+ mapM_ (putStrLn . (" "<>) . show) minplugins++data Update = Install | Update deriving Eq+instance ShowText Update where+ show Install = "installing"+ show Update = "updating"++data UpdateStatus+ = UpdateStatus {+ installed :: [Plugin],+ updated :: [Plugin],+ nosync :: [Plugin],+ outdated :: [Plugin],+ failed :: [Plugin]+ }++instance Semigroup UpdateStatus where+ UpdateStatus i u n o f <> UpdateStatus i' u' n' o' f'+ = UpdateStatus (i <> i') (u <> u') (n <> n') (o <> o') (f <> f')++instance Monoid UpdateStatus where+ mempty = UpdateStatus [] [] [] [] []++updatePlugin :: Update -> Maybe [String] -> Setting -> IO ()+updatePlugin update maybePlugins setting = do+ setNumCapabilities =<< getNumProcessors+ let unknownPlugins = filter (`notElem` map rtpName (plugins setting)) (fromMaybe [] maybePlugins)+ unless (null unknownPlugins) $+ mapM_ (suggestPlugin (plugins setting)) unknownPlugins+ createPluginDirectory+ dir <- pluginDirectory+ let specified p = rtpName p `elem` fromMaybe [] maybePlugins || maybePlugins == Just []+ let filterplugin p = isNothing maybePlugins || specified p+ let ps = filter filterplugin (plugins setting)+ let count xs = if length xs > 1 then "s (" <> show (length xs) <> ")" else ""+ time <- maximum <$> mapM' (lastUpdatePlugin dir) ps+ status <- fmap mconcat <$> displayConsoleRegions $+ mapConcurrently (fmap mconcat . mapM (\p -> updateOnePlugin time dir update (specified p) p)) $+ transpose $ takeWhile (not . null) $ unfoldr (Just . splitAt 6) ps+ putStrLn $ (if null (failed status) then "Success" else "Error occured") <> " in " <> show update <> "."+ unless (null (installed status)) $ do+ putStrLn $ "Installed plugin" <> count (installed status) <> ": "+ mapM_ (putStrLn . (" "<>) . name) (reverse (installed status))+ unless (null (updated status)) $ do+ putStrLn $ "Updated plugin" <> count (updated status) <> ": "+ mapM_ (putStrLn . (" "<>) . name) (reverse (updated status))+ unless (null (failed status)) $ do+ putStrLn $ "Failed plugin" <> count (failed status) <> ": "+ mapM_ (putStrLn . (" "<>) . name) (reverse (failed status))+ generatePluginCode setting+ gatherFtdetectScript setting+ generateHelpTags setting++mapM' :: P.MonadParallel m => (a -> m b) -> [a] -> m [b]+mapM' f = fmap mconcat . P.mapM (mapM f) . transpose . takeWhile (not . null) . unfoldr (Just . splitAt 32)++cleanAndCreateDirectory :: FilePath -> IO ()+cleanAndCreateDirectory dir = do+ createDirectoryIfMissing True dir+ removeDirectoryRecursive dir+ createDirectoryIfMissing True dir++generateHelpTags :: Setting -> IO ()+generateHelpTags setting = do+ dir <- pluginDirectory+ let docdir = dir <> "miv/doc/"+ cleanAndCreateDirectory docdir+ P.forM_ (map (\p -> dir <> rtpName p <> "/doc/") (plugins setting)) $ \path -> do+ exists <- doesDirectoryExist path+ when exists $+ void $ system $ unwords ["sh", "-c", "\"cd", "'" <> path <> "'", "&& cp *", "'" <> docdir <> "'", "2>/dev/null\""]+ _ <- system $ "vim -u NONE -i NONE -N -e -s -c 'helptags " <> docdir <> "' -c quit"+ putStrLn "Success in processing helptags."++lastUpdatePlugin :: FilePath -> Plugin -> IO Integer+lastUpdatePlugin dir plugin = do+ let path = dir <> rtpName plugin <> "/.git"+ exists <- doesDirectoryExist path+ if exists then lastUpdate path else return 0++updateOnePlugin :: Integer -> FilePath -> Update -> Bool -> Plugin -> IO UpdateStatus+updateOnePlugin time dir update specified plugin = do+ let path = dir <> rtpName plugin+ repo = vimScriptRepo (name plugin)+ cloneCommand = if submodule plugin then cloneSubmodule else clone+ pullCommand = if submodule plugin then pullSubmodule else pull+ putStrLn' = \region -> setConsoleRegion region . ((name plugin <> ": ") <>)+ finish' = \region -> finishConsoleRegion region . ((name plugin <> ": ") <>)+ exists <- doesDirectoryExist path+ gitstatus <- gitStatus path+ if not exists || (gitstatus /= ExitSuccess && not (sync plugin))+ then withConsoleRegion Linear $ \region -> do+ putStrLn' region "Installing"+ when exists $ removeDirectoryRecursive path+ cloneStatus <- execCommand (unpack $ name plugin) region $ cloneCommand repo path+ created <- doesDirectoryExist path+ if cloneStatus /= ExitSuccess || not created+ then return mempty { failed = [plugin] }+ else return mempty { installed = [plugin] }+ else if update == Install || not (sync plugin)+ then return mempty { nosync = [plugin] }+ else withConsoleRegion Linear $ \region -> do+ lastUpdateTime <- lastUpdate path+ if lastUpdateTime < time - 60 * 60 * 24 * 30 && not specified+ then do+ finish' region "Outdated"+ return mempty { outdated = [plugin] }+ else do+ putStrLn' region "Pulling"+ pullStatus <- execCommand (unpack $ name plugin) region $ pullCommand path+ newUpdateTime <- lastUpdate path+ return $ if pullStatus /= ExitSuccess+ then mempty { failed = [plugin] }+ else if newUpdateTime <= lastUpdateTime+ then mempty+ else mempty { updated = [plugin] }++execCommand :: String -> ConsoleRegion -> String -> IO ExitCode+execCommand pluginName region command = do+ (_, Just hout, Just herr, ph) <- createProcess (proc "sh" ["-c", command]) { std_out = CreatePipe, std_err = CreatePipe }+ outmvar <- newEmptyMVar+ errmvar <- newEmptyMVar+ _ <- forkIO $ go herr errmvar ""+ _ <- forkIO $ go hout outmvar ""+ errline <- takeMVar errmvar+ outline <- takeMVar outmvar+ code <- waitForProcess ph+ finishConsoleRegion region (pluginName ++ ": " ++ if null outline then errline else outline)+ return code+ where go h mvar lastLine = do+ e <- tryIOError $ hGetLine h+ case e of+ Left err -> if isEOFError err then putMVar mvar lastLine else return ()+ Right line -> do+ setConsoleRegion region (pluginName ++ ": " ++ line)+ threadDelay 100000+ go h mvar line++vimScriptRepo :: Text -> FilePath+vimScriptRepo pluginname | T.any (=='/') pluginname = unpack pluginname+ | otherwise = unpack $ "vim-scripts/" <> pluginname++listPlugin :: Setting -> IO ()+listPlugin setting = mapM_ putStrLn $ space $ map format $ plugins setting+ where format p = [show p, name p, pack $ gitUrl (vimScriptRepo (name p))]+ space xs =+ let max0 = maximum (map (T.length . (!!0)) xs) + 1+ max1 = maximum (map (T.length . (!!1)) xs) + 1+ in map (\(as:bs:cs:_) -> as <> T.replicate (max0 - T.length as) " " <> bs <> T.replicate (max1 - T.length bs) " " <> cs) xs++cleanDirectory :: Setting -> IO ()+cleanDirectory setting = do+ createPluginDirectory+ dir <- pluginDirectory+ createDirectoryIfMissing True dir+ cnt <- getDirectoryContents dir+ let paths = "." : ".." : "miv" : map (unpack . show) (plugins setting)+ delpath' = [ dir <> d | d <- cnt, d `notElem` paths ]+ deldir <- filterM doesDirectoryExist delpath'+ delfile <- filterM doesFileExist delpath'+ let delpath = deldir <> delfile+ if not (null delpath)+ then do putStrLn "Remove:"+ mapM_ (putStrLn . pack . (" "<>)) delpath+ putStr "Really? [y/N] "+ hFlush stdout+ c <- getChar+ when (c == 'y' || c == 'Y') $ do+ mapM_ removeDirectoryRecursive deldir+ mapM_ removeFile delfile+ else putStrLn "Clean."++saveScript :: (FilePath, Place, [Text]) -> IO ()+saveScript (dir, place, code) = do+ let relname = show place+ path = dir <> unpack relname+ isAllAscii = all (T.all (<='~')) code+ body = "" : (if isAllAscii then [] else [ "scriptencoding utf-8", "" ])+ <> [ "let s:save_cpo = &cpo"+ , "set cpo&vim"+ , "" ]+ <> code+ <> [ ""+ , "let &cpo = s:save_cpo"+ , "unlet s:save_cpo" ]+ time <- getZonedTime+ contents <- fileContents path+ when (contents /= Just body) $+ writeFile path $ unlines $+ [ "\" Filename: " <> relname+ , "\" Last Change: " <> show time+ , "\" Generated by " <> nameversion ] ++ body++fileContents :: String -> IO (Maybe [Text])+fileContents path = do+ eitherFile <- tryJust (guard . isDoesNotExistError) (openFile path ReadMode)+ case eitherFile of+ Right file -> do+ contentLines <- T.lines <$> hGetContents file+ hClose file+ return $ Just $ dropWhile ("\" " `T.isPrefixOf`) contentLines+ Left _ -> return Nothing++generatePluginCode :: Setting -> IO ()+generatePluginCode setting = do+ dir <- fmap (<>"miv/") pluginDirectory+ createDirectoryIfMissing True dir+ -- TODO: remove unused directories and files+ createDirectoryIfMissing True (dir <> "plugin/")+ createDirectoryIfMissing True (dir <> "autoload/miv/")+ createDirectoryIfMissing True (dir <> "ftplugin/")+ P.mapM_ (saveScript . (\(t, s) -> (dir, t, s)))+ (vimScriptToList (gatherScript setting))+ putStrLn "Success in generating Vim scripts of miv."++gatherFtdetectScript :: Setting -> IO ()+gatherFtdetectScript setting = do+ dir <- pluginDirectory+ cleanAndCreateDirectory (dir <> "miv/ftdetect/")+ forM_ (plugins setting) $ \plugin -> do+ let path = rtpName plugin+ exists <- doesDirectoryExist (dir <> path <> "/ftdetect")+ when exists $ do+ files <- (\\ [".", ".."]) <$> getDirectoryContents (dir <> path <> "/ftdetect")+ forM_ files $ \file ->+ copyFile (dir <> path <> "/ftdetect/" <> file) (dir <> "miv/ftdetect/" <> file)+ putStrLn "Success in gathering ftdetect scripts."++data EachStatus = EachStatus { failed' :: [Plugin] }++instance Semigroup EachStatus where+ EachStatus f <> EachStatus f' = EachStatus (f <> f')++instance Monoid EachStatus where+ mempty = EachStatus []++eachPlugin :: String -> Setting -> IO ()+eachPlugin command setting = do+ createPluginDirectory+ dir <- pluginDirectory+ status <- mconcat <$> mapM (eachOnePlugin command dir) (plugins setting)+ unless (null (failed' status)) $ do+ putStrLn "Error:"+ mapM_ (putStrLn . (" "<>) . name) (failed' status)++eachOnePlugin :: String -> FilePath -> Plugin -> IO EachStatus+eachOnePlugin command dir plugin = do+ let path = dir <> rtpName plugin+ exists <- doesDirectoryExist path+ if not exists+ then return mempty+ else do exitCode <- system (unwords ["cd", "'" <> path <> "'", "&&", command])+ case exitCode of+ ExitSuccess -> return mempty+ _ -> return $ mempty { failed' = [plugin] }++eachHelp :: IO ()+eachHelp = mapM_ putStrLn [ "Specify command:", " miv each [command]" ]++pathPlugin :: [String] -> Setting -> IO ()+pathPlugin plugins' setting = do+ let ps = filter (\p -> rtpName p `elem` plugins' || null plugins') (plugins setting)+ dir <- pluginDirectory+ forM_ ps (\plugin -> putStrLn (pack dir <> show plugin))++mainProgram :: [String] -> IO ()+mainProgram [] = printUsage+mainProgram ['-':arg] = mainProgram [arg]+mainProgram ["help"] = printUsage+mainProgram ["version"] = putStrLn nameversion+mainProgram ["install"] = getSetting >>= updatePlugin Install Nothing+mainProgram ["update"] = getSetting >>= updatePlugin Update Nothing+mainProgram ["update!"] = getSetting >>= updatePlugin Update (Just [])+mainProgram ["update", "!"] = getSetting >>= updatePlugin Update (Just [])+mainProgram ["each"] = eachHelp+mainProgram ["list"] = getSetting >>= listPlugin+mainProgram ["command"] = commandHelp+mainProgram ["clean"] = getSetting >>= cleanDirectory+mainProgram ["edit"] = getSettingFile >>= maybe (return ()) (($) void . system . ("vim "<>))+mainProgram ["generate"] = getSetting >>= generatePluginCode+mainProgram ["ftdetect"] = getSetting >>= gatherFtdetectScript+mainProgram ["helptags"] = getSetting >>= generateHelpTags+mainProgram ["path"] = getSetting >>= pathPlugin []+mainProgram [arg] = suggestCommand arg+mainProgram ("install":args) = getSetting >>= updatePlugin Install (Just args)+mainProgram ("update":args) = getSetting >>= updatePlugin Update (Just args)+mainProgram ("each":args) = getSetting >>= eachPlugin (unwords args)+mainProgram ("path":args) = getSetting >>= pathPlugin args+mainProgram (('-':arg):args) = mainProgram (arg:args)+mainProgram _ = printUsage++main :: IO ()+main = getArgs >>= mainProgram
+ src/Mapping.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE OverloadedStrings #-}+module Mapping where++import Data.Monoid ((<>))+import Data.Text (Text, unwords, null)+import Prelude hiding (show, unwords, null)++import Mode+import ShowText++data MapUnique = MapUnique | MapNoUnique+ deriving Eq++instance ShowText MapUnique where+ show MapUnique = "<unique>"+ show MapNoUnique = ""++data MapSilent = MapSilent | MapNoSilent+ deriving Eq++instance ShowText MapSilent where+ show MapSilent = "<silent>"+ show MapNoSilent = ""++data Mapping =+ Mapping { mapName :: Text+ , mapRepText :: Text+ , mapUnique :: MapUnique+ , mapSilent :: MapSilent+ , mapMode :: Mode+ } deriving Eq++instance ShowText Mapping where+ show m = unwords (filter (not . null)+ [ show (mapMode m) <> "noremap"+ , show (mapUnique m)+ <> show (mapSilent m)+ , mapName m+ , mapRepText m+ ])++defaultMapping :: Mapping+defaultMapping+ = Mapping { mapName = ""+ , mapRepText = ""+ , mapUnique = MapUnique+ , mapSilent = MapSilent+ , mapMode = NormalMode+ }+
+ src/Mode.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE OverloadedStrings #-}+module Mode where++import Data.Text (unpack)++import ReadText+import ShowText++data Mode = NormalMode+ | VisualMode+ | SelectMode+ | InsertMode+ | CmdlineMode+ | ExMode+ | OperatorPendingMode+ | ReplaceMode+ | VirtualReplaceMode+ | InsertNormalMode+ | InesrtVisualMode+ | InsertSelectMode+ deriving Eq++instance ShowText Mode where+ show NormalMode = "n"+ show VisualMode = "v"+ show SelectMode = "s"+ show InsertMode = "i"+ show CmdlineMode = "c"+ show ExMode = "ex"+ show OperatorPendingMode = "o"+ show ReplaceMode = "r"+ show VirtualReplaceMode = "vr"+ show InsertNormalMode = "in"+ show InesrtVisualMode = "iv"+ show InsertSelectMode = "is"++instance ReadText Mode where+ read "n" = NormalMode+ read "v" = VisualMode+ read "s" = SelectMode+ read "i" = InsertMode+ read "c" = CmdlineMode+ read "ex" = ExMode+ read "o" = OperatorPendingMode+ read "r" = ReplaceMode+ read "vr" = VirtualReplaceMode+ read "in" = InsertNormalMode+ read "iv" = InesrtVisualMode+ read "is" = InsertSelectMode+ read m = error $ "unknown mode: " ++ unpack m
+ src/Plugin.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE OverloadedStrings, RecordWildCards #-}+module Plugin where++import Control.Applicative ((<|>))+import Data.Aeson+import qualified Data.Text as T+import Data.Text (Text, unpack)++import ShowText++data Plugin =+ Plugin { name :: Text+ , filetypes :: [Text]+ , commands :: [Text]+ , functions :: [Text]+ , mappings :: [Text]+ , mapmodes :: [Text]+ , insert :: Bool+ , enable :: Text+ , sync :: Bool+ , mapleader :: Text+ , script :: [Text]+ , after :: [Text]+ , before :: [Text]+ , dependon :: [Text]+ , dependedby :: [Text]+ , loadafter :: [Text]+ , loadbefore :: [Text]+ , submodule :: Bool+ } deriving (Eq, Ord)++instance ShowText Plugin where+ show plg = subst (name plg)+ where subst s | T.any (=='/') s = subst (T.tail $ T.dropWhile (/='/') s)+ | ".vim" `T.isSuffixOf` s = T.take (T.length s - 4) s+ | "-vim" `T.isSuffixOf` s = T.take (T.length s - 4) s+ | "vim-" `T.isPrefixOf` s = T.drop 4 s+ | otherwise = T.filter (`notElem`("!?;:/<>()[]{}|~'\"" :: String)) s++rtpName :: Plugin -> String+rtpName = unpack . ShowText.show++instance FromJSON Plugin where+ parseJSON = withObject "plugin" $ \o -> do+ name <- o .:? "name" .!= ""+ filetypes <- o .: "filetype" <|> (fmap return <$> o .:? "filetype") .!= []+ commands <- o .: "command" <|> (fmap return <$> o .:? "command") .!= []+ functions <- o .: "function" <|> (fmap return <$> o .:? "function") .!= []+ mappings <- o .: "mapping" <|> (fmap return <$> o .:? "mapping") .!= []+ mapmodes <- o .: "mapmode" <|> (fmap return <$> o .:? "mapmode") .!= []+ mapleader <- o .:? "mapleader" .!= ""+ insert <- o .:? "insert" .!= False+ enable <- o .:? "enable" .!= ""+ sync <- o .:? "sync" .!= True+ script <- T.lines <$> o .:? "script" .!= ""+ after <- T.lines <$> o .:? "after" .!= ""+ before <- T.lines <$> o .:? "before" .!= ""+ dependon <- o .: "dependon" <|> (fmap return <$> o .:? "dependon") .!= []+ dependedby <- o .: "dependedby" <|> (fmap return <$> o .:? "dependedby") .!= []+ loadafter <- o .: "loadafter" <|> (fmap return <$> o .:? "loadafter") .!= []+ loadbefore <- o .: "loadbefore" <|> (fmap return <$> o .:? "loadbefore") .!= []+ submodule <- o .:? "submodule" .!= False+ return Plugin {..}
+ src/ReadText.hs view
@@ -0,0 +1,10 @@+{-# LANGUAGE FlexibleInstances, IncoherentInstances, UndecidableInstances #-}+module ReadText where++import Data.Text++class ReadText a where+ read :: Text -> a++instance Read a => ReadText a where+ read = Prelude.read . unpack
+ src/Setting.hs view
@@ -0,0 +1,30 @@+{-# LANGUAGE OverloadedStrings, RecordWildCards #-}+module Setting where++import Data.Aeson+import Data.Function (on)+import qualified Data.HashMap.Lazy as HM+import Data.List (sortBy)+import Data.Text (Text, lines)+import Prelude hiding (lines)++import Plugin++data Setting =+ Setting { plugins :: [Plugin]+ , filetype :: HM.HashMap Text [Text]+ , before :: [Text]+ , after :: [Text]+ } deriving Eq++instance FromJSON Setting where+ parseJSON = withObject "setting" $ \o -> do+ plugins <- pluginHashMapToList <$> o .:? "plugin" .!= HM.empty+ filetype <- HM.map lines <$> o .:? "filetype" .!= HM.empty+ before <- lines <$> o .:? "before" .!= ""+ after <- lines <$> o .:? "after" .!= ""+ return Setting {..}+ where pluginHashMapToList = sortWith name . HM.foldlWithKey' (\a k v -> v { name = k } : a) []++sortWith :: Ord b => (a -> b) -> [a] -> [a]+sortWith = sortBy . on compare
+ src/ShowText.hs view
@@ -0,0 +1,10 @@+{-# LANGUAGE FlexibleInstances, IncoherentInstances, UndecidableInstances #-}+module ShowText where++import Data.Text++class ShowText a where+ show :: a -> Text++instance Show a => ShowText a where+ show = pack . Prelude.show
+ src/VimScript.hs view
@@ -0,0 +1,351 @@+{-# LANGUAGE DeriveGeneric, OverloadedStrings #-}+module VimScript where++import Data.Char (isAlpha, isAlphaNum, toLower)+import Data.Hashable+import qualified Data.HashMap.Lazy as HM+import Data.List (foldl')+import Data.Maybe (mapMaybe)+import Data.Monoid ((<>))+import Data.Text (Text, unwords, singleton)+import qualified Data.Text as T+import GHC.Generics (Generic)+import Prelude hiding (show, unwords, read)++import qualified Command as C+import qualified Mapping as M+import Mode+import qualified Plugin as P+import ReadText+import qualified Setting as S+import ShowText++data VimScript = VimScript (HM.HashMap Place [Text])+ deriving (Eq)++data Place = Plugin+ | Autoload Text+ | Ftplugin Text+ deriving (Eq, Generic)++instance Hashable Place where+ hashWithSalt a Plugin = a `hashWithSalt` (0 :: Int) `hashWithSalt` ("" :: Text)+ hashWithSalt a (Autoload s) = a `hashWithSalt` (1 :: Int) `hashWithSalt` s+ hashWithSalt a (Ftplugin s) = a `hashWithSalt` (2 :: Int) `hashWithSalt` s++instance ShowText Place where+ show Plugin = "plugin/miv.vim"+ show (Autoload "") = "autoload/miv.vim"+ show (Autoload s) = "autoload/miv/" <> s <> ".vim"+ show (Ftplugin s) = "ftplugin/" <> s <> ".vim"++autoloadSubdirName :: Place -> Maybe Text+autoloadSubdirName (Autoload "") = Nothing+autoloadSubdirName (Autoload s) = Just s+autoloadSubdirName _ = Nothing++isFtplugin :: Place -> Bool+isFtplugin (Ftplugin _) = True+isFtplugin _ = False++vimScriptToList :: VimScript -> [(Place, [Text])]+vimScriptToList (VimScript x) = HM.toList x++instance Semigroup VimScript where+ VimScript x <> VimScript y+ | HM.null x = VimScript y+ | HM.null y = VimScript x+ | otherwise = VimScript (HM.unionWith concat' x y)+ where+ concat' a [] = a+ concat' [] b = b+ concat' a b = a <> [""] <> b++instance Monoid VimScript where+ mempty = VimScript HM.empty++gatherScript :: S.Setting -> VimScript+gatherScript setting = addAutoloadNames+ $ beforeScript setting+ <> gatherBeforeAfterScript plugins+ <> gather "dependon" P.dependon plugins+ <> gather "dependedby" P.dependedby plugins+ <> gather "mappings" P.mappings plugins+ <> gather "mapmodes" P.mapmodes plugins+ <> gatherFuncUndefined setting+ <> pluginLoader+ <> mappingLoader+ <> commandLoader+ <> foldl' (<>) mempty (map pluginConfig plugins)+ <> filetypeLoader setting+ <> gatherInsertEnter setting+ <> filetypeScript (S.filetype setting)+ <> afterScript setting+ where plugins = S.plugins setting++gatherBeforeAfterScript :: [P.Plugin] -> VimScript+gatherBeforeAfterScript x = insertAuNameMap $ gatherScripts x (mempty, HM.empty)+ where+ insertAuNameMap :: (VimScript, HM.HashMap Text Text) -> VimScript+ insertAuNameMap (vs, hm) = VimScript (HM.singleton (Autoload "") $+ [ "let s:autoload = {" ]+ <> [ " \\ " <> singleQuote k <> ": " <> singleQuote a <> "," | (k, a) <- HM.toList hm ]+ <> [ " \\ }" ]) <> vs+ gatherScripts :: [P.Plugin] -> (VimScript, HM.HashMap Text Text) -> (VimScript, HM.HashMap Text Text)+ gatherScripts (p:ps) (vs, hm)+ | null (P.before p) && null (P.after p) = gatherScripts ps (vs, hm)+ | otherwise = gatherScripts ps (vs <> vs', HM.insert name hchar hm)+ where+ name = T.filter isAlphaNum (T.toLower (show p))+ hchar | null (loadScript p) = maybe "_" singleton $ getHeadChar $ show p+ | otherwise = "_"+ funcname str = "miv#" <> hchar <> "#" <> str <> "_" <> name+ au = Autoload hchar+ vs' = VimScript $ HM.singleton au $ wrapFunction (funcname "before") (P.before p)+ <> wrapFunction (funcname "after") (P.after p)+ gatherScripts [] (vs, hm) = (vs, hm)++addAutoloadNames :: VimScript -> VimScript+addAutoloadNames h@(VimScript hm)+ = VimScript (HM.singleton (Autoload "")+ [ "let s:autoloads = { " <> T.intercalate ", " (((<>": 1") . singleQuote)+ <$> mapMaybe autoloadSubdirName (HM.keys hm)) <> " }"])+ <> h++gather :: Text -> (P.Plugin -> [Text]) -> [P.Plugin] -> VimScript+gather name f plg+ = VimScript (HM.singleton (Autoload "") $+ [ "let s:" <> name <> " = {" ]+ <> [ " \\ " <> singleQuote (show p)+ <> ": [ " <> T.intercalate ", " (map singleQuote (f p)) <> " ],"+ | p <- plg, not (null (f p)) ]+ <> [ " \\ }" ])++pluginConfig :: P.Plugin -> VimScript+pluginConfig plg+ = VimScript (HM.singleton Plugin $ wrapInfo $+ wrapEnable plg $ mapleader <> gatherCommand plg <> gatherMapping plg <> P.script plg <> loadScript plg)+ where+ wrapInfo [] = []+ wrapInfo str = ("\" " <> P.name plg) : str+ mapleader = (\s -> if T.null s then [] else ["let g:mapleader = " <> singleQuote s]) (P.mapleader plg)++loadScript :: P.Plugin -> [Text]+loadScript plg+ | all null [ P.commands plg, P.mappings plg, P.functions plg, P.filetypes plg+ , P.loadafter plg, P.loadbefore plg ] && not (P.insert plg)+ = ["call miv#load(" <> singleQuote (show plg) <> ")"]+ | otherwise = []++gatherCommand :: P.Plugin -> [Text]+gatherCommand plg+ | not (null (P.commands plg))+ = [show (C.defaultCommand { C.cmdName = c+ , C.cmdRepText = unwords ["call miv#command(" <> singleQuote (show plg) <> ","+ , singleQuote c <> ","+ , singleQuote "<bang>" <> ","+ , "<q-args>,"+ , "expand('<line1>'),"+ , "expand('<line2>'))" ] }) | c <- P.commands plg]+ | otherwise = []++gatherMapping :: P.Plugin -> [Text]+gatherMapping plg+ | not (null (P.mappings plg))+ = let genMapping+ = [\mode ->+ M.defaultMapping+ { M.mapName = c+ , M.mapRepText = escape mode <> ":<C-u>call miv#mapping("+ <> singleQuote (show plg) <> ", "+ <> singleQuote c <> ", "+ <> singleQuote (show mode) <> ")<CR>"+ , M.mapMode = mode } | c <- P.mappings plg]+ escape m = if m `elem` [ InsertMode, OperatorPendingMode ] then "<ESC>" else ""+ modes = if null (P.mapmodes plg) then [NormalMode, VisualMode] else map read (P.mapmodes plg)+ in concat [map (show . f) modes | f <- genMapping]+ | otherwise = []++beforeScript :: S.Setting -> VimScript+beforeScript setting = VimScript (HM.singleton Plugin (S.before setting))++afterScript :: S.Setting -> VimScript+afterScript setting = VimScript (HM.singleton Plugin (S.after setting))++filetypeLoader :: S.Setting -> VimScript+filetypeLoader setting+ = HM.foldrWithKey f mempty (filetypeLoadPlugins (S.plugins setting) HM.empty)+ where+ f ft plg val =+ case getHeadChar ft of+ Nothing -> val+ Just c ->+ let funcname = "miv#" <> singleton c <> "#load_" <> T.filter isAlphaNum (T.toLower ft)+ in val+ <> VimScript (HM.singleton (Autoload (singleton c))+ (("function! " <> funcname <> "() abort")+ : " setl ft="+ : concat [wrapEnable b+ [ " call miv#load(" <> singleQuote (show b) <> ")"] | b <- plg]+ <> [ " autocmd! MivFileTypeLoad" <> ft+ , " setl ft=" <> ft+ , " silent! doautocmd FileType " <> ft+ , "endfunction"+ ]))+ <> VimScript (HM.singleton Plugin+ [ "augroup MivFileTypeLoad" <> ft+ , " autocmd!"+ , " autocmd FileType " <> ft <> " call " <> funcname <> "()"+ , "augroup END"+ ])++filetypeLoadPlugins :: [P.Plugin] -> HM.HashMap Text [P.Plugin] -> HM.HashMap Text [P.Plugin]+filetypeLoadPlugins (b:plugins) fts+ | not (null (P.filetypes b))+ = filetypeLoadPlugins plugins (foldr (flip (HM.insertWith (<>)) [b]) fts (P.filetypes b))+ | otherwise = filetypeLoadPlugins plugins fts+filetypeLoadPlugins [] fts = fts++gatherInsertEnter :: S.Setting -> VimScript+gatherInsertEnter setting+ = VimScript (HM.singleton Plugin (f [ p | p <- S.plugins setting, P.insert p ]))+ where f [] = []+ f plgs = "\" InsertEnter"+ : "function! s:insertEnter() abort"+ : [ " call miv#load(" <> singleQuote (show p) <> ")" | p <- plgs :: [P.Plugin] ]+ <> [ " autocmd! MivInsertEnter"+ , " silent! doautocmd InsertEnter"+ , "endfunction"+ , ""+ , "augroup MivInsertEnter"+ , " autocmd!"+ , " autocmd InsertEnter * call s:insertEnter()"+ , "augroup END"+ , "" ]++gatherFuncUndefined :: S.Setting -> VimScript+gatherFuncUndefined setting+ = VimScript (HM.singleton Plugin (f [ p | p <- S.plugins setting, not (null (P.functions p))]))+ where f [] = []+ f plgs = "\" FuncUndefined"+ : "function! s:funcUndefined() abort"+ : " let f = expand('<amatch>')"+ : concat [+ [ " if f =~# " <> singleQuote q+ , " call miv#load(" <> singleQuote (show p) <> ")"+ , " endif" ] | (p, q) <- concatMap (\q -> map ((,) q) (P.functions q)) plgs]+ <> [ "endfunction"+ , ""+ , "augroup MivFuncUndefined"+ , " autocmd!"+ , " autocmd FuncUndefined * call s:funcUndefined()"+ , "augroup END"+ , "" ]++wrapEnable :: P.Plugin -> [Text] -> [Text]+wrapEnable plg str+ | null str = []+ | T.null (P.enable plg) = str+ | P.enable plg == "0" = []+ | otherwise = (indent <> "if " <> P.enable plg)+ : map (" "<>) str+ <> [indent <> "endif"]+ where indent = T.takeWhile (==' ') (head str)++singleQuote :: Text -> Text+singleQuote str = "'" <> str <> "'"++filetypeScript :: HM.HashMap Text [Text] -> VimScript+filetypeScript =+ HM.foldrWithKey (\ft scr val -> val <> VimScript (HM.singleton (Ftplugin ft) scr)) mempty++wrapFunction :: Text -> [Text] -> [Text]+wrapFunction funcname script =+ ["function! " <> funcname <> "() abort"]+ <> map (" "<>) script+ <> ["endfunction"]++getHeadChar :: Text -> Maybe Char+getHeadChar xs+ | T.null xs = Nothing+ | otherwise = let x = T.head xs in if isAlpha x then Just (toLower x) else getHeadChar (T.tail xs)++mappingLoader :: VimScript+mappingLoader = VimScript (HM.singleton (Autoload "")+ [ "function! miv#mapping(name, mapping, mode) abort"+ , " call miv#load(a:name)"+ , " if a:mode ==# 'v' || a:mode ==# 'x'"+ , " call feedkeys('gv', 'n')"+ , " elseif a:mode ==# 'o'"+ , " call feedkeys(\"\\<Esc>\", 'n')"+ , " call feedkeys(v:operator, 'm')"+ , " endif"+ , " call feedkeys((v:count ? v:count : '') . substitute(a:mapping, '<Plug>', \"\\<Plug>\", 'g'), 'm')"+ , " return ''"+ , "endfunction"+ ])++commandLoader :: VimScript+commandLoader = VimScript (HM.singleton (Autoload "")+ [ "function! miv#command(name, command, bang, args, line1, line2) abort"+ , " silent! execute 'delcommand' a:command"+ , " call miv#load(a:name)"+ , " let range = a:line1 != a:line2 ? a:line1.','.a:line2 : ''"+ , " try"+ , " exec range.a:command.a:bang a:args"+ , " catch /^Vim\\%((\\a\\+)\\)\\=:E481:/"+ , " exec a:command.a:bang a:args"+ , " endtry"+ , "endfunction"+ ])++pluginLoader :: VimScript+pluginLoader = VimScript (HM.singleton (Autoload "")+ [ "let s:loaded = {}"+ , "let s:path = expand('<sfile>:p:h:h')"+ , "let s:mivpath = expand('<sfile>:p:h:h:h') . '/'"+ , "function! miv#load(name) abort"+ , " if has_key(s:loaded, a:name)"+ , " return"+ , " endif"+ , " let s:loaded[a:name] = 1"+ , " for n in get(s:dependon, a:name, [])"+ , " call miv#load(n)"+ , " endfor"+ , " for mode in get(s:mapmodes, a:name, [ 'n', 'v' ])"+ , " for mapping in get(s:mappings, a:name, [])"+ , " silent! execute mode . 'unmap' mapping"+ , " endfor"+ , " endfor"+ , " let name = substitute(tolower(a:name), '[^a-z0-9]', '', 'g')"+ , " let au = has_key(s:autoload, name) && get(s:autoloads, s:autoload[name])"+ , " if au"+ , " call miv#{s:autoload[name]}#before_{name}()"+ , " endif"+ , " let newrtp = s:mivpath . a:name . '/'"+ , " if !isdirectory(newrtp)"+ , " return"+ , " endif"+ , " let rtps = split(&rtp, ',')"+ , " let i = index(map(deepcopy(rtps), 'resolve(v:val)'), s:path)"+ , " if i < 0"+ , " let mivpath = get(filter(deepcopy(rtps), 'v:val =~# \"miv.\\\\?$\"'), 0, '')"+ , " let i = max([0, index(deepcopy(rtps), mivpath)])"+ , " endif"+ , " let &rtp = join(insert(rtps, newrtp, i), ',')"+ , " if isdirectory(newrtp . 'after/')"+ , " exec 'set rtp+=' . newrtp . 'after/'"+ , " endif"+ , " for dir in filter(['plugin', 'after/plugin'], 'isdirectory(newrtp . v:val)')"+ , " for file in split(glob(newrtp . dir . '/**/*.vim'), '\\n')"+ , " silent! source `=file`"+ , " endfor"+ , " endfor"+ , " if au"+ , " call miv#{s:autoload[name]}#after_{name}()"+ , " endif"+ , " for n in get(s:dependedby, a:name, [])"+ , " call miv#load(n)"+ , " endfor"+ , "endfunction"+ ])
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}