rasa-ext-slate 0.1.0.0 → 0.1.2
raw patch · 11 files changed
+287/−280 lines, 11 filesdep +rasa-ext-bufsdep ~basedep ~lensdep ~rasaPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: rasa-ext-bufs
Dependency ranges changed: base, lens, rasa, rasa-ext-logger, rasa-ext-status-bar, rasa-ext-style, text, vty, yi-rope
API changes (from Hackage documentation)
- Rasa.Renderer.Slate: slate :: Scheduler ()
- Rasa.Renderer.Slate: terminalEvents :: Action [Keypress]
+ Rasa.Ext.Slate: slate :: Scheduler ()
Files
- rasa-ext-slate.cabal +39/−38
- src/Rasa/Ext/Slate.hs +28/−0
- src/Rasa/Ext/Slate/Internal/Attributes.hs +99/−0
- src/Rasa/Ext/Slate/Internal/Event.hs +34/−0
- src/Rasa/Ext/Slate/Internal/Render.hs +59/−0
- src/Rasa/Ext/Slate/Internal/State.hs +28/−0
- src/Rasa/Renderer/Slate.hs +0/−27
- src/Rasa/Renderer/Slate/Attributes.hs +0/−99
- src/Rasa/Renderer/Slate/Event.hs +0/−36
- src/Rasa/Renderer/Slate/Render.hs +0/−52
- src/Rasa/Renderer/Slate/State.hs +0/−28
rasa-ext-slate.cabal view
@@ -1,41 +1,42 @@-name: rasa-ext-slate-version: 0.1.0.0-synopsis: Rasa extension for rendering to terminal with vty-description: Rasa extension for rendering to terminal with vty-homepage: https://github.com/ChrisPenner/rasa/-license: MIT-license-file: LICENSE-author: Chris Penner-maintainer: christopher.penner@gmail.com-copyright: 2016 Chris Penner-category: Extension-build-type: Simple--- extra-source-files:-cabal-version: >=1.10--library- hs-source-dirs: src- exposed-modules:- Rasa.Renderer.Slate- other-modules:- Rasa.Renderer.Slate.Render- Rasa.Renderer.Slate.Event- Rasa.Renderer.Slate.State- Rasa.Renderer.Slate.Attributes+name: rasa-ext-slate+version: 0.1.2+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/+synopsis: Rasa extension for rendering to terminal with vty+description:+ Rasa extension for rendering to terminal with vty+category: Extension+author: Chris Penner - build-depends: base >= 4.7 && < 5- , rasa- , rasa-ext-style- , rasa-ext-status-bar- , text- , yi-rope- , lens- , vty >= 5.14- , rasa-ext-logger- default-language: Haskell2010+source-repository head+ type: git+ location: https://github.com/ChrisPenner/rasa - ghc-options: -Wall+library+ exposed-modules:+ Rasa.Ext.Slate+ build-depends:+ base >=4.8 && <5,+ rasa >=0.1.4 && <0.2,+ rasa-ext-bufs >=0.1.1 && <0.2,+ rasa-ext-style >=0.1.1 && <0.2,+ rasa-ext-status-bar >=0.1.1 && <0.2,+ text >=1.2.2.1 && <1.3,+ yi-rope >=0.7.0.2 && <0.8,+ lens ==4.14.*,+ vty ==5.14.*,+ rasa-ext-logger >=0.1.1 && <0.2+ default-language: Haskell2010+ hs-source-dirs: src+ other-modules:+ Rasa.Ext.Slate.Internal.Render+ Rasa.Ext.Slate.Internal.Event+ Rasa.Ext.Slate.Internal.State+ Rasa.Ext.Slate.Internal.Attributes+ ghc-options: -Wall -source-repository head- type: git- location: https://github.com/ChrisPenner/rasa
+ src/Rasa/Ext/Slate.hs view
@@ -0,0 +1,28 @@+module Rasa.Ext.Slate (slate) where++import Rasa.Ext+import Rasa.Ext.Slate.Internal.Render (render)+import Rasa.Ext.Slate.Internal.Event (terminalEvents)+import Rasa.Ext.Slate.Internal.State (getVty)++import qualified Graphics.Vty as V+import Control.Monad.IO.Class++-- | The main export for this extension. Add this to your user config.+--+-- e.g.+--+-- > rasa $ do+-- > slate+-- > ...+slate :: Scheduler ()+slate = do+ onInit terminalEvents+ onRender render+ onExit shutdown++-- | Call vty shutdown procedure (if this doesn't happen the terminal ends up in strange states)+shutdown :: Action ()+shutdown = do+ v <- getVty+ liftIO $ V.shutdown v
+ src/Rasa/Ext/Slate/Internal/Attributes.hs view
@@ -0,0 +1,99 @@+{-# LANGUAGE OverloadedStrings #-}+module Rasa.Ext.Slate.Internal.Attributes where++import Rasa.Ext+import Rasa.Ext.Style+import qualified Yi.Rope as Y+import qualified Graphics.Vty as V+import Control.Lens++-- | Convert style from "Rasa.Ext.Style" into 'V.Attr's+convertStyle :: Style -> V.Attr+convertStyle (Style (fg', bg', flair')) = V.Attr+ (maybe V.KeepCurrent convertFlair flair')+ (maybe V.KeepCurrent convertColor fg')+ (maybe V.KeepCurrent convertColor bg')++-- | Convert flair from "Rasa.Ext.Style" into 'V.Style's+convertFlair :: Flair -> V.MaybeDefault V.Style+convertFlair Standout = V.SetTo V.standout+convertFlair Underline = V.SetTo V.underline+convertFlair ReverseVideo = V.SetTo V.reverseVideo+convertFlair Blink = V.SetTo V.blink+convertFlair Dim = V.SetTo V.dim+convertFlair Bold = V.SetTo V.bold+convertFlair DefFlair = V.Default++-- | Convert colors from "Rasa.Ext.Style" into 'V.Color's+convertColor :: Color -> V.MaybeDefault V.Color+convertColor Black = V.SetTo V.black+convertColor Red = V.SetTo V.red+convertColor Green = V.SetTo V.green+convertColor Yellow = V.SetTo V.yellow+convertColor Blue = V.SetTo V.blue+convertColor Magenta = V.SetTo V.magenta+convertColor Cyan = V.SetTo V.cyan+convertColor White = V.SetTo V.white+convertColor DefColor = V.Default++-- | helper to reset to default attributes+reset :: V.Image+reset = V.text' V.defAttr ""++-- | A newtype to define a (not necessarily law abiding) Monoid for 'V.Attr' which acts as we like.+newtype AttrMonoid = AttrMonoid {+ attr' :: V.Attr+}++-- | We want 'mempty' to be 'V.defAttr' instead of 'V.currentAttr' for use in 'combineSpans'.+instance Monoid AttrMonoid where+ mempty = AttrMonoid V.defAttr+ AttrMonoid v `mappend` AttrMonoid v' = AttrMonoid $ v `mappend` v'++-- | Apply a list of styles to the given text, resulting in a 'V.Image'.+applyAttrs :: [Span V.Attr] -> Y.YiString -> V.Image+applyAttrs atts txt = applyAttrs' converted (Y.lines txt)+ where combined = combineSpans (atts & traverse.mapped %~ AttrMonoid)+ converted = combined & traverse._2 %~ attr'++applyAttrs' :: [(Coord, V.Attr)] -> [Y.YiString] -> V.Image+applyAttrs' atts lines' = vertCat $ uncurry attrLine <$> pairLines atts lines'+ where+ vertCat = foldr ((V.<->) . (V.<|> reset)) V.emptyImage+++-- | Applies the list of attrs to the line and returns a 'V.Image'. It assumes that the list+-- contains only 'Coord's on the same line (i.e. row == 0)+--+-- Should be able to clean this up and provide better guarantees if I do a scan+-- over attrs and get each successive mappend of them, then do T.splitAt for+-- each offset, then apply the attr for each section at the begining of each+-- of T.lines within each group. Ugly I know.+attrLine :: [(Coord, V.Attr)] -> Y.YiString -> V.Image+attrLine [] txt = plainText txt+attrLine atts "" = V.text' (mconcat (snd <$> atts)) ""+attrLine ((Coord _ 0, attr):atts) txt = V.text' attr "" V.<|> attrLine atts txt+attrLine atts@((Coord _ col, _):_) txt =+ let (prefix, suffix) = Y.splitAt col txt+ in plainText prefix V.<|> attrLine (decrCol col atts) suffix++-- | Pairs up lines with their styles.+pairLines :: [(Coord, b)] -> [a] -> [([(Coord, b)], a)]+pairLines _ [] = []+pairLines [] ls = zip (repeat []) ls+pairLines crds@((Coord 0 _, _):_) (l:ls) = (takeWhile isSameRow crds, l) : pairLines (decrRow $ dropWhile isSameRow crds) ls+ where isSameRow (Coord 0 _, _) = True+ isSameRow _ = False+pairLines crds (l:ls) = ([], l): pairLines (decrRow crds) ls++-- | Decrements the row of all future attrs' location+decrRow :: [(Coord, a)] -> [(Coord, a)]+decrRow = fmap (\(Coord r c, a) -> (Coord (r-1) c, a))++-- | Decrements the column of all future attrs' location by the given amount+decrCol :: Int -> [(Coord, a)] -> [(Coord, a)]+decrCol n = fmap (\(Coord r c, a) -> (Coord r (c-n), a))++-- | Creates a text image without any new attributes.+plainText :: Y.YiString -> V.Image+plainText = V.text' V.currentAttr . Y.toText
+ src/Rasa/Ext/Slate/Internal/Event.hs view
@@ -0,0 +1,34 @@+module Rasa.Ext.Slate.Internal.Event (terminalEvents) where++import Rasa.Ext++import Rasa.Ext.Slate.Internal.State++import qualified Graphics.Vty as V++-- | Provides keypress events from the terminal, converted from Vty events.+terminalEvents :: Action ()+terminalEvents = do+ v <- getVty+ eventProvider $ convertEvent <$> V.nextEvent v++-- | Converts a 'V.Event' into a keypress if possible.+convertEvent :: V.Event -> Keypress+convertEvent (V.EvKey e mods) = convertKeypress e mods+convertEvent _ = Unknown++-- | Converts a 'V.Event' into a keypress if possible.+convertKeypress :: V.Key -> [V.Modifier] -> Keypress+convertKeypress V.KEnter _ = Enter+convertKeypress V.KBS _ = BS+convertKeypress V.KEsc _ = Esc+convertKeypress (V.KChar c) mods = Keypress c (fmap convertMod mods)+convertKeypress _ _ = Unknown++-- | Converts a 'V.Modifier' into a 'Mod'.+convertMod :: V.Modifier -> Mod+convertMod m = case m of+ V.MShift -> Shift+ V.MCtrl -> Ctrl+ V.MMeta -> Alt+ V.MAlt -> Alt
+ src/Rasa/Ext/Slate/Internal/Render.hs view
@@ -0,0 +1,59 @@+{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, OverloadedStrings #-}+module Rasa.Ext.Slate.Internal.Render (render) where++import Rasa.Ext+import Rasa.Ext.Bufs+import Rasa.Ext.Style+import Rasa.Ext.StatusBar (left, center, right)+import Rasa.Ext.Slate.Internal.State+import Rasa.Ext.Slate.Internal.Attributes+++import qualified Graphics.Vty as V+import Control.Lens+import Control.Monad.IO.Class++import Data.Monoid+import Data.Foldable++import qualified Yi.Rope as Y++-- | Given a window size, creates a 'BufAction' which will return an image representing the buffer it's run in.+renderBuf :: (Int, Int) -> BufAction V.Image+renderBuf (width, height) = do+ txt <- use text+ atts <- fmap (fmap convertStyle) <$> use styles+ let img = applyAttrs atts txt+ return $ V.resize width height img++-- | Get the current terminal size.+getSize :: Action (Int, Int)+getSize = do+ v <- getVty+ liftIO $ V.displayBounds $ V.outputIface v++-- | Render the Editor+render :: Action ()+render = do+ (width, height) <- getSize+ mBufImg <- focusDo $ renderBuf (width, height - 1)+ case mBufImg of+ Nothing -> return ()+ Just bufImg -> do+ statusBar <- renderStatus width+ let img = bufImg V.<-> statusBar+ pic = V.picForImage img+ v <- getVty+ liftIO $ V.update v pic++-- | Render the status bar.+renderStatus :: Int -> Action V.Image+renderStatus width = fmap fold . focusDo $ do+ statuses <- use bufExt+ let spacer = Y.replicate spacerSize " "+ spacerSize = (width - Y.length (Y.concat joinedParts)) `div` 2+ barParts = [ statuses^.left, statuses^.center, statuses^.right ]+ addSpacer = (<> spacer)+ joinedParts = Y.intercalate " | " <$> barParts+ fullLine = foldMap addSpacer joinedParts+ return $ V.text' V.defAttr (Y.toText fullLine)
+ src/Rasa/Ext/Slate/Internal/State.hs view
@@ -0,0 +1,28 @@+module Rasa.Ext.Slate.Internal.State (getVty) where++import Rasa.Ext+import Control.Lens++import Control.Monad.IO.Class+import qualified Graphics.Vty as V++-- | Store 'V.Vty' state globally+newtype Slate = Slate V.Vty+instance Show Slate where+ show _ = "Slate"++-- | V.Vty must be initialized, this takes IO to perform.+initUi :: Action V.Vty+initUi = do+ cfg <- liftIO V.standardIOConfig+ v <- liftIO $ V.mkVty cfg+ ext .= Just (Slate v)+ return v++-- | Gets vty by checking if it has been initialized yet, if not it runs the initialization.+getVty :: Action V.Vty+getVty = do+ v <- use ext+ case v of+ Just (Slate v') -> return v'+ Nothing -> initUi
− src/Rasa/Renderer/Slate.hs
@@ -1,27 +0,0 @@-module Rasa.Renderer.Slate (slate, terminalEvents) where--import Rasa.Ext-import Rasa.Renderer.Slate.Render (render)-import Rasa.Renderer.Slate.Event (terminalEvents)-import Rasa.Renderer.Slate.State (getVty)--import qualified Graphics.Vty as V-import Control.Monad.IO.Class---- | The main export for this extension. Add this to your user config.------ e.g.------ > rasa [...] $ do--- > slate--- > ...-slate :: Scheduler ()-slate = do- onRender render- onExit shutdown---- | Call vty shutdown procedure (if this doesn't happen the terminal ends up in strange states)-shutdown :: Action ()-shutdown = do- v <- getVty- liftIO $ V.shutdown v
− src/Rasa/Renderer/Slate/Attributes.hs
@@ -1,99 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-module Rasa.Renderer.Slate.Attributes where--import Rasa.Ext-import Rasa.Ext.Style-import qualified Yi.Rope as Y-import qualified Graphics.Vty as V-import Control.Lens---- | Convert style from "Rasa.Ext.Style" into 'V.Attr's-convertStyle :: Style -> V.Attr-convertStyle (Style (fg', bg', flair')) = V.Attr- (maybe V.KeepCurrent convertFlair flair')- (maybe V.KeepCurrent convertColor fg')- (maybe V.KeepCurrent convertColor bg')---- | Convert flair from "Rasa.Ext.Style" into 'V.Style's-convertFlair :: Flair -> V.MaybeDefault V.Style-convertFlair Standout = V.SetTo V.standout-convertFlair Underline = V.SetTo V.underline-convertFlair ReverseVideo = V.SetTo V.reverseVideo-convertFlair Blink = V.SetTo V.blink-convertFlair Dim = V.SetTo V.dim-convertFlair Bold = V.SetTo V.bold-convertFlair DefFlair = V.Default---- | Convert colors from "Rasa.Ext.Style" into 'V.Color's-convertColor :: Color -> V.MaybeDefault V.Color-convertColor Black = V.SetTo V.black-convertColor Red = V.SetTo V.red-convertColor Green = V.SetTo V.green-convertColor Yellow = V.SetTo V.yellow-convertColor Blue = V.SetTo V.blue-convertColor Magenta = V.SetTo V.magenta-convertColor Cyan = V.SetTo V.cyan-convertColor White = V.SetTo V.white-convertColor DefColor = V.Default---- | helper to reset to default attributes-reset :: V.Image-reset = V.text' V.defAttr ""---- | A newtype to define a (not necessarily law abiding) Monoid for 'V.Attr' which acts as we like.-newtype AttrMonoid = AttrMonoid {- attr' :: V.Attr-}---- | We want 'mempty' to be 'V.defAttr' instead of 'V.currentAttr' for use in 'combineSpans'.-instance Monoid AttrMonoid where- mempty = AttrMonoid V.defAttr- AttrMonoid v `mappend` AttrMonoid v' = AttrMonoid $ v `mappend` v'---- | Apply a list of styles to the given text, resulting in a 'V.Image'.-applyAttrs :: [Span V.Attr] -> Y.YiString -> V.Image-applyAttrs atts txt = applyAttrs' converted (Y.lines txt)- where combined = combineSpans (atts & traverse.mapped %~ AttrMonoid)- converted = combined & traverse._2 %~ attr'--applyAttrs' :: [(Coord, V.Attr)] -> [Y.YiString] -> V.Image-applyAttrs' atts lines' = vertCat $ uncurry attrLine <$> pairLines atts lines'- where- vertCat = foldr ((V.<->) . (V.<|> reset)) V.emptyImage----- | Applies the list of attrs to the line and returns a 'V.Image'. It assumes that the list--- contains only 'Coord's on the same line (i.e. row == 0)------ Should be able to clean this up and provide better guarantees if I do a scan--- over attrs and get each successive mappend of them, then do T.splitAt for--- each offset, then apply the attr for each section at the begining of each--- of T.lines within each group. Ugly I know.-attrLine :: [(Coord, V.Attr)] -> Y.YiString -> V.Image-attrLine [] txt = plainText txt-attrLine atts "" = V.text' (mconcat (snd <$> atts)) ""-attrLine ((Coord _ 0, attr):atts) txt = V.text' attr "" V.<|> attrLine atts txt-attrLine atts@((Coord _ col, _):_) txt =- let (prefix, suffix) = Y.splitAt col txt- in plainText prefix V.<|> attrLine (decrCol col atts) suffix---- | Pairs up lines with their styles.-pairLines :: [(Coord, b)] -> [a] -> [([(Coord, b)], a)]-pairLines _ [] = []-pairLines [] ls = zip (repeat []) ls-pairLines crds@((Coord 0 _, _):_) (l:ls) = (takeWhile isSameRow crds, l) : pairLines (decrRow $ dropWhile isSameRow crds) ls- where isSameRow (Coord 0 _, _) = True- isSameRow _ = False-pairLines crds (l:ls) = ([], l): pairLines (decrRow crds) ls---- | Decrements the row of all future attrs' location-decrRow :: [(Coord, a)] -> [(Coord, a)]-decrRow = fmap (\(Coord r c, a) -> (Coord (r-1) c, a))---- | Decrements the column of all future attrs' location by the given amount-decrCol :: Int -> [(Coord, a)] -> [(Coord, a)]-decrCol n = fmap (\(Coord r c, a) -> (Coord r (c-n), a))---- | Creates a text image without any new attributes.-plainText :: Y.YiString -> V.Image-plainText = V.text' V.currentAttr . Y.toText
− src/Rasa/Renderer/Slate/Event.hs
@@ -1,36 +0,0 @@-module Rasa.Renderer.Slate.Event (terminalEvents) where--import Rasa.Ext--import Rasa.Renderer.Slate.State-import Control.Monad.IO.Class-import Data.Maybe--import qualified Graphics.Vty as V---- | Provides keypress events from the terminal, converted from Vty events.-terminalEvents :: Action [Keypress]-terminalEvents = do- v <- getVty- liftIO $ maybeToList . convertEvent <$> V.nextEvent v---- | Converts a 'V.Event' into a keypress if possible.-convertEvent :: V.Event -> Maybe Keypress-convertEvent (V.EvKey e mods) = convertKeypress e mods-convertEvent _ = Nothing---- | Converts a 'V.Event' into a keypress if possible.-convertKeypress :: V.Key -> [V.Modifier] -> Maybe Keypress-convertKeypress V.KEnter _ = Just Enter-convertKeypress V.KBS _ = Just BS-convertKeypress V.KEsc _ = Just Esc-convertKeypress (V.KChar c) mods = Just $ Keypress c (fmap convertMod mods)-convertKeypress _ _ = Nothing---- | Converts a 'V.Modifier' into a 'Mod'.-convertMod :: V.Modifier -> Mod-convertMod m = case m of- V.MShift -> Shift- V.MCtrl -> Ctrl- V.MMeta -> Alt- V.MAlt -> Alt
− src/Rasa/Renderer/Slate/Render.hs
@@ -1,52 +0,0 @@-{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, OverloadedStrings #-}-module Rasa.Renderer.Slate.Render (render) where--import Rasa.Ext-import Rasa.Ext.Style-import Rasa.Ext.StatusBar (left, center, right)-import Rasa.Renderer.Slate.State-import Rasa.Renderer.Slate.Attributes-import Control.Monad.IO.Class--import qualified Graphics.Vty as V-import Control.Lens--import qualified Data.Text as T-import Data.Monoid---- | Given a window size, creates a 'BufAction' which will return an image representing the buffer it's run in.-renderBuf :: (Int, Int) -> BufAction V.Image-renderBuf (width, height) = do- txt <- use rope- atts <- fmap (fmap convertStyle) <$> use styles- let img = applyAttrs atts txt- return $ V.resize width height img---- | Get the current terminal size.-getSize :: Action (Int, Int)-getSize = do- v <- getVty- liftIO $ V.displayBounds $ V.outputIface v---- | Render the Editor-render :: Action ()-render = do- (width, height) <- getSize- bufImg <- focusDo $ renderBuf (width, height - 1)- statusBar <- renderStatus width- let img = bufImg V.<-> statusBar- pic = V.picForImage img- v <- getVty- liftIO $ V.update v pic---- | Render the status bar.-renderStatus :: Int -> Action V.Image-renderStatus width = focusDo $ do- statuses <- use bufExt- let spacer = T.replicate spacerSize " "- spacerSize = (width - T.length (T.concat joinedParts)) `div` 2- barParts = [ statuses^.left, statuses^.center, statuses^.right ]- addSpacer = (<> spacer)- joinedParts = T.intercalate " | " <$> barParts- fullLine = foldMap addSpacer joinedParts- return $ V.text' V.defAttr fullLine
− src/Rasa/Renderer/Slate/State.hs
@@ -1,28 +0,0 @@-module Rasa.Renderer.Slate.State (getVty) where--import Rasa.Ext-import Control.Lens--import Control.Monad.IO.Class-import qualified Graphics.Vty as V---- | Store 'V.Vty' state globally-newtype Slate = Slate V.Vty-instance Show Slate where- show _ = "Slate"---- | V.Vty must be initialized, this takes IO to perform.-initUi :: Action V.Vty-initUi = do- cfg <- liftIO V.standardIOConfig- v <- liftIO $ V.mkVty cfg- ext .= Just (Slate v)- return v---- | Gets vty by checking if it has been initialized yet, if not it runs the initialization.-getVty :: Action V.Vty-getVty = do- v <- use ext- case v of- Just (Slate v') -> return v'- Nothing -> initUi