-----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Actions.Eval
-- Copyright   :  (c) 2009 Daniel Schoepe
-- License     :  BSD3-style (see LICENSE)
--
-- Maintainer  :  Daniel Schoepe <daniel.schoepe@gmail.com>
-- Stability   :  unstable
-- Portability :  unportable
--
-- Evaluate haskell expressions at runtime in the running xmonad instance.
--
-----------------------------------------------------------------------------

module XMonad.Actions.Eval (
                            -- * Usage
                            -- $usage

                            -- * Documentation
                            -- $documentation

                             evalExpression
                           , evalExpressionWithReturn
                           , EvalConfig(..)
                           , defaultEvalConfig
                           ) where

import XMonad.Core
import XMonad.Util.Run
import Language.Haskell.Interpreter
import Data.List
import System.Environment

-- $usage
-- This module provides functions to evaluate haskell expressions at runtime
-- To use it, bind a key to evalExpression, for example in combination with a prompt:
--
-- > import XMonad
-- > import XMonad.Actions.Eval
-- > import XMonad.Prompt.Input
-- > ..
-- >   , ((modMask,xK_t), inputPrompt defaultXPConfig "Eval" >>= flip whenJust (evalExpression defaultEvalConfig))
--
-- For detailed instructions on editing your key bindings, see
-- "XMonad.Doc.Extending#Editing_key_bindings".

-- $documentation

-- In here due to the apparent lack of a replace function in the standard library.
-- (Used for correctly displaying newlines in error messages)
replace :: Eq a => [a] -> [a] -> [a] -> [a]
replace lst@(x:xs) sub repl | sub `isPrefixOf` lst = repl ++ replace (drop (length sub) lst) sub repl
                            | otherwise = x:(replace xs sub repl)
replace _ _ _ = []

-- | Configuration structure
data EvalConfig = EvalConfig { handleError :: InterpreterError -> X String
                             -- ^ Function to handle errors
                             , imports     :: [(ModuleName,Maybe String)]
                             -- ^ Modules to import for interpreting the expression.
                             -- The pair consists of the module name and an optional
                             -- qualification of the imported module.
                             , modules     :: [String]
                             -- ^ Other source files that should be loaded
                             -- The definitions of these modules will be visible
                             -- regardless of whether they are exported.
                             }

-- | Defaults for evaluating expressions.
defaultEvalConfig :: EvalConfig
defaultEvalConfig = EvalConfig { handleError = handleErrorDefault
                               , imports = [("Prelude",Nothing),("XMonad",Nothing),
                                            ("XMonad.StackSet",Just "W"),("XMonad.Core",Nothing)]
                               , modules = []
                               }

-- | Default way to handle(in this case: display) an error during interpretation of an expression.
handleErrorDefault :: InterpreterError -> X String
handleErrorDefault err = do
  xmsg <- fmap (maybe "xmessage" id) (io $ lookupEnv "XMONAD_XMESSAGE")
  io (safeSpawn xmsg [replace (show err) "\\n" "\n"]) >> return "Error"

-- | Returns an Interpreter action that loads the desired modules and interprets the expression.
interpret' :: EvalConfig -> String -> Interpreter (X String)
interpret' conf s = do
  loadModules $ modules conf
  setTopLevelModules =<< getLoadedModules
  setImportsQ $ imports conf
  interpret ("show `fmap` ("++s++")") (return "")

-- | Evaluates a given expression whose result type has to be an instance of Show
evalExpressionWithReturn :: EvalConfig -> String -> X String
evalExpressionWithReturn conf s = io (runInterpreter $ interpret' conf s) >>=
                                  either (handleError conf) id

-- | Evaluates a given expression, but discard the returned value. Provided for
-- more convenient use in keybindings
evalExpression :: EvalConfig -> String -> X ()
evalExpression cnf = (>> return ()) . evalExpressionWithReturn cnf
