packages feed

yi-0.3: Yi/Main.hs

-- Copyright (c) Tuomo Valkonen 2004.
-- Copyright (c) Don Stewart 2004-5.
-- Copyright (c) Jean-Philippe Bernardy 2006,2007.
--
-- This program is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--

--
-- | This is the real main module of Yi, and is shared between
-- Main.hs (the static binary), and dynamically loaded by Boot.hs.
-- We take any config arguments from the boot loader (if that is how we
-- are being invoked) parse command line args, initialise the ui, before
-- jumping into an event loop.
--

module Yi.Main (main, Kernel) where

import Prelude hiding (error)

import qualified Yi.Core    as Core
import qualified Yi.Dired   as Dired
import qualified Yi.Buffer  as Buffer
import qualified Yi.Keymap  as Keymap
import qualified Yi.Eval    as Eval
import qualified Yi.Editor  as Editor
import qualified Yi.Keymap.Emacs  as Emacs
import qualified Yi.Keymap.Vim  as Vim
import Yi.UI.Common (UIBoot)
import Yi.Kernel
import Yi.Debug

import Yi.Interact hiding (write)

#ifdef FRONTEND_COCOA
import qualified Yi.UI.Cocoa
import Foundation (withAutoreleasePool)
#endif
#ifdef FRONTEND_GTK
import qualified Yi.UI.Gtk
#endif
#ifdef FRONTEND_VTY
import qualified Yi.UI.Vty
#endif

import Data.Char
import Data.List                ( intersperse )

import System.Console.GetOpt
import System.Environment       ( getArgs )
import System.Exit

#include "ghcconfig.h"


frontends :: [(String,UIBoot)]
frontends =
#ifdef FRONTEND_COCOA
   ("cocoa", Yi.UI.Cocoa.start) :
#endif
#ifdef FRONTEND_GTK
   ("gtk", Yi.UI.Gtk.start) :
#endif
#ifdef FRONTEND_VTY
   ("vty", Yi.UI.Vty.start) :
#endif
   []

frontendNames :: [String]
frontendNames = map fst' frontends
  where fst' :: (a,UIBoot) -> a
        fst' (x,_) = x

-- ---------------------------------------------------------------------
-- | Argument parsing. Pretty standard.

data Opts = Help
          | Version
          | OptIgnore String
          | LineNo String
          | EditorNm String
          | File String
          | Frontend String
          | ConfigFile String

-- | List of editors for which we provide an emulation.
editors :: [(String,Keymap.Keymap)]
editors = [("emacs", Emacs.keymap),
           ("vim", Vim.keymap)]

options :: [OptDescr Opts]
options = [
    Option ['f']  ["frontend"]    (ReqArg Frontend "[frontend]")
        ("Select frontend, which can be one of:\n" ++
         (concat . intersperse ", ") frontendNames),
    Option ['y']  ["config-file"] (ReqArg ConfigFile  "path") "Specify a configuration file",
    Option ['V']  ["version"]     (NoArg Version) "Show version information",
    Option ['B']  ["libdir"]      (ReqArg OptIgnore "libdir") "Path to runtime libraries",
    Option ['b']  ["bindir"]      (ReqArg OptIgnore "bindir") "Path to runtime library binaries\n(default: libdir)",
    Option ['h']  ["help"]        (NoArg Help)    "Show this help",
    Option ['l']  ["line"]        (ReqArg LineNo "[num]") "Start on line number",
    Option []     ["as"]          (ReqArg EditorNm "[editor]")
        ("Start with editor keymap, where editor is one of:\n" ++
                (concat . intersperse ", " . map fst) editors)
    ]

-- | usage string.
usage, versinfo :: IO ()
usage    = putStr   $ usageInfo "Usage: yi [option...] [file]" options

versinfo = putStrLn $ "yi 0.3.0"
-- TODO: pull this out of the cabal configuration

-- | deal with real options
do_opt :: Opts -> IO (Keymap.YiM ())
do_opt o = case o of
    Frontend f     -> case map toLower f `elem` frontendNames of
                        False -> do putStrLn ("Unknown frontend: " ++ show f)
                                    exitWith (ExitFailure 1)
                        _     -> return (return ()) -- Processed differently
    OptIgnore _   -> return (return ())
    ConfigFile _  -> return (return ())
    Help          -> usage    >> exitWith ExitSuccess
    Version       -> versinfo >> exitWith ExitSuccess
    LineNo l      -> return (Keymap.withBuffer (Buffer.gotoLn (read l)) >> return ())
    File file     -> return (Dired.fnewE file)
    EditorNm emul -> case lookup (map toLower emul) editors of
                       Just km -> return $ Core.changeKeymap km
                       Nothing -> do putStrLn ("Unknown emulation: " ++ show emul)
                                     exitWith (ExitFailure 1)

-- | everything that is left over
do_args :: [String] -> IO (Core.StartConfig, [Keymap.YiM ()])
do_args args =
    case (getOpt (ReturnInOrder File) options args) of
        (o, [], []) ->  do
            let frontend = head $ [f | Frontend   f <- o] ++ [head frontendNames]
                yiConfig = head $ [f | ConfigFile f <- o] ++ ["YiConfig"]
                config   = Core.StartConfig { Core.startFrontEnd   = case lookup frontend frontends of
                                                                       Nothing -> error "Panic: frontend not found"
                                                                       Just x -> x
                                            , Core.startConfigFile = yiConfig
                                            }
            actions <- mapM do_opt o
            return (config, actions)
        (_, _, errs) -> do putStrLn (concat errs)
                           exitWith (ExitFailure 1)

startConsole :: Keymap.YiM ()
startConsole = do
  console <- Core.withEditor $ Editor.getBufferWithName "*console*"
  Keymap.setBufferKeymap console (Eval.consoleKeymap <||)

openScratchBuffer :: Keymap.YiM ()
openScratchBuffer = Core.withEditor $ do     -- emacs-like behaviour
      Editor.newBufferE "*scratch*"
                   ("-- This buffer is for notes you don't want to save, and for haskell evaluation\n" ++
                    "-- If you want to create a file, open that file,\n" ++
                    "-- then enter the text in that file's own buffer.\n\n")
      return ()

-- ---------------------------------------------------------------------
-- | 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 :: Kernel -> IO ()
#ifdef FRONTEND_COCOA
main kernel = withAutoreleasePool $ do
#else
main kernel = do
#endif
    (config, mopts) <- do_args =<< getArgs
    Core.startEditor config kernel Nothing (startConsole : openScratchBuffer : mopts)