packages feed

rasa 0.1.3 → 0.1.4

raw patch · 11 files changed

+374/−236 lines, 11 filesdep ~asyncdep ~containersdep ~data-default

Dependency ranges changed: async, containers, data-default, lens, mtl, text, text-lens, transformers, yi-rope

Files

README.md view
@@ -80,6 +80,17 @@ Here's a great guide on building a copy-paste extension from scratch! I definitely recommend checking it out! +### Configuring Rasa++Rasa is designed to be easy to configure, both when adding extensions provided+by the community, and when writing your own user-scripts.++Rasa is written in Haskell, and the configuration is done in the Haskell+language, don't let that scare you though, you can script Rasa and add+extensions without knowing much haskell!++Check out the [Example Config](https://github.com/ChrisPenner/rasa/tree/master/rasa-example-config) to get an idea of where to go next!+ ### [Building Your First Extension](https://github.com/ChrisPenner/rasa/blob/master/docs/Building-An-Extension.md)  \^ That guide will walk you through installation and getting running! Once
rasa.cabal view
@@ -1,84 +1,85 @@-name:                rasa-version:             0.1.3-synopsis:            A modular text editor-homepage:            https://github.com/ChrisPenner/rasa#readme-license:             MIT-license-file:        LICENSE-author:              Chris Penner-maintainer:          christopher.penner@gmail.com-copyright:           2016 Chris Penner-category:            Text Editor, Executable-build-type:          Simple-extra-source-files:  README.md-cabal-version:       >=1.10-description:         A modular text editor-  This is only a snippet, see the project's <https://github.com/ChrisPenner/rasa README>.-  .-  Rasa is a text editor project with a few interesting goals. For better or worse it attempts-  to be as modular as possible. This means that most functionality which would typically be considered to be 'core' in other editors-  is implemented as extensions in Rasa.-  This approach comes with its own share of pros and cons, for instance:-  .-  /Pros/-  .-  * Implementing most core functionality as extensions ensures a powerful and elegant extension interface.-  .-  * Flexibility; don't like the default cursor implementation? Write your own!-  .-  * Adaptability; the core of Rasa is miniscule, you can mix and match extensions to build any editor you want.-  .-  /Cons/-  .-  * Module cross-dependencies makes the community infrastructure more fragile; We'll likely have to develop a solution to this as a community as time goes on.-  .-  * Fragmentation; Not having a single implementation for a given feature means extensions that depend on a feature have to pick a specific implementation to augment. Over time data-structures and types will be standardized into Rasa's core to help alleviate this.-  .-  While highly experimental, I've found the current API to be quite expressive and adaptable; -  for instance I was able to implement the notion of multiple cursors using the extension API in less than a day. I hope to keep the learning curve low as development continues.-  .-  /Getting Started/-  .-  First clone the <https://github.com/ChrisPenner/rasa Github repo>-  and try running the example-config included there. -  Once you get it running (see the <https://github.com/ChrisPenner/rasa/blob/master/README.md README>) then you-  can customize your keymap to add a few mappings you like. -  Then I'd check out the -  <https://github.com/ChrisPenner/rasa/blob/master/docs/Building-An-Extension.md Building your own extensions guide>.-  It goes in depth into everything you'd want to know!-  .-  If you have any issues (and I'm sure there'll be a few; it's a new project!) please report them <https://github.com/ChrisPenner/rasa/issues here> and we'll talk about it!--library-  hs-source-dirs:      src-  exposed-modules:-                       Rasa--                       Rasa.Ext--                       Rasa.Internal.Action-                       Rasa.Internal.Buffer-                       Rasa.Internal.Directive-                       Rasa.Internal.Editor-                       Rasa.Internal.Events-                       Rasa.Internal.Extensions-                       Rasa.Internal.Range-                       Rasa.Internal.Scheduler-                       Rasa.Internal.Text+name: rasa+version: 0.1.4+cabal-version: >=1.10+build-type: Simple+license: MIT+license-file: LICENSE+copyright: 2016 Chris Penner+maintainer: christopher.penner@gmail.com+homepage: https://github.com/ChrisPenner/rasa#readme+synopsis: A modular text editor+description:+    A modular text editor+    This is only a snippet, see the project's <https://github.com/ChrisPenner/rasa README>.+    .+    Rasa is a text editor project with a few interesting goals. For better or worse it attempts+    to be as modular as possible. This means that most functionality which would typically be considered to be 'core' in other editors+    is implemented as extensions in Rasa.+    This approach comes with its own share of pros and cons, for instance:+    .+    /Pros/+    .+    * Implementing most core functionality as extensions ensures a powerful and elegant extension interface.+    .+    * Flexibility; don't like the default cursor implementation? Write your own!+    .+    * Adaptability; the core of Rasa is miniscule, you can mix and match extensions to build any editor you want.+    .+    /Cons/+    .+    * Module cross-dependencies makes the community infrastructure more fragile; We'll likely have to develop a solution to this as a community as time goes on.+    .+    * Fragmentation; Not having a single implementation for a given feature means extensions that depend on a feature have to pick a specific implementation to augment. Over time data-structures and types will be standardized into Rasa's core to help alleviate this.+    .+    While highly experimental, I've found the current API to be quite expressive and adaptable;+    for instance I was able to implement the notion of multiple cursors using the extension API in less than a day. I hope to keep the learning curve low as development continues.+    .+    /Getting Started/+    .+    First clone the <https://github.com/ChrisPenner/rasa Github repo>+    and try running the example-config included there.+    Once you get it running (see the <https://github.com/ChrisPenner/rasa/blob/master/README.md README>) then you+    can customize your keymap to add a few mappings you like.+    Then I'd check out the+    <https://github.com/ChrisPenner/rasa/blob/master/docs/Building-An-Extension.md Building your own extensions guide>.+    It goes in depth into everything you'd want to know!+    .+    If you have any issues (and I'm sure there'll be a few; it's a new project!) please report them <https://github.com/ChrisPenner/rasa/issues here> and we'll talk about it!+category: Text Editor, Executable+author: Chris Penner+extra-source-files:+    README.md -  ghc-options:         -Wall-  build-depends:       base >= 4.7 && < 5-                     , async-                     , containers-                     , data-default-                     , lens-                     , mtl-                     , text-                     , text-lens-                     , transformers-                     , yi-rope+source-repository head+    type: git+    location: https://github.com/ChrisPenner/rasa -  default-language:    Haskell2010+library+    exposed-modules:+        Rasa+        Rasa.Ext+        Rasa.Internal.Action+        Rasa.Internal.Async+        Rasa.Internal.Buffer+        Rasa.Internal.Directive+        Rasa.Internal.Editor+        Rasa.Internal.Events+        Rasa.Internal.Extensions+        Rasa.Internal.Range+        Rasa.Internal.Scheduler+        Rasa.Internal.Text+    build-depends:+        base >=4.8 && <5,+        async >=2.1.1 && <2.2,+        containers >=0.5.7.1 && <0.6,+        data-default >=0.7.1.1 && <0.8,+        lens ==4.14.*,+        mtl >=2.2.1 && <2.3,+        text >=1.2.2.1 && <1.3,+        text-lens >=0.1.0.0 && <0.2,+        transformers >=0.5.2.0 && <0.6,+        yi-rope >=0.7.0.2 && <0.8+    default-language: Haskell2010+    hs-source-dirs: src+    ghc-options: -Wall -source-repository head-  type:     git-  location: https://github.com/ChrisPenner/rasa
src/Rasa.hs view
@@ -6,46 +6,45 @@ import Rasa.Internal.Events import Rasa.Internal.Scheduler -import Control.Lens import Control.Concurrent.Async+import Control.Lens import Control.Monad-import Control.Monad.State-import Control.Monad.Reader+import Control.Monad.IO.Class import Data.Default (def)-import Data.Foldable+import Data.List  -- | The main function to run rasa.--- +-- -- @rasa eventProviders extensions@--- --- This should be imported by a user-config and called with extension event providers and extension event hooks as--- arguments. e.g.:--- --- > rasa [slateEvent] $ do+--+-- This should be imported by a user-config with and called with a 'Scheduler'+-- containing any extensions which have event listeners.+--+-- > rasa $ do -- >   cursor -- >   vim-rasa :: [Action [Keypress]] -> Scheduler () -> IO ()-rasa eventProviders scheduler =+-- >   slate++rasa :: Scheduler () -> IO ()+rasa scheduler =   evalAction def hooks $ do     dispatchEvent Init-    eventLoop eventProviders+    eventLoop     dispatchEvent Exit     where hooks = getHooks scheduler  -- | This is the main event loop, it runs recursively forever until something--- sets 'Rasa.Editor.exiting'. It runs the pre-event hooks, then listens for an--- event from the event providers, then runs the post event hooks and repeats.-eventLoop :: [Action [Keypress]] -> Action ()-eventLoop eventProviders = do+-- sets 'Rasa.Editor.exiting'. It runs the pre-event hooks, then checks if any+-- async events have finished, then runs the post event hooks and repeats.+eventLoop :: Action ()+eventLoop = do   dispatchEvent BeforeRender   dispatchEvent OnRender   dispatchEvent AfterRender   dispatchEvent BeforeEvent-  currentEditor <- get-  hooks <- ask-  -- This is a little weird, but I think it needs to be this way to execute providers in parallel-  asyncEventProviders <- liftIO $ traverse (async.evalAction currentEditor hooks) eventProviders-  (_, nextEvents) <- liftIO $ waitAny asyncEventProviders-  traverse_ dispatchEvent nextEvents+  asyncActions <- use asyncs+  (done, action) <- liftIO $ waitAny asyncActions+  asyncs %= delete done+  action   isExiting <- use exiting-  unless isExiting $ eventLoop eventProviders+  unless isExiting eventLoop
src/Rasa/Ext.hs view
@@ -45,23 +45,29 @@   (   -- * Editor Actions     Action+  , doAsync   , exit-  , addBuffer-  , addBufferThen-  , nextBuf-  , prevBuf -  -- * Buffer Actions+  -- * Managing Buffers+  , newBuffer+  , nextBufRef+  , prevBufRef++  -- * Working with Buffers   , BufAction   , bufDo-  , focusDo+  , bufDo_+  , buffersDo+  , buffersDo_++  -- * Working with Text   , overRange   , replaceRange   , deleteRange   , insertAt   , sizeOf -  -- * Persisting Extension State+  -- * Working with Extensions   -- | Extension states for ALL the extensions installed are stored in the same   -- map keyed by their 'Data.Typeable.TypeRep' so if more than one extension   -- uses the same type then they'll conflict. This is easily solved by simply@@ -99,20 +105,13 @@     -- * Accessing/Editing Context   , Buffer+  , BufRef+  , HasEditor   , text-  -- | A lens over the buffer's 'Data.Text.Text'. Use within a 'BufAction' as+  -- | A lens over the buffer's Text as a 'Yi.Rope.YiString'. Use within a 'BufAction':   --   -- > txt <- use text -  , exiting-  -- | A lens over the current 'exit' status of the editor, allows an extension to-  -- signal the editor to shutdown. If this is set the current events will finish processing, then the-  ---  -- 'Rasa.Ext.Events.Exit' event will be dispatched, then the editor will exit.-  -- Use within an 'Action'-  ---  -- > exiting .= True-   -- * Events   , Keypress(..)   , Mod(..)@@ -123,6 +122,7 @@   , Hook   , dispatchEvent   , eventListener+  , eventProvider    -- * Built-in Event Hooks   , onInit@@ -131,6 +131,7 @@   , onRender   , afterRender   , onExit+  , onBufAdded     -- * Ranges   , Range(..)@@ -153,11 +154,11 @@   , asText   , asString   , asLines-  , rope   , clamp   ) where  import Rasa.Internal.Action+import Rasa.Internal.Async import Rasa.Internal.Directive import Rasa.Internal.Editor import Rasa.Internal.Events
src/Rasa/Internal/Action.hs view
@@ -1,15 +1,25 @@-{-# LANGUAGE GeneralizedNewtypeDeriving, ExistentialQuantification #-}+{-# LANGUAGE GeneralizedNewtypeDeriving, ExistentialQuantification, TemplateHaskell, StandaloneDeriving #-}  module Rasa.Internal.Action where +import Control.Lens+import Control.Concurrent.Async import Control.Monad.State import Control.Monad.Reader import Data.Dynamic import Data.Map+import Data.Default  import Rasa.Internal.Buffer import Rasa.Internal.Editor ++-- | A wrapper around event listeners so they can be stored in 'Hooks'.+data Hook = forall a. Hook a++-- | A map of Event types to a list of listeners for that event+type Hooks = Map TypeRep [Hook]+ -- | This is a monad-transformer stack for performing actions against the editor. -- You register Actions to be run in response to events using 'Rasa.Internal.Scheduler.eventListener' --@@ -22,17 +32,37 @@ --      * Add\/Edit\/Focus buffers and a few other Editor-level things, see the 'Rasa.Internal.Ext.Directive' module.  newtype Action a = Action-  { runAct :: StateT Editor (ReaderT Hooks IO) a-  } deriving (Functor, Applicative, Monad, MonadState Editor, MonadReader Hooks, MonadIO)+  { runAct :: StateT ActionState (ReaderT Hooks IO) a+  } deriving (Functor, Applicative, Monad, MonadState ActionState, MonadReader Hooks, MonadIO) + -- | Unwrap and execute an Action (returning the editor state)-execAction :: Editor -> Hooks -> Action () -> IO Editor-execAction editor hooks action  = flip runReaderT hooks $ execStateT (runAct action) editor+execAction :: ActionState -> Hooks -> Action () -> IO ActionState+execAction actionState hooks action = flip runReaderT hooks . execStateT (runAct action) $ actionState  -- | Unwrap and evaluate an Action (returning the value)-evalAction :: Editor -> Hooks -> Action a ->IO a-evalAction editor hooks action  = flip runReaderT hooks $ evalStateT (runAct action) editor+evalAction :: ActionState -> Hooks -> Action a -> IO a+evalAction actionState hooks action  = flip runReaderT hooks $ evalStateT (runAct action) actionState +type AsyncAction = Async (Action ())+data ActionState = ActionState+  { _ed :: Editor+  , _asyncs :: [AsyncAction]+  }+makeClassy ''ActionState++instance HasEditor ActionState where+  editor = ed++instance Default ActionState where+  def = ActionState+    { _ed=def+    , _asyncs=def+    }++instance Show ActionState where+  show as = show (_ed as)+ -- | This is a monad-transformer stack for performing actions on a specific buffer. -- You register BufActions to be run by embedding them in a scheduled 'Action' via 'bufferDo' or 'focusDo' --@@ -47,9 +77,16 @@   { getBufAction::StateT Buffer (ReaderT Hooks IO) a   } deriving (Functor, Applicative, Monad, MonadState Buffer, MonadReader Hooks, MonadIO) ---- | A wrapper around event listeners so they can be stored in 'Hooks'.-data Hook = forall a. Hook a+-- | This lifts up a bufAction into an Action which performs the 'BufAction'+-- over the referenced buffer and returns the result (if the buffer existed)+liftBuf :: BufAction a -> BufRef -> Action (Maybe a)+liftBuf bufAct (BufRef bufRef) = do+  mBuf <- use (buffers.at bufRef)+  case mBuf of+    Nothing -> return Nothing+    Just buf -> do+      hooks <- ask+      (val, newBuf) <- liftIO $ flip runReaderT hooks . flip runStateT buf . getBufAction $ bufAct+      buffers.at bufRef ?= newBuf+      return . Just $ val --- | A map of Event types to a list of listeners for that event-type Hooks = Map TypeRep [Hook]
+ src/Rasa/Internal/Async.hs view
@@ -0,0 +1,59 @@+module Rasa.Internal.Async +  ( doAsync+  , eventProvider+) where++import Rasa.Internal.Action+import Rasa.Internal.Scheduler++import Data.Typeable+import Control.Concurrent.Async+import Control.Lens+import Control.Monad.IO.Class++-- | This function takes an IO which results in some Event, it runs the IO+-- asynchronously and dispatches the event, then repeats the process all over+-- again. Use this inside the onInit scheduler to register an event listener+-- for some event (e.g. keypresses or network activity)++eventProvider :: Typeable a => IO a -> Action ()+eventProvider getEventIO = doAsync (dispatchAndRerun <$> getEventIO)+    where dispatchAndRerun evt = do+            dispatchEvent evt+            eventProvider getEventIO++-- | doAsync allows you to perform a task asynchronously and then apply the+-- result. In @doAsync asyncAction@, @asyncAction@ is an IO which resolves to+-- an Action, note that the context in which the second action is executed is+-- NOT the same context in which doAsync is called; it is likely that text and+-- other state have changed while the IO executed, so it's a good idea to check+-- (inside the applying Action) that things are in a good state before making+-- changes. Here's an example:+--+-- > asyncCapitalize :: Action ()+-- > asyncCapitalize = do+-- >   txt <- focusDo $ use text+-- >   -- We give doAsync an IO which resolves in an action+-- >   doAsync $ ioPart txt+-- >+-- > ioPart :: Text -> IO (Action ())+-- > ioPart txt = do+-- >   result <- longAsyncronousCapitalizationProgram txt+-- >   -- Note that this returns an Action, but it's still wrapped in IO+-- >   return $ maybeApplyResult txt result+-- >+-- > maybeApplyResult :: Text -> Text -> Action ()+-- > maybeApplyResult oldTxt capitalized = do+-- >   -- We get the current buffer's text, which may have changed since we started+-- >   newTxt <- focusDo (use text)+-- >   if newTxt == oldTxt+-- >     -- If the text is the same as it was, we can apply the transformation+-- >     then focusDo (text .= capitalized)+-- >     -- Otherwise we can choose to re-queue the whole action and try again+-- >     -- Or we could just give up.+-- >     else asyncCapitalize++doAsync ::  IO (Action ()) -> Action ()+doAsync asyncIO = do+  newAsync <- liftIO $ async asyncIO+  asyncs <>= [newAsync]
src/Rasa/Internal/Buffer.hs view
@@ -8,15 +8,12 @@   , bufExts   -- | A lens over the extensions stored for a buffer   , text-  , rope-  -- | A lens over the text stored in a buffer (as a 'Y.YiString')-  , newBuffer+-- | A lens into the text of the given buffer. Use within a BufAction.+  , mkBuffer   ) where -import Rasa.Internal.Text import Rasa.Internal.Extensions -import qualified Data.Text as T import qualified Yi.Rope as Y import Control.Lens hiding (matching) import Data.Map@@ -25,24 +22,20 @@ -- A buffer is the State of the 'Rasa.Internal.Action.BufAction' monad transformer stack, -- so the type may be useful in defining lenses over your extension states. data Buffer = Buffer-  { _rope :: Y.YiString+  { _text :: Y.YiString   , _bufExts :: ExtMap   }  makeLenses ''Buffer --- | A lens into the text of the given buffer. Use within a BufAction.-text :: Lens' Buffer T.Text-text = rope.asText- instance Show Buffer where-  show b = "<Buffer {text:" ++ show (b^..text . to (T.take 30)) ++ "...,\n"+  show b = "<Buffer {text:" ++ show (b^..text . to (Y.take 30)) ++ "...,\n"            ++ "exts: " ++ show (b^.bufExts) ++ "}>\n"  -- | Creates a new buffer from the givven text.-newBuffer :: T.Text -> Buffer-newBuffer txt =+mkBuffer :: Y.YiString -> Buffer+mkBuffer txt =   Buffer-    { _rope = Y.fromText txt+    { _text = txt     , _bufExts = empty     }
src/Rasa/Internal/Directive.hs view
@@ -3,14 +3,16 @@   (   -- * Performing Actions on Buffers     bufDo-  , focusDo+  , bufDo_+  , buffersDo+  , buffersDo_    -- * Editor Actions   , exit-  , addBuffer-  , addBufferThen-  , nextBuf-  , prevBuf+  , newBuffer+  , getBufRefs+  , nextBufRef+  , prevBufRef    -- * Buffer Actions   , overRange@@ -24,36 +26,76 @@ import Rasa.Internal.Editor import Rasa.Internal.Action import Rasa.Internal.Range+import Rasa.Internal.Scheduler+import Rasa.Internal.Events import Rasa.Internal.Buffer as B  import Control.Lens-import qualified Data.Text as T+import Control.Monad+import Data.Maybe+import Data.IntMap as M+import qualified Yi.Rope as Y + -- | This lifts a 'Rasa.Action.BufAction' to an 'Rasa.Action.Action' which--- performs the 'Rasa.Action.BufAction' on the focused buffer.+-- performs the 'Rasa.Action.BufAction' on every buffer and collects the return+-- values as a list. -focusDo :: BufAction a -> Action a-focusDo = Action . zoom focusedBuf . getBufAction+buffersDo :: BufAction a -> Action [a]+buffersDo bufAct = do+  bufRefs <- getBufRefs+  catMaybes . foldMap (:[]) <$> mapM (liftBuf bufAct) bufRefs +buffersDo_ :: BufAction a -> Action ()+buffersDo_ = void . buffersDo+ -- | This lifts a 'Rasa.Action.BufAction' to an 'Rasa.Action.Action' which--- performs the 'Rasa.Action.BufAction' on every buffer and collects the return--- values via 'mappend'+-- performs the 'Rasa.Action.BufAction' on the buffer referred to by the 'BufRef'+-- If the buffer referred to no longer exists this returns @Action Nothing@.+bufDo :: BufRef -> BufAction a -> Action (Maybe a)+bufDo bufRef bufAct = liftBuf bufAct bufRef -bufDo :: Monoid a => BufAction a -> Action a-bufDo = Action . zoom (buffers . traverse) . getBufAction+bufDo_ :: BufRef -> BufAction a -> Action ()+bufDo_ bufRef bufAct = void $ bufDo bufRef bufAct  -- | This adds a new buffer with the given text.+newBuffer :: Y.YiString -> Action BufRef+newBuffer txt = do+  n <- nextBufId <<+= 1+  buffers %= insert n (mkBuffer txt)+  let bufRef = BufRef n+  dispatchEvent (BufAdded bufRef)+  return bufRef -addBuffer :: T.Text -> Action ()-addBuffer txt = buffers <>= [newBuffer txt]+-- | Returns an up-to-date list of all 'BufRef's+getBufRefs :: Action [BufRef]+getBufRefs = fmap BufRef <$> use (buffers.to keys) --- | This adds a new buffer with the given text then performs the given--- 'Rasa.Action.BufAction' agains that buffer.-addBufferThen :: T.Text -> BufAction a -> Action a-addBufferThen txt act = do-  buffers <>= [newBuffer txt]-  fmap (!! 0) . Action . zoom (buffers._last) . fmap (:[]) . getBufAction $ act+-- | Gets 'BufRef' that comes after the one provided+nextBufRef :: BufRef -> Action BufRef+nextBufRef br@(BufRef bufInt) = do+  bufMap <- use buffers+  if M.null bufMap+     then return br+     else do+       let mGreaterInd = lookupGT bufInt bufMap+       case mGreaterInd of+         Just (greaterInd, _) -> return $ BufRef greaterInd+         Nothing -> return . BufRef . fst . findMin $ bufMap +-- | Gets 'BufRef' that comes before the one provided+prevBufRef :: BufRef -> Action BufRef+prevBufRef br@(BufRef bufInt) = do+  bufMap <- use buffers+  if M.null bufMap+     then return br+     else do+       let mLesserInd = lookupGT bufInt bufMap+       case mLesserInd of+         Just (lesserInd, _) -> return $ BufRef lesserInd+         Nothing -> return . BufRef . fst . findMax $ bufMap++ -- | This signals to the editor that you'd like to shutdown. The current events -- will finish processing, then the 'Rasa.Ext.Scheduler.onExit' hook will run, -- then the editor will exit.@@ -61,31 +103,19 @@ exit :: Action () exit = exiting .= True --- | Switches focus to the next buffer-nextBuf :: Action ()-nextBuf = do-  numBuffers <- use (buffers.to length)-  focused %= (`mod` numBuffers) . (+1)---- | Switches focus to the previous buffer-prevBuf :: Action ()-prevBuf = do-  numBuffers <- use (buffers.to length)-  focused %= (`mod` numBuffers) . subtract 1- -- | Deletes the text in the given range from the buffer. deleteRange :: Range -> BufAction () deleteRange r = range r.asText .= ""  -- | Replaces the text in the given range from the buffer.-replaceRange :: Range -> T.Text -> BufAction ()-replaceRange r txt = range r.asText .= txt+replaceRange :: Range -> Y.YiString -> BufAction ()+replaceRange r txt = range r .= txt  -- | Inserts text into the buffer at the given Coord.-insertAt :: Coord -> T.Text -> BufAction ()-insertAt c txt = range (Range c c).asText .= txt+insertAt :: Coord -> Y.YiString -> BufAction ()+insertAt c txt = range (Range c c) .= txt  -- | Runs the given function over the text in the range, replacing it with the results.-overRange :: Range -> (T.Text -> T.Text) -> BufAction ()-overRange r f = range r.asText %= f+overRange :: Range -> (Y.YiString -> Y.YiString) -> BufAction ()+overRange r f = range r %= f 
src/Rasa/Internal/Editor.hs view
@@ -7,10 +7,8 @@   (   -- * Accessing/Storing state   Editor--  , focused--- | 'focused' is the index of the currently selected buffer (this will likely change)-+  , HasEditor+  , editor   , buffers  -- |'buffers' is a lens into all buffers.@@ -22,10 +20,10 @@ -- > exiting .= True    , ext-  , allBufExt   , bufExt-  , focusedBuf+  , nextBufId   , range+  , BufRef(..)   ) where  import Rasa.Internal.Buffer@@ -36,22 +34,31 @@ import Data.Dynamic import Data.Default import Data.Maybe+import Data.IntMap import Control.Lens import qualified Yi.Rope as Y +-- | An opaque reference to a buffer (The contained Int is not meant to be+-- altered). It is possible for references to become stale if buffers are+-- deleted. Operations over invalid BufRef's are simply ignored and return+-- 'Nothing' if a value was expected.+newtype BufRef =+  BufRef Int+  deriving (Show, Eq)+ -- | This is the primary state of the editor. data Editor = Editor-  { _buffers :: [Buffer]-  , _focused :: Int+  { _buffers :: IntMap Buffer   , _exiting :: Bool   , _extState :: ExtMap+  , _nextBufId :: Int   }-makeLenses ''Editor+makeClassy ''Editor  instance Show Editor where-  show editor =-    "Buffers==============\n" ++ show (editor^.buffers) ++ "\n\n"-    ++ "Editor Extensions==============\n" ++ show (editor^.extState) ++ "\n\n"+  show ed =+    "Buffers==============\n" ++ show (ed^.buffers) ++ "\n\n"+    ++ "Editor Extensions==============\n" ++ show (ed^.extState) ++ "\n\n"     ++ "---\n\n"  @@ -59,23 +66,11 @@   def =     Editor     { _extState = def-    , _buffers=fmap newBuffer [ "Buffer 0\nHey! How's it going over there?\nI'm having just a splended time!\nAnother line for you sir?"-                            , "Buffer 1\nHey! How's it going over there?\nI'm having just a splended time!\nAnother line for you sir?" ]-    , _focused=0+    , _buffers=empty     , _exiting=False+    , _nextBufId=0     } --- | A lens over the extensions of all buffers.--- This is useful for setting defaults or altering extension state across all buffers.-allBufExt-  :: forall a.-     (Show a, Typeable a)-  => Traversal' Editor (Maybe a)-allBufExt =-  buffers . traverse . bufExts . at (typeRep (Proxy :: Proxy a)) . mapping coerce-  where-    coerce = iso (\(Ext x) -> unsafeCoerce x) Ext- -- | 'bufExt' is a lens which will focus a given extension's state within a -- buffer (within a 'Data.Action.BufAction'). The lens will automagically focus -- the required extension by using type inference. It's a little bit of magic,@@ -109,35 +104,23 @@ -- nothing has yet been stored.  ext-  :: forall a.-     (Show a, Typeable a, Default a)-  => Lens' Editor a+  :: forall a e.+    (Show a, Typeable a, Default a, HasEditor e)+  => Lens' e a ext = lens getter setter   where-    getter editor =-      fromMaybe def $ editor ^. extState . at (typeRep (Proxy :: Proxy a)) .+    getter ed =+      fromMaybe def $ ed ^. extState . at (typeRep (Proxy :: Proxy a)) .       mapping coerce-    setter editor new =+    setter ed new =       set         (extState . at (typeRep (Proxy :: Proxy a)) . mapping coerce)         (Just new)-        editor+        ed     coerce = iso (\(Ext x) -> unsafeCoerce x) Ext --- | 'focusedBuf' is a lens which focuses the currently selected buffer.-focusedBuf :: Lens' Editor Buffer-focusedBuf = lens getter setter-  where-    getter editor =-      let foc = editor ^. focused-      in editor ^?! buffers . ix foc-    setter editor new =-      let foc = editor ^. focused-      in editor & buffers . ix foc .~ new- -- | A lens over text which is encompassed by a 'Range' range :: Range -> Lens' Buffer Y.YiString range (Range start end) = lens getter setter-  where getter = view (rope . beforeC end . afterC start)-        setter old new = old & rope . beforeC end . afterC start .~ new-+  where getter = view (text . beforeC end . afterC start)+        setter old new = old & text . beforeC end . afterC start .~ new
src/Rasa/Internal/Events.hs view
@@ -1,7 +1,18 @@ {-# language ExistentialQuantification #-}-module Rasa.Internal.Events where+module Rasa.Internal.Events +  ( Init(..)+  , BeforeEvent(..)+  , BeforeRender(..)+  , OnRender(..)+  , AfterRender(..)+  , Exit(..)+  , BufAdded(..)+  , Keypress(..)+  , Mod(..)+  ) where  import Data.Dynamic+import Rasa.Internal.Editor  -- | The Event type represents a common denominator for all actions that could -- occur Event transmitters express events that have occured as a member of this@@ -29,6 +40,9 @@ -- any clean-up (saving files, etc.) data Exit = Exit deriving (Show, Eq, Typeable) +-- | This event is dispatched after adding a new buffer. The contained BufRef refers to the new buffer.+data BufAdded = BufAdded BufRef deriving (Show, Eq, Typeable)+ -- | This event is dispatched in response to keyboard key presses. It contains both -- the char that was pressed and any modifiers ('Mod') that where held when the key was pressed. data Keypress@@ -37,6 +51,7 @@   | Esc   | BS   | Enter+  | Unknown   deriving (Show, Eq, Typeable)  -- | This represents each modifier key that could be pressed along with a key.
src/Rasa/Internal/Scheduler.hs view
@@ -15,11 +15,13 @@   , onExit   , onInit   , onRender+  , onBufAdded   ) where   import Rasa.Internal.Action import Rasa.Internal.Events+import Rasa.Internal.Editor  import Control.Lens import Control.Monad.Reader@@ -119,3 +121,10 @@ onExit :: Action () -> Scheduler () onExit action = eventListener (const action :: Exit -> Action ()) +-- | Registers an action to be performed after a new buffer is added.+-- +-- The supplied function will be called with a 'BufRef' to the new buffer, and the resulting 'Action' will be run.+onBufAdded :: (BufRef -> Action ()) -> Scheduler ()+onBufAdded f = eventListener listener+  where+    listener (BufAdded bRef) = f bRef