{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE Haskell2010 #-} {-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} -- | This module implements persistence across different Yi runs. -- It includes minibuffer command history, marks 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, persistentSearch) where import GHC.Generics (Generic) import Control.Exc (ignoringException) import Lens.Micro.Platform ((.=), makeLenses, use) import Control.Monad (when) import Data.Binary (Binary, decodeFile, encodeFile) import Data.Default (Default, def) import qualified Data.Map as M (map) import Data.Typeable (Typeable) import System.Directory (doesFileExist) import Yi.Config.Simple.Types (Field, customVariable) import Yi.Editor import Yi.History (Histories (..), History (..)) import Yi.Keymap (YiM) import Yi.KillRing (Killring (..)) import Yi.Paths (getPersistentStateFilename) import Yi.Regex (SearchExp (..)) import Yi.Search.Internal (getRegexE, setRegexE) import Yi.Types (YiConfigVariable) import Yi.Utils (io) data PersistentState = PersistentState { histories :: !Histories , aKillring :: !Killring , aCurrentRegex :: Maybe SearchExp } deriving (Generic) instance Binary PersistentState newtype MaxHistoryEntries = MaxHistoryEntries { _unMaxHistoryEntries :: Int } deriving(Typeable, Binary) instance Default MaxHistoryEntries where def = MaxHistoryEntries 1000 instance YiConfigVariable MaxHistoryEntries makeLenses ''MaxHistoryEntries maxHistoryEntries :: Field Int maxHistoryEntries = customVariable . unMaxHistoryEntries newtype PersistentSearch = PersistentSearch { _unPersistentSearch :: Bool } deriving(Typeable, Binary) instance Default PersistentSearch where def = PersistentSearch True instance YiConfigVariable PersistentSearch makeLenses ''PersistentSearch persistentSearch :: Field Bool persistentSearch = customVariable . unPersistentSearch -- | Trims per-command histories to contain at most N completions each. trimHistories :: Int -> Histories -> Histories trimHistories maxHistory (Histories m) = Histories $ M.map trimH m where trimH (History cur content prefix) = History cur (trim content) prefix trim content = drop (max 0 (length content - maxHistory)) content -- | 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 getEditorDyn kr <- withEditor $ use killringA curRe <- withEditor getRegexE let pState = PersistentState { histories = trimHistories histLimit hist , 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 putEditorDyn $ histories pState (.=) killringA $ aKillring pState PersistentSearch keepSearch <- askConfigVariableA when keepSearch . withEditor $ maybe (return ()) setRegexE $ aCurrentRegex pState