yi-0.8.0: src/library/Yi/Eval.hs
{-# LANGUAGE CPP, ScopedTypeVariables, TypeOperators, DeriveDataTypeable, GeneralizedNewtypeDeriving, TemplateHaskell, RecordWildCards #-}
module Yi.Eval (
-- * Main (generic) evaluation interface
execEditorAction,
getAllNamesInScope,
Evaluator(..),
evaluator,
-- ** Standard evaluators
ghciEvaluator,
publishedActionsEvaluator,
publishedActions,
publishAction,
-- * Eval\/Interpretation
jumpToErrorE,
jumpToE,
consoleKeymap,
) where
import Prelude hiding (error)
import Control.Applicative
import Control.Monad
import Control.Lens hiding (Action)
import Data.Array
import Data.List
import Data.Monoid
import Data.Typeable
import Data.Binary
import Data.Default
import qualified Language.Haskell.Interpreter as LHI
import System.Directory(doesFileExist)
import qualified Data.HashMap.Strict as M
import {-# source #-} Yi.Boot (reload)
import Yi.Config.Simple.Types
import Yi.Core
import Yi.File
import Yi.Hooks
import Yi.Regex
import qualified Yi.Paths(getEvaluatorContextFilename)
import Yi.Utils
import Yi.Debug
-- | Runs the action, as written by the user.
--
-- The behaviour of this function can be customised by modifying the 'Evaluator' variable.
execEditorAction :: String -> YiM ()
execEditorAction = runHook execEditorActionImpl
-- | Lists the action names in scope, for use by 'execEditorAction'.
--
-- The behaviour of this function can be customised by modifying the 'Evaluator' variable.
getAllNamesInScope :: YiM [String]
getAllNamesInScope = runHook getAllNamesInScopeImpl
{- | Config variable for customising the behaviour of 'execEditorAction' and 'getAllNamesInScope'.
Set this variable using 'evaluator'. See 'ghciEvaluator' and 'finiteListEvaluator' for two implementation.
-}
data Evaluator = Evaluator {
execEditorActionImpl :: String -> YiM (), -- ^ implementation of 'execEditorAction'
getAllNamesInScopeImpl :: YiM [String] -- ^ implementation of 'getAllNamesInScope'
}
deriving(Typeable)
-- | The evaluator to use for 'execEditorAction' and 'getAllNamesInScope'.
evaluator :: Field Evaluator
evaluator = customVariable
instance Default Evaluator where def = ghciEvaluator
instance YiConfigVariable Evaluator
------------------------- Evaluator based on GHCi
newtype NamesCache = NamesCache [String] deriving (Typeable, Binary)
instance Default NamesCache where
def = NamesCache []
instance YiVariable NamesCache
{- | Evaluator implemented by calling GHCi. This evaluator can run arbitrary expressions in the class 'YiAction'.
The following two imports are always present:
> import Yi
> import qualified Yi.Keymap as Yi.Keymap
Also, if the file
> $HOME/.config/yi/local/Env.hs
exists, it is imported unqualified.
-}
ghciEvaluator :: Evaluator
ghciEvaluator = Evaluator{..} where
execEditorActionImpl :: String -> YiM ()
execEditorActionImpl "reload" = reload
execEditorActionImpl s = do
contextFile <- Yi.Paths.getEvaluatorContextFilename
haveUserContext <- io $ doesFileExist contextFile
res <- io $ LHI.runInterpreter $ do
LHI.set [LHI.searchPath LHI.:= []]
LHI.set [LHI.languageExtensions LHI.:= [LHI.OverloadedStrings,
LHI.NoImplicitPrelude -- use Yi prelude instead.
]]
when haveUserContext $ do
LHI.loadModules [contextFile]
LHI.setTopLevelModules ["Env"]
LHI.setImportsQ [("Yi", Nothing), ("Yi.Keymap",Just "Yi.Keymap")] -- Yi.Keymap: Action lives there
LHI.interpret ("Yi.makeAction ("++s++")") (error "as" :: Action)
case res of
Left err -> errorEditor (show err)
Right action -> runAction action
getAllNamesInScopeImpl :: YiM [String]
getAllNamesInScopeImpl = do
NamesCache cache <- withEditor $ use dynA
result <-if null cache then do
res <-io $ LHI.runInterpreter $ do
LHI.set [LHI.searchPath LHI.:= []]
LHI.getModuleExports "Yi"
return $ case res of
Left err ->[show err]
Right exports -> flattenExports exports
else return $ sort cache
withEditor $ assign dynA (NamesCache result)
return result
flattenExports :: [LHI.ModuleElem] -> [String]
flattenExports = concatMap flattenExport
flattenExport :: LHI.ModuleElem -> [String]
flattenExport (LHI.Fun x) = [x]
flattenExport (LHI.Class _ xs) = xs
flattenExport (LHI.Data _ xs) = xs
------------------- PublishedActions evaluator
newtype PublishedActions = PublishedActions {
publishedActions_ :: M.HashMap String Action
} deriving(Typeable, Monoid)
instance Default PublishedActions where def = mempty
makeLensesWithSuffix "A" ''PublishedActions
instance YiConfigVariable PublishedActions
-- | Accessor for the published actions. Consider using 'publishAction'.
publishedActions :: Field (M.HashMap String Action)
publishedActions = customVariable . publishedActions_A
-- | Publish the given action, by the given name. This will overwrite any existing actions by the same name.
publishAction :: (YiAction a x, Show x) => String -> a -> ConfigM ()
publishAction s a = (%=) publishedActions (M.insert s (makeAction a))
{- | Evaluator based on a fixed list of published actions. Has a few differences from 'ghciEvaluator':
* expressions can't be evaluated
* all suggested actions are actually valued
* (related to the above) doesn't contain junk actions from Prelude
* doesn't require GHCi backend, so uses less memory
-}
publishedActionsEvaluator :: Evaluator
publishedActionsEvaluator = Evaluator{..} where
getAllNamesInScopeImpl = (M.keys . (^. publishedActions)) <$> askCfg
execEditorActionImpl s =
((M.lookup s . (^. publishedActions)) <$> askCfg) >>=
maybe (return ()) runAction
------------------- Miscellaneous interpreter
jumpToE :: String -> Int -> Int -> YiM ()
jumpToE filename line column = do
void $ editFile filename
withBuffer $ do _ <- gotoLn line
moveXorEol column
errorRegex :: Regex
errorRegex = makeRegex "^(.+):([0-9]+):([0-9]+):.*$"
parseErrorMessage :: String -> Maybe (String, Int, Int)
parseErrorMessage ln = do
(_,result,_) <- matchOnceText errorRegex ln
let [_,filename,line,col] = take 3 $ map fst $ elems result
return (filename, read line, read col)
parseErrorMessageB :: BufferM (String, Int, Int)
parseErrorMessageB = do
ln <- readLnB
let Just location = parseErrorMessage ln
return location
jumpToErrorE :: YiM ()
jumpToErrorE = do
(f,l,c) <- withBuffer parseErrorMessageB
jumpToE f l c
prompt :: String
prompt = "Yi> "
takeCommand :: String -> String
takeCommand x | prompt `isPrefixOf` x = drop (length prompt) x
| otherwise = x
consoleKeymap :: Keymap
consoleKeymap = do _ <- event (Event KEnter [])
write $ do x <- withBuffer readLnB
case parseErrorMessage x of
Just (f,l,c) -> jumpToE f l c
Nothing -> do withBuffer $ do
p <- pointB
botB
p' <- pointB
when (p /= p') $
insertN ("\n" ++ prompt ++ takeCommand x)
insertN "\n"
pt <- pointB
insertN prompt
bm <- getBookmarkB "errorInsert"
setMarkPointB bm pt
execEditorAction $ takeCommand x