Permalink
Browse files

Merge pull request #34 from mgajda/master

Persistent command history and killring, and useful bugfixes...
  • Loading branch information...
ethercrow committed Jul 30, 2012
2 parents 15c5a2c + fec0842 commit 32c136a6115ef826d035eb2805bf6f4dffc0e3be
View
@@ -46,6 +46,7 @@ Mark Wright
Massimiliano Gubinelli
Michael Dagitses
Michael Maloney
+Michal J. Gajda
Nicolas Pouillard
Paulo Tanimoto
Samuel Bronson
@@ -56,9 +56,10 @@ yiDriver cfg = do
, Dyre.hidePackages = ["mtl"]
, Dyre.ghcOpts = (["-threaded", "-O2"] ++
#ifdef PROFILING
- ["-prof", "-auto-all", "-rtsopts"] ++
+ ["-prof", "-auto-all", "-rtsopts", "-osuf=p_o", "-hisuf=p_hi"] ++
#endif
ghcOptions cfgcon)
+ , Dyre.includeCurrentDirectory = False
}
in Dyre.wrapMain yiParams (finalCfg, cfgcon)
@@ -1,3 +1,5 @@
+-- -*- haskell -*-
+
module Yi.Boot where
import Yi.Keymap
@@ -468,26 +468,31 @@ defaultModeLine prefix = do
modeNm <- gets (withMode0 modeName)
unchanged <- gets isUnchangedBuffer
let pct = if (pos == 1) || (s == 0)
- then "Top"
+ then " Top"
else getPercent p s
chg = if unchanged then "-" else "*"
roStr = if ro then "%" else chg
- hexChar = "0x" ++ Numeric.showHex (Data.Char.ord curChar) ""
+ hexChar = "0x" ++ padString 2 '0' (Numeric.showHex (Data.Char.ord curChar) "")
nm <- gets $ shortIdentString prefix
return $
roStr ++ chg ++ " "
++ nm ++
replicate 5 ' ' ++
hexChar ++ " " ++
- "L" ++ show ln ++ " " ++ "C" ++ show col ++
+ "L" ++ padString 5 ' ' (show ln) ++ " " ++ "C" ++ padString 3 ' ' (show col) ++
" " ++ pct ++
" " ++ modeNm ++
" " ++ show (fromPoint p)
+padString :: Int -> Char -> String -> String
+padString n c s = replicate k c ++ s
+ where
+ k = max 0 $ n - length s
+
-- | Given a point, and the file size, gives us a percent string
getPercent :: Point -> Point -> String
-getPercent a b = show p ++ "%"
+getPercent a b = padString 3 ' ' (show p) ++ "%"
where p = ceiling (aa / bb * 100.0 :: Double) :: Int
aa = fromIntegral a :: Double
bb = fromIntegral b :: Double
@@ -1 +1,3 @@
+-- -*- haskell -*-
+
module Yi.Buffer.Misc where data FBuffer
@@ -84,6 +84,7 @@ import Yi.String
import Yi.Style (errorStyle, strongHintStyle)
import qualified Yi.UI.Common as UI
import Yi.Window (dummyWindow, bufkey, wkey, winRegion)
+import {-# source #-} Yi.PersistentState(loadPersistentState, savePersistentState)
-- | Make an action suitable for an interactive run.
-- UI will be refreshed.
@@ -110,6 +111,7 @@ startEditor cfg st = do
-- Use an empty state unless resuming from an earlier session and one is already available
let editor = maybe emptyEditor id st
+ -- here to add load history etc?
-- Setting up the 1st window is a bit tricky because most functions assume there exists a "current window"
newSt <- newMVar $ YiVar editor [] 1 M.empty
@@ -121,6 +123,8 @@ startEditor cfg st = do
yi = Yi ui inF outF cfg newSt
ui <- uiStart cfg inF outF editor
return (ui, runYi)
+
+ runYi $ loadPersistentState
runYi $ do if isNothing st
then postActions $ startActions cfg -- process options if booting for the first time
@@ -198,6 +202,7 @@ showEvs :: [Event] -> String
-- | Quit.
quitEditor :: YiM ()
quitEditor = do
+ savePersistentState
onYiVar $ terminateSubprocesses (const True)
withUI (flip UI.end True)
@@ -144,6 +144,10 @@ tabsA = tabs_A . fixCurrentBufferA_
currentTabA :: Accessor Editor Tab
currentTabA = PL.focusA . tabsA
+askConfigVariableA :: (YiConfigVariable b, MonadEditor m) => m b
+askConfigVariableA = do cfg <- askCfg
+ return $ cfg ^. configVarsA ^. configVariableA
+
dynA :: YiVariable a => Accessor Editor a
dynA = dynamicValueA . dynamicA
@@ -1 +1,3 @@
+-- -*- haskell -*-
+
module Yi.Editor where data Editor
@@ -1,3 +1,5 @@
+-- -*- haskell -*-
+
module Yi.File where
import Yi.Buffer.Basic
@@ -83,6 +83,7 @@ import Yi.Tag
import Yi.Window (bufkey)
import Yi.Hoogle (hoogle, hoogleSearch)
import qualified Codec.Binary.UTF8.String as UTF8
+import Yi.Keymap.Vim.TagStack
--
@@ -176,41 +177,6 @@ data VimExCmd = VimExCmd { cmdNames :: [String]
type VimExCmdMap = [VimExCmd] -- very simple implementation yet
-newtype VimTagStack = VimTagStack { tagsStack :: [(FilePath, Point)] }
- deriving (Typeable, Binary)
-
-instance Initializable VimTagStack where
- initial = VimTagStack []
-
-instance YiVariable VimTagStack
-
-getTagStack :: EditorM VimTagStack
-getTagStack = getDynamic
-
-setTagStack :: VimTagStack -> EditorM ()
-setTagStack = setDynamic
-
-listTagStack :: EditorM [(FilePath, Point)]
-listTagStack = return . tagsStack =<< getTagStack
-
-pushTagStack :: FilePath -> Point -> EditorM ()
-pushTagStack fp p = do VimTagStack ts <- getTagStack
- setTagStack $ VimTagStack $ (fp, p):ts
-
-peekTagStack :: EditorM (Maybe (FilePath, Point))
-peekTagStack = do VimTagStack ts <- getTagStack
- case ts of
- [] -> return Nothing
- (p:_) -> return $ Just p
-
--- pop 'count' element from the tag stack.
-popTagStack :: Int -> EditorM (Maybe (FilePath, Point))
-popTagStack count = do VimTagStack ts <- getTagStack
- case drop (count - 1) ts of
- [] -> return Nothing
- (p:ps) -> do setTagStack $ VimTagStack ps
- return $ Just p
-
$(nameDeriveAccessors ''VimOpts $ Just.(++ "A"))
-- | The Vim keymap is divided into several parts, roughly corresponding
@@ -0,0 +1,60 @@
+{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving #-}
+module Yi.Keymap.Vim.TagStack(VimTagStack(..),
+ getTagStack,
+ setTagStack,
+ listTagStack,
+ pushTagStack,
+ peekTagStack,
+ popTagStack)
+where
+
+import System.FilePath(FilePath)
+import Yi.Buffer.Basic(Point)
+import Yi.Prelude(Initializable(..))
+import Yi.Dynamic
+import Yi.Editor
+
+import Data.Binary
+import Data.Typeable
+{-
+import {-# source #-} Yi.Boot
+import Yi.Core
+import Yi.File
+import Yi.History
+import Yi.MiniBuffer
+ -}
+
+newtype VimTagStack = VimTagStack { tagsStack :: [(FilePath, Point)] }
+ deriving (Typeable, Binary)
+
+instance Initializable VimTagStack where
+ initial = VimTagStack []
+
+instance YiVariable VimTagStack
+
+getTagStack :: EditorM VimTagStack
+getTagStack = getDynamic
+
+setTagStack :: VimTagStack -> EditorM ()
+setTagStack = setDynamic
+
+listTagStack :: EditorM [(FilePath, Point)]
+listTagStack = return . tagsStack =<< getTagStack
+
+pushTagStack :: FilePath -> Point -> EditorM ()
+pushTagStack fp p = do VimTagStack ts <- getTagStack
+ setTagStack $ VimTagStack $ (fp, p):ts
+
+peekTagStack :: EditorM (Maybe (FilePath, Point))
+peekTagStack = do VimTagStack ts <- getTagStack
+ case ts of
+ [] -> return Nothing
+ (p:_) -> return $ Just p
+
+-- pop 'count' element from the tag stack.
+popTagStack :: Int -> EditorM (Maybe (FilePath, Point))
+popTagStack count = do VimTagStack ts <- getTagStack
+ case drop (count - 1) ts of
+ [] -> return Nothing
+ (p:ps) -> do setTagStack $ VimTagStack ps
+ return $ Just p
@@ -0,0 +1,117 @@
+{-# 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.FilePath((</>))
+import System.Directory(getAppUserDataDirectory, doesFileExist)
+import qualified Data.Map as M
+
+import Control.Exc(ignoringException)
+
+import Yi.Prelude
+import Yi.Dynamic
+import Yi.Config
+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(..))
+
+
+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
+
+-- | Finds a path of history file.
+getPersistentStateFilename :: YiM String
+getPersistentStateFilename = do cfgDir <- io $ getAppUserDataDirectory "yi"
+ return $ cfgDir </> "history"
+
+-- | 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
+
@@ -0,0 +1,8 @@
+-- -*- haskell -*-
+
+module Yi.PersistentState(loadPersistentState, savePersistentState) where
+
+import Yi.Keymap(YiM)
+
+loadPersistentState :: YiM ()
+savePersistentState :: YiM ()
Oops, something went wrong.

0 comments on commit 32c136a

Please sign in to comment.