yi-0.6.7.0: src/library/Yi/PersistentState.hs
{-# 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