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 +11/−0
- rasa.cabal +82/−81
- src/Rasa.hs +23/−24
- src/Rasa/Ext.hs +19/−18
- src/Rasa/Internal/Action.hs +49/−12
- src/Rasa/Internal/Async.hs +59/−0
- src/Rasa/Internal/Buffer.hs +7/−14
- src/Rasa/Internal/Directive.hs +69/−39
- src/Rasa/Internal/Editor.hs +30/−47
- src/Rasa/Internal/Events.hs +16/−1
- src/Rasa/Internal/Scheduler.hs +9/−0
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