{-# LANGUAGE TemplateHaskell, ScopedTypeVariables, NoMonomorphismRestriction, Haskell2010, GeneralizedNewtypeDeriving, DeriveDataTypeable #-} -- Copyright '2012 by Michal J. Gajda -- -- | This module implements persistence across different Yi runs. -- It includes minibuffer command history, marks, VimTagStack etc. -- Warning: Current version will _not_ check whether two or more instances -- of Yi are run at the same time. module Yi.PersistentState(loadPersistentState, savePersistentState, maxHistoryEntries) where import Prelude hiding ((.)) import Data.Binary import Data.DeriveTH import Data.Accessor.Template(nameDeriveAccessors) import System.Directory(doesFileExist) import qualified Data.Map as M import Control.Exc(ignoringException) import Yi.Prelude import Yi.Dynamic import Yi.Config.Simple.Types(customVariable, Field) import Yi.History import Yi.Editor import Yi.Keymap(YiM) import Yi.Keymap.Vim.TagStack(VimTagStack(..), getTagStack, setTagStack) import Yi.KillRing(Killring(..)) import Yi.Search(getRegexE, setRegexE) import Yi.Regex(SearchExp(..)) import Yi.Paths(getPersistentStateFilename) data PersistentState = PersistentState { histories :: !Histories , vimTagStack :: !VimTagStack , aKillring :: !Killring , aCurrentRegex :: Maybe SearchExp } $(derive makeBinary ''PersistentState) newtype MaxHistoryEntries = MaxHistoryEntries { unMaxHistoryEntries :: Int } deriving(Typeable, Binary) instance Initializable MaxHistoryEntries where initial = MaxHistoryEntries 1000 instance YiConfigVariable MaxHistoryEntries $(nameDeriveAccessors ''MaxHistoryEntries (\n -> Just (n ++ "A"))) maxHistoryEntries :: Field Int maxHistoryEntries = unMaxHistoryEntriesA . customVariable -- | Trims per-command histories to contain at most N completions each. trimHistories :: Int -> Histories -> Histories trimHistories maxHistory = M.map trimH where trimH (History cur content prefix) = History cur (trim content) prefix trim content = drop (max 0 (length content - maxHistory)) content -- | Trims VimTagStack to contain at most N values. trimTagStack :: Int -> VimTagStack -> VimTagStack trimTagStack maxHistory = VimTagStack . take maxHistory . tagsStack -- | Here is a persistent history saving part. -- We assume each command is a single line. -- To add new components, one has to: -- -- * add new field in @PersistentState@ structure, -- * add write and read parts in @loadPersistentState@/@savePersistentState@, -- * add a trimming code in @savePersistentState@ to prevent blowing up -- of save file. savePersistentState :: YiM () savePersistentState = do MaxHistoryEntries histLimit <- withEditor $ askConfigVariableA pStateFilename <- getPersistentStateFilename (hist :: Histories) <- withEditor $ getA dynA tagStack <- withEditor $ getTagStack kr <- withEditor $ getA killringA curRe <- withEditor $ getRegexE let pState = PersistentState { histories = trimHistories histLimit hist , vimTagStack = trimTagStack histLimit tagStack , aKillring = kr -- trimmed during normal operation , aCurrentRegex = curRe -- just a single value -> no need to trim } io $ encodeFile pStateFilename $ pState -- | Reads and decodes a persistent state in both strict, and exception robust -- way. readPersistentState :: YiM (Maybe PersistentState) readPersistentState = do pStateFilename <- getPersistentStateFilename pStateExists <- io $ doesFileExist pStateFilename if not pStateExists then return Nothing else io $ ignoringException $ strictDecoder pStateFilename where strictDecoder filename = do (state :: PersistentState) <- decodeFile filename state `seq` return (Just state) -- | Loads a persistent state, and sets Yi state variables accordingly. loadPersistentState :: YiM () loadPersistentState = do maybePState <- readPersistentState case maybePState of Nothing -> return () Just pState -> do withEditor $ putA dynA $ histories pState withEditor $ setTagStack $ vimTagStack pState withEditor $ putA killringA $ aKillring pState withEditor $ maybe (return ()) setRegexE $ aCurrentRegex pState