{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} -- | This is the main module of Yi, called with configuration from the user. -- Here we mainly process command line arguments. module Yi.Main ( -- * Static main main, -- * Command line processing do_args, ConsoleConfig(..), ) where import Control.Monad import Data.Char import Data.Monoid import qualified Data.Text as T import Data.List (intercalate) import Data.Version (showVersion) import Lens.Micro.Platform (view) import System.Console.GetOpt import System.Exit #ifndef HLINT #include "ghcconfig.h" #endif import Yi.Buffer import Yi.Config import Yi.Core (startEditor) import Yi.Debug import Yi.Editor import Yi.File import Yi.Keymap import Yi.Option (YiOption, OptionError(..), yiCustomOptions) import Yi.Paths (getConfigDir) import Paths_yi_core -- | Configuration information which can be set in the command-line, but not -- in the user's configuration file. data ConsoleConfig = ConsoleConfig { ghcOptions :: [String], selfCheck :: Bool, userConfigDir :: IO FilePath } defaultConsoleConfig :: ConsoleConfig defaultConsoleConfig = ConsoleConfig { ghcOptions = [], selfCheck = False, userConfigDir = Yi.Paths.getConfigDir } -- --------------------------------------------------------------------- -- | Argument parsing. Pretty standard. data Opts = Help | Version | LineNo String | EditorNm String | File String | Frontend String | ConfigFile String | SelfCheck | GhcOption String | Debug | OpenInTabs | CustomNoArg YiOption | CustomReqArg (String -> YiOption) String | CustomOptArg (Maybe String -> YiOption) (Maybe String) -- | List of editors for which we provide an emulation. editors :: [(String,Config -> Config)] editors = [] builtinOptions :: [OptDescr Opts] builtinOptions = [ Option [] ["self-check"] (NoArg SelfCheck) "Run self-checks" , Option ['f'] ["frontend"] (ReqArg Frontend "FRONTEND") frontendHelp , Option ['y'] ["config-file"] (ReqArg ConfigFile "PATH") "Specify a folder containing a configuration yi.hs file" , Option ['V'] ["version"] (NoArg Version) "Show version information" , Option ['h'] ["help"] (NoArg Help) "Show this help" , Option [] ["debug"] (NoArg Debug) "Write debug information in a log file" , Option ['l'] ["line"] (ReqArg LineNo "NUM") "Start on line number" , Option [] ["as"] (ReqArg EditorNm "EDITOR") editorHelp , Option [] ["ghc-option"] (ReqArg GhcOption "OPTION") "Specify option to pass to ghc when compiling configuration file" , Option [openInTabsShort] [openInTabsLong] (NoArg OpenInTabs) "Open files in tabs" ] where frontendHelp = "Select frontend" editorHelp = "Start with editor keymap, where editor is one of:\n" ++ (intercalate ", " . fmap fst) editors convertCustomOption :: OptDescr YiOption -> OptDescr Opts convertCustomOption (Option short long desc help) = Option short long desc' help where desc' = convertCustomArgDesc desc convertCustomArgDesc :: ArgDescr YiOption -> ArgDescr Opts convertCustomArgDesc (NoArg o) = NoArg (CustomNoArg o) convertCustomArgDesc (ReqArg f s) = ReqArg (CustomReqArg f) s convertCustomArgDesc (OptArg f s) = OptArg (CustomOptArg f) s customOptions :: Config -> [OptDescr Opts] customOptions = fmap convertCustomOption . view yiCustomOptions openInTabsShort :: Char openInTabsShort = 'p' openInTabsLong :: String openInTabsLong = "open-in-tabs" -- | usage string. usage :: [OptDescr Opts] -> T.Text usage opts = T.pack $ usageInfo "Usage: yi [option...] [file]" opts versinfo :: T.Text versinfo = "yi " <> T.pack (showVersion version) -- | Transform the config with options do_args :: Bool -> Config -> [String] -> Either OptionError (Config, ConsoleConfig) do_args ignoreUnknown cfg args = let options = customOptions cfg ++ builtinOptions in case getOpt' (ReturnInOrder File) options args of (os, [], [], []) -> handle options os (os, _, u:us, []) -> if ignoreUnknown then handle options os else Prelude.error $ "unknown arguments: " ++ intercalate ", " (u:us) (_os, _ex, _ey, errs) -> Prelude.error (concat errs) where shouldOpenInTabs = ("--" ++ openInTabsLong) `elem` args || ('-':[openInTabsShort]) `elem` args handle options os = foldM (getConfig options shouldOpenInTabs) (cfg, defaultConsoleConfig) (reverse os) -- | Update the default configuration based on a command-line option. getConfig :: [OptDescr Opts] -> Bool -> (Config, ConsoleConfig) -> Opts -> Either OptionError (Config, ConsoleConfig) getConfig options shouldOpenInTabs (cfg, cfgcon) opt = case opt of Frontend _ -> Prelude.error "Panic: frontend not found" Help -> Left $ OptionError (usage options) ExitSuccess Version -> Left $ OptionError versinfo ExitSuccess Debug -> return (cfg { debugMode = True }, cfgcon) LineNo l -> case startActions cfg of x : xs -> return (cfg { startActions = x:makeAction (gotoLn (read l)):xs }, cfgcon) [] -> Prelude.error "The `-l' option must come after a file argument" File filename -> if shouldOpenInTabs && not (null (startActions cfg)) then prependActions [YiA $ openNewFile filename, EditorA newTabE] else prependAction $ openNewFile filename EditorNm emul -> case lookup (fmap toLower emul) editors of Just modifyCfg -> return (modifyCfg cfg, cfgcon) Nothing -> Prelude.error $ "Unknown emulation: " ++ show emul GhcOption ghcOpt -> return (cfg, cfgcon { ghcOptions = ghcOptions cfgcon ++ [ghcOpt] }) ConfigFile f -> return (cfg, cfgcon { userConfigDir = return f }) CustomNoArg o -> do cfg' <- o cfg return (cfg', cfgcon) CustomReqArg f s -> do cfg' <- f s cfg return (cfg', cfgcon) CustomOptArg f s -> do cfg' <- f s cfg return (cfg', cfgcon) _ -> return (cfg, cfgcon) where prependActions as = return (cfg { startActions = fmap makeAction as ++ startActions cfg }, cfgcon) prependAction a = return (cfg { startActions = makeAction a : startActions cfg}, cfgcon) -- --------------------------------------------------------------------- -- | Static main. This is the front end to the statically linked -- application, and the real front end, in a sense. 'dynamic_main' calls -- this after setting preferences passed from the boot loader. -- main :: (Config, ConsoleConfig) -> Maybe Editor -> IO () main (cfg, _cfgcon) state = do when (debugMode cfg) $ initDebug ".yi.dbg" startEditor cfg state