packages feed

patat 0.9.2.0 → 0.10.0.0

raw patch · 22 files changed

+1538/−816 lines, 22 filesdep +randomdep +vectorPVP ok

version bump matches the API change (PVP)

Dependencies added: random, vector

API changes (from Hackage documentation)

- Patat.Images: new :: ImageSettings -> IO Handle
- Patat.Presentation: getDisplaySize :: Presentation -> IO Size
- Patat.Presentation.Display: data Size
- Patat.Presentation.Display: getDisplaySize :: Presentation -> IO Size
- Patat.Presentation.Display: instance GHC.Show.Show Patat.Presentation.Display.Size
- Patat.Presentation.Interactive: instance GHC.Show.Show Patat.Presentation.Interactive.UpdatedPresentation
- Patat.Presentation.Internal: [slideSpeakerNotes] :: Slide -> !SpeakerNotes
- Patat.Presentation.Internal: instance Data.Aeson.Types.FromJSON.FromJSON Patat.Presentation.Internal.EvalSettings
- Patat.Presentation.Internal: instance Data.Aeson.Types.FromJSON.FromJSON Patat.Presentation.Internal.ExtensionList
- Patat.Presentation.Internal: instance Data.Aeson.Types.FromJSON.FromJSON Patat.Presentation.Internal.ImageSettings
- Patat.Presentation.Internal: instance Data.Aeson.Types.FromJSON.FromJSON Patat.Presentation.Internal.Margins
- Patat.Presentation.Internal: instance Data.Aeson.Types.FromJSON.FromJSON Patat.Presentation.Internal.PresentationSettings
- Patat.Presentation.Internal: instance GHC.Base.Monoid Patat.Presentation.Internal.Margins
- Patat.Presentation.Internal: instance GHC.Base.Monoid Patat.Presentation.Internal.PresentationSettings
- Patat.Presentation.Internal: instance GHC.Base.Semigroup Patat.Presentation.Internal.Margins
- Patat.Presentation.Internal: instance GHC.Base.Semigroup Patat.Presentation.Internal.PresentationSettings
- Patat.Presentation.Internal: instance GHC.Show.Show Patat.Presentation.Internal.EvalSettings
- Patat.Presentation.Internal: instance GHC.Show.Show Patat.Presentation.Internal.ExtensionList
- Patat.Presentation.Internal: instance GHC.Show.Show Patat.Presentation.Internal.ImageSettings
- Patat.Presentation.Internal: instance GHC.Show.Show Patat.Presentation.Internal.Presentation
- Patat.Presentation.Internal: instance GHC.Show.Show Patat.Presentation.Internal.PresentationSettings
- Patat.Presentation.Internal: marginsOf :: PresentationSettings -> (Int, Int)
- Patat.Presentation.SpeakerNotes: data Handle
- Patat.Presentation.SpeakerNotes: data Settings
- Patat.Presentation.SpeakerNotes: data SpeakerNotes
- Patat.Presentation.SpeakerNotes: instance Data.Aeson.Types.FromJSON.FromJSON Patat.Presentation.SpeakerNotes.Settings
- Patat.Presentation.SpeakerNotes: instance GHC.Base.Monoid Patat.Presentation.SpeakerNotes.SpeakerNotes
- Patat.Presentation.SpeakerNotes: instance GHC.Base.Semigroup Patat.Presentation.SpeakerNotes.SpeakerNotes
- Patat.Presentation.SpeakerNotes: instance GHC.Classes.Eq Patat.Presentation.SpeakerNotes.SpeakerNotes
- Patat.Presentation.SpeakerNotes: instance GHC.Show.Show Patat.Presentation.SpeakerNotes.Settings
- Patat.Presentation.SpeakerNotes: instance GHC.Show.Show Patat.Presentation.SpeakerNotes.SpeakerNotes
- Patat.Presentation.SpeakerNotes: parse :: Block -> Maybe SpeakerNotes
- Patat.Presentation.SpeakerNotes: partition :: [Block] -> (SpeakerNotes, [Block])
- Patat.Presentation.SpeakerNotes: remove :: [Block] -> [Block]
- Patat.Presentation.SpeakerNotes: split :: [Block] -> (SpeakerNotes, [Block])
- Patat.Presentation.SpeakerNotes: toText :: SpeakerNotes -> Text
- Patat.Presentation.SpeakerNotes: with :: Settings -> (Handle -> IO a) -> IO a
- Patat.Presentation.SpeakerNotes: write :: Handle -> EncodingFallback -> SpeakerNotes -> IO ()
- Patat.PrettyPrint: instance Data.Foldable.Foldable Patat.PrettyPrint.Trimmable
- Patat.PrettyPrint: instance Data.String.IsString Patat.PrettyPrint.Doc
- Patat.PrettyPrint: instance Data.Traversable.Traversable Patat.PrettyPrint.Trimmable
- Patat.PrettyPrint: instance GHC.Base.Functor Patat.PrettyPrint.DocE
- Patat.PrettyPrint: instance GHC.Base.Functor Patat.PrettyPrint.Trimmable
- Patat.PrettyPrint: instance GHC.Base.Monoid Patat.PrettyPrint.Doc
- Patat.PrettyPrint: instance GHC.Base.Semigroup Patat.PrettyPrint.Doc
- Patat.PrettyPrint: instance GHC.Classes.Eq Patat.PrettyPrint.Chunk
- Patat.PrettyPrint: instance GHC.Classes.Eq Patat.PrettyPrint.Control
- Patat.PrettyPrint: instance GHC.Show.Show Patat.PrettyPrint.Doc
+ Patat.AutoAdvance: maybeAutoAdvance :: Maybe Int -> Chan PresentationCommand -> (Chan PresentationCommand -> IO a) -> IO a
+ Patat.Images: withHandle :: ImageSettings -> (Handle -> IO a) -> IO a
+ Patat.Presentation: [pSlideSettings] :: Presentation -> !Seq PresentationSettings
+ Patat.Presentation: [pTransitionGens] :: Presentation -> !Seq (Maybe TransitionGen)
+ Patat.Presentation: [psTransition] :: PresentationSettings -> !Maybe TransitionSettings
+ Patat.Presentation: getPresentationSize :: Presentation -> IO Size
+ Patat.Presentation.Comments: Comment :: SpeakerNotes -> Either String PresentationSettings -> Comment
+ Patat.Presentation.Comments: [cConfig] :: Comment -> Either String PresentationSettings
+ Patat.Presentation.Comments: [cSpeakerNotes] :: Comment -> SpeakerNotes
+ Patat.Presentation.Comments: data Comment
+ Patat.Presentation.Comments: data SpeakerNotes
+ Patat.Presentation.Comments: data SpeakerNotesHandle
+ Patat.Presentation.Comments: instance GHC.Base.Monoid Patat.Presentation.Comments.Comment
+ Patat.Presentation.Comments: instance GHC.Base.Monoid Patat.Presentation.Comments.SpeakerNotes
+ Patat.Presentation.Comments: instance GHC.Base.Semigroup Patat.Presentation.Comments.Comment
+ Patat.Presentation.Comments: instance GHC.Base.Semigroup Patat.Presentation.Comments.SpeakerNotes
+ Patat.Presentation.Comments: instance GHC.Classes.Eq Patat.Presentation.Comments.SpeakerNotes
+ Patat.Presentation.Comments: instance GHC.Show.Show Patat.Presentation.Comments.Comment
+ Patat.Presentation.Comments: instance GHC.Show.Show Patat.Presentation.Comments.SpeakerNotes
+ Patat.Presentation.Comments: parse :: Block -> Maybe Comment
+ Patat.Presentation.Comments: parseSlideSettings :: Comment -> Either String PresentationSettings
+ Patat.Presentation.Comments: partition :: [Block] -> (Comment, [Block])
+ Patat.Presentation.Comments: remove :: [Block] -> [Block]
+ Patat.Presentation.Comments: speakerNotesToText :: SpeakerNotes -> Text
+ Patat.Presentation.Comments: split :: [Block] -> (Comment, [Block])
+ Patat.Presentation.Comments: withSpeakerNotesHandle :: SpeakerNotesSettings -> (SpeakerNotesHandle -> IO a) -> IO a
+ Patat.Presentation.Comments: writeSpeakerNotes :: SpeakerNotesHandle -> EncodingFallback -> SpeakerNotes -> IO ()
+ Patat.Presentation.Internal: MarginSettings :: !Maybe (FlexibleNum Int) -> !Maybe (FlexibleNum Int) -> !Maybe (FlexibleNum Int) -> MarginSettings
+ Patat.Presentation.Internal: [mTop] :: Margins -> Int
+ Patat.Presentation.Internal: [msLeft] :: MarginSettings -> !Maybe (FlexibleNum Int)
+ Patat.Presentation.Internal: [msRight] :: MarginSettings -> !Maybe (FlexibleNum Int)
+ Patat.Presentation.Internal: [msTop] :: MarginSettings -> !Maybe (FlexibleNum Int)
+ Patat.Presentation.Internal: [pSlideSettings] :: Presentation -> !Seq PresentationSettings
+ Patat.Presentation.Internal: [pTransitionGens] :: Presentation -> !Seq (Maybe TransitionGen)
+ Patat.Presentation.Internal: [psTransition] :: PresentationSettings -> !Maybe TransitionSettings
+ Patat.Presentation.Internal: [slideComment] :: Slide -> !Comment
+ Patat.Presentation.Internal: activeSettings :: Presentation -> PresentationSettings
+ Patat.Presentation.Internal: data MarginSettings
+ Patat.Presentation.Internal: data Size
+ Patat.Presentation.Internal: getPresentationSize :: Presentation -> IO Size
+ Patat.Presentation.Internal: getSettings :: Int -> Presentation -> PresentationSettings
+ Patat.Presentation.Internal: margins :: PresentationSettings -> Margins
+ Patat.Presentation.Settings: EvalSettings :: !Text -> !Bool -> !Bool -> EvalSettings
+ Patat.Presentation.Settings: ExtensionList :: Extensions -> ExtensionList
+ Patat.Presentation.Settings: ImageSettings :: !Text -> !Object -> ImageSettings
+ Patat.Presentation.Settings: MarginSettings :: !Maybe (FlexibleNum Int) -> !Maybe (FlexibleNum Int) -> !Maybe (FlexibleNum Int) -> MarginSettings
+ Patat.Presentation.Settings: PresentationSettings :: !Maybe (FlexibleNum Int) -> !Maybe (FlexibleNum Int) -> !Maybe MarginSettings -> !Maybe Bool -> !Maybe Theme -> !Maybe Bool -> !Maybe (FlexibleNum Int) -> !Maybe Int -> !Maybe ExtensionList -> !Maybe ImageSettings -> !Maybe Bool -> !Maybe EvalSettingsMap -> !Maybe Bool -> !Maybe [FilePath] -> !Maybe SpeakerNotesSettings -> !Maybe TransitionSettings -> PresentationSettings
+ Patat.Presentation.Settings: SpeakerNotesSettings :: !FilePath -> SpeakerNotesSettings
+ Patat.Presentation.Settings: TransitionSettings :: !Text -> !Object -> TransitionSettings
+ Patat.Presentation.Settings: [evalCommand] :: EvalSettings -> !Text
+ Patat.Presentation.Settings: [evalFragment] :: EvalSettings -> !Bool
+ Patat.Presentation.Settings: [evalReplace] :: EvalSettings -> !Bool
+ Patat.Presentation.Settings: [isBackend] :: ImageSettings -> !Text
+ Patat.Presentation.Settings: [isParams] :: ImageSettings -> !Object
+ Patat.Presentation.Settings: [msLeft] :: MarginSettings -> !Maybe (FlexibleNum Int)
+ Patat.Presentation.Settings: [msRight] :: MarginSettings -> !Maybe (FlexibleNum Int)
+ Patat.Presentation.Settings: [msTop] :: MarginSettings -> !Maybe (FlexibleNum Int)
+ Patat.Presentation.Settings: [psAutoAdvanceDelay] :: PresentationSettings -> !Maybe (FlexibleNum Int)
+ Patat.Presentation.Settings: [psBreadcrumbs] :: PresentationSettings -> !Maybe Bool
+ Patat.Presentation.Settings: [psColumns] :: PresentationSettings -> !Maybe (FlexibleNum Int)
+ Patat.Presentation.Settings: [psEval] :: PresentationSettings -> !Maybe EvalSettingsMap
+ Patat.Presentation.Settings: [psImages] :: PresentationSettings -> !Maybe ImageSettings
+ Patat.Presentation.Settings: [psIncrementalLists] :: PresentationSettings -> !Maybe Bool
+ Patat.Presentation.Settings: [psMargins] :: PresentationSettings -> !Maybe MarginSettings
+ Patat.Presentation.Settings: [psPandocExtensions] :: PresentationSettings -> !Maybe ExtensionList
+ Patat.Presentation.Settings: [psRows] :: PresentationSettings -> !Maybe (FlexibleNum Int)
+ Patat.Presentation.Settings: [psSlideLevel] :: PresentationSettings -> !Maybe Int
+ Patat.Presentation.Settings: [psSlideNumber] :: PresentationSettings -> !Maybe Bool
+ Patat.Presentation.Settings: [psSpeakerNotes] :: PresentationSettings -> !Maybe SpeakerNotesSettings
+ Patat.Presentation.Settings: [psSyntaxDefinitions] :: PresentationSettings -> !Maybe [FilePath]
+ Patat.Presentation.Settings: [psTheme] :: PresentationSettings -> !Maybe Theme
+ Patat.Presentation.Settings: [psTransition] :: PresentationSettings -> !Maybe TransitionSettings
+ Patat.Presentation.Settings: [psWrap] :: PresentationSettings -> !Maybe Bool
+ Patat.Presentation.Settings: [snsFile] :: SpeakerNotesSettings -> !FilePath
+ Patat.Presentation.Settings: [tsParams] :: TransitionSettings -> !Object
+ Patat.Presentation.Settings: [tsType] :: TransitionSettings -> !Text
+ Patat.Presentation.Settings: [unExtensionList] :: ExtensionList -> Extensions
+ Patat.Presentation.Settings: data EvalSettings
+ Patat.Presentation.Settings: data ImageSettings
+ Patat.Presentation.Settings: data MarginSettings
+ Patat.Presentation.Settings: data PresentationSettings
+ Patat.Presentation.Settings: data SpeakerNotesSettings
+ Patat.Presentation.Settings: data TransitionSettings
+ Patat.Presentation.Settings: defaultExtensionList :: ExtensionList
+ Patat.Presentation.Settings: defaultPresentationSettings :: PresentationSettings
+ Patat.Presentation.Settings: instance Data.Aeson.Types.FromJSON.FromJSON Patat.Presentation.Settings.EvalSettings
+ Patat.Presentation.Settings: instance Data.Aeson.Types.FromJSON.FromJSON Patat.Presentation.Settings.ExtensionList
+ Patat.Presentation.Settings: instance Data.Aeson.Types.FromJSON.FromJSON Patat.Presentation.Settings.ImageSettings
+ Patat.Presentation.Settings: instance Data.Aeson.Types.FromJSON.FromJSON Patat.Presentation.Settings.MarginSettings
+ Patat.Presentation.Settings: instance Data.Aeson.Types.FromJSON.FromJSON Patat.Presentation.Settings.PresentationSettings
+ Patat.Presentation.Settings: instance Data.Aeson.Types.FromJSON.FromJSON Patat.Presentation.Settings.SpeakerNotesSettings
+ Patat.Presentation.Settings: instance Data.Aeson.Types.FromJSON.FromJSON Patat.Presentation.Settings.TransitionSettings
+ Patat.Presentation.Settings: instance GHC.Base.Monoid Patat.Presentation.Settings.MarginSettings
+ Patat.Presentation.Settings: instance GHC.Base.Monoid Patat.Presentation.Settings.PresentationSettings
+ Patat.Presentation.Settings: instance GHC.Base.Semigroup Patat.Presentation.Settings.MarginSettings
+ Patat.Presentation.Settings: instance GHC.Base.Semigroup Patat.Presentation.Settings.PresentationSettings
+ Patat.Presentation.Settings: instance GHC.Show.Show Patat.Presentation.Settings.EvalSettings
+ Patat.Presentation.Settings: instance GHC.Show.Show Patat.Presentation.Settings.ExtensionList
+ Patat.Presentation.Settings: instance GHC.Show.Show Patat.Presentation.Settings.ImageSettings
+ Patat.Presentation.Settings: instance GHC.Show.Show Patat.Presentation.Settings.MarginSettings
+ Patat.Presentation.Settings: instance GHC.Show.Show Patat.Presentation.Settings.PresentationSettings
+ Patat.Presentation.Settings: instance GHC.Show.Show Patat.Presentation.Settings.SpeakerNotesSettings
+ Patat.Presentation.Settings: instance GHC.Show.Show Patat.Presentation.Settings.TransitionSettings
+ Patat.Presentation.Settings: newtype ExtensionList
+ Patat.Presentation.Settings: type EvalSettingsMap = HashMap Text EvalSettings
+ Patat.PrettyPrint: char :: Char -> Doc
+ Patat.PrettyPrint.Internal: Ansi :: ([SGR] -> [SGR]) -> d -> DocE d
+ Patat.PrettyPrint.Internal: ClearScreenControl :: Control
+ Patat.PrettyPrint.Internal: Control :: Control -> DocE d
+ Patat.PrettyPrint.Internal: ControlChunk :: Control -> Chunk
+ Patat.PrettyPrint.Internal: Doc :: [DocE Doc] -> Doc
+ Patat.PrettyPrint.Internal: GoToLineControl :: Int -> Control
+ Patat.PrettyPrint.Internal: Hardline :: DocE d
+ Patat.PrettyPrint.Internal: Hardspace :: DocE d
+ Patat.PrettyPrint.Internal: Indent :: LineBuffer -> LineBuffer -> d -> DocE d
+ Patat.PrettyPrint.Internal: NewlineChunk :: Chunk
+ Patat.PrettyPrint.Internal: NotTrimmable :: !a -> Trimmable a
+ Patat.PrettyPrint.Internal: Softline :: DocE d
+ Patat.PrettyPrint.Internal: Softspace :: DocE d
+ Patat.PrettyPrint.Internal: String :: String -> DocE d
+ Patat.PrettyPrint.Internal: StringChunk :: [SGR] -> String -> Chunk
+ Patat.PrettyPrint.Internal: Trimmable :: !a -> Trimmable a
+ Patat.PrettyPrint.Internal: WrapAt :: Maybe Int -> d -> DocE d
+ Patat.PrettyPrint.Internal: [ansiCode] :: DocE d -> [SGR] -> [SGR]
+ Patat.PrettyPrint.Internal: [ansiDoc] :: DocE d -> d
+ Patat.PrettyPrint.Internal: [indentDoc] :: DocE d -> d
+ Patat.PrettyPrint.Internal: [indentFirstLine] :: DocE d -> LineBuffer
+ Patat.PrettyPrint.Internal: [indentOtherLines] :: DocE d -> LineBuffer
+ Patat.PrettyPrint.Internal: [unDoc] :: Doc -> [DocE Doc]
+ Patat.PrettyPrint.Internal: [wrapAtCol] :: DocE d -> Maybe Int
+ Patat.PrettyPrint.Internal: [wrapDoc] :: DocE d -> d
+ Patat.PrettyPrint.Internal: chunkLines :: Chunks -> [Chunks]
+ Patat.PrettyPrint.Internal: chunkToDocE :: Chunk -> DocE Doc
+ Patat.PrettyPrint.Internal: chunkToString :: Chunk -> String
+ Patat.PrettyPrint.Internal: data Chunk
+ Patat.PrettyPrint.Internal: data Control
+ Patat.PrettyPrint.Internal: data DocE d
+ Patat.PrettyPrint.Internal: data Trimmable a
+ Patat.PrettyPrint.Internal: dimensions :: Doc -> (Int, Int)
+ Patat.PrettyPrint.Internal: docToChunks :: Doc -> Chunks
+ Patat.PrettyPrint.Internal: hPutDoc :: Handle -> Doc -> IO ()
+ Patat.PrettyPrint.Internal: instance Data.Foldable.Foldable Patat.PrettyPrint.Internal.Trimmable
+ Patat.PrettyPrint.Internal: instance Data.String.IsString Patat.PrettyPrint.Internal.Doc
+ Patat.PrettyPrint.Internal: instance Data.Traversable.Traversable Patat.PrettyPrint.Internal.Trimmable
+ Patat.PrettyPrint.Internal: instance GHC.Base.Functor Patat.PrettyPrint.Internal.DocE
+ Patat.PrettyPrint.Internal: instance GHC.Base.Functor Patat.PrettyPrint.Internal.Trimmable
+ Patat.PrettyPrint.Internal: instance GHC.Base.Monoid Patat.PrettyPrint.Internal.Doc
+ Patat.PrettyPrint.Internal: instance GHC.Base.Semigroup Patat.PrettyPrint.Internal.Doc
+ Patat.PrettyPrint.Internal: instance GHC.Classes.Eq Patat.PrettyPrint.Internal.Chunk
+ Patat.PrettyPrint.Internal: instance GHC.Classes.Eq Patat.PrettyPrint.Internal.Control
+ Patat.PrettyPrint.Internal: instance GHC.Show.Show Patat.PrettyPrint.Internal.Doc
+ Patat.PrettyPrint.Internal: mkDoc :: DocE Doc -> Doc
+ Patat.PrettyPrint.Internal: newtype Doc
+ Patat.PrettyPrint.Internal: null :: Doc -> Bool
+ Patat.PrettyPrint.Internal: putDoc :: Doc -> IO ()
+ Patat.PrettyPrint.Internal: string :: String -> Doc
+ Patat.PrettyPrint.Internal: toString :: Doc -> String
+ Patat.PrettyPrint.Internal: type Chunks = [Chunk]
+ Patat.PrettyPrint.Matrix: Cell :: [SGR] -> Char -> Cell
+ Patat.PrettyPrint.Matrix: data Cell
+ Patat.PrettyPrint.Matrix: docToMatrix :: Size -> Doc -> Matrix
+ Patat.PrettyPrint.Matrix: emptyCell :: Cell
+ Patat.PrettyPrint.Matrix: hPutMatrix :: Handle -> Size -> Matrix -> IO ()
+ Patat.PrettyPrint.Matrix: instance GHC.Classes.Eq Patat.PrettyPrint.Matrix.Cell
+ Patat.PrettyPrint.Matrix: instance GHC.Show.Show Patat.PrettyPrint.Matrix.Cell
+ Patat.PrettyPrint.Matrix: type Matrix = Vector Cell
+ Patat.Size: Size :: Int -> Int -> Size
+ Patat.Size: [sCols] :: Size -> Int
+ Patat.Size: [sRows] :: Size -> Int
+ Patat.Size: data Size
+ Patat.Size: getTerminalSize :: IO Size
+ Patat.Size: instance GHC.Show.Show Patat.Size.Size
+ Patat.Transition: Duration :: Double -> Duration
+ Patat.Transition: TransitionInstance :: TransitionId -> Size -> NonEmpty (Matrix, Duration) -> TransitionInstance
+ Patat.Transition: [tiFrames] :: TransitionInstance -> NonEmpty (Matrix, Duration)
+ Patat.Transition: [tiId] :: TransitionInstance -> TransitionId
+ Patat.Transition: [tiSize] :: TransitionInstance -> Size
+ Patat.Transition: data TransitionId
+ Patat.Transition: data TransitionInstance
+ Patat.Transition: newTransition :: TransitionGen -> Size -> Doc -> Doc -> IO TransitionInstance
+ Patat.Transition: newtype Duration
+ Patat.Transition: parseTransitionSettings :: TransitionSettings -> Either String TransitionGen
+ Patat.Transition: stepTransition :: TransitionId -> TransitionInstance -> Maybe TransitionInstance
+ Patat.Transition: threadDelayDuration :: Duration -> IO ()
+ Patat.Transition: type TransitionGen = Size -> Matrix -> Matrix -> StdGen -> NonEmpty (Matrix, Duration)
+ Patat.Transition.Internal: Duration :: Double -> Duration
+ Patat.Transition.Internal: TransitionInstance :: TransitionId -> Size -> NonEmpty (Matrix, Duration) -> TransitionInstance
+ Patat.Transition.Internal: [Transition] :: FromJSON conf => (conf -> TransitionGen) -> Transition
+ Patat.Transition.Internal: [tiFrames] :: TransitionInstance -> NonEmpty (Matrix, Duration)
+ Patat.Transition.Internal: [tiId] :: TransitionInstance -> TransitionId
+ Patat.Transition.Internal: [tiSize] :: TransitionInstance -> Size
+ Patat.Transition.Internal: data Transition
+ Patat.Transition.Internal: data TransitionId
+ Patat.Transition.Internal: data TransitionInstance
+ Patat.Transition.Internal: instance GHC.Classes.Eq Patat.Transition.Internal.TransitionId
+ Patat.Transition.Internal: instance GHC.Show.Show Patat.Transition.Internal.Duration
+ Patat.Transition.Internal: newTransition :: TransitionGen -> Size -> Doc -> Doc -> IO TransitionInstance
+ Patat.Transition.Internal: newtype Duration
+ Patat.Transition.Internal: stepTransition :: TransitionId -> TransitionInstance -> Maybe TransitionInstance
+ Patat.Transition.Internal: threadDelayDuration :: Duration -> IO ()
+ Patat.Transition.Internal: type TransitionGen = Size -> Matrix -> Matrix -> StdGen -> NonEmpty (Matrix, Duration)
+ Patat.Transition.SlideLeft: instance Data.Aeson.Types.FromJSON.FromJSON Patat.Transition.SlideLeft.Config
+ Patat.Transition.SlideLeft: slideLeft :: Config -> TransitionGen
- Patat.AutoAdvance: autoAdvance :: Int -> Chan PresentationCommand -> IO (Chan PresentationCommand)
+ Patat.AutoAdvance: autoAdvance :: Int -> Chan PresentationCommand -> (Chan PresentationCommand -> IO a) -> IO a
- Patat.Presentation: Presentation :: !FilePath -> !EncodingFallback -> ![Inline] -> ![Inline] -> !PresentationSettings -> !Seq Slide -> !Seq Breadcrumbs -> !Index -> !SyntaxMap -> Presentation
+ Patat.Presentation: Presentation :: !FilePath -> !EncodingFallback -> ![Inline] -> ![Inline] -> !PresentationSettings -> !Seq Slide -> !Seq Breadcrumbs -> !Seq PresentationSettings -> !Seq (Maybe TransitionGen) -> !Index -> !SyntaxMap -> Presentation
- Patat.Presentation: PresentationSettings :: !Maybe (FlexibleNum Int) -> !Maybe (FlexibleNum Int) -> !Maybe Margins -> !Maybe Bool -> !Maybe Theme -> !Maybe Bool -> !Maybe (FlexibleNum Int) -> !Maybe Int -> !Maybe ExtensionList -> !Maybe ImageSettings -> !Maybe Bool -> !Maybe EvalSettingsMap -> !Maybe Bool -> !Maybe [FilePath] -> !Maybe Settings -> PresentationSettings
+ Patat.Presentation: PresentationSettings :: !Maybe (FlexibleNum Int) -> !Maybe (FlexibleNum Int) -> !Maybe MarginSettings -> !Maybe Bool -> !Maybe Theme -> !Maybe Bool -> !Maybe (FlexibleNum Int) -> !Maybe Int -> !Maybe ExtensionList -> !Maybe ImageSettings -> !Maybe Bool -> !Maybe EvalSettingsMap -> !Maybe Bool -> !Maybe [FilePath] -> !Maybe SpeakerNotesSettings -> !Maybe TransitionSettings -> PresentationSettings
- Patat.Presentation: [psMargins] :: PresentationSettings -> !Maybe Margins
+ Patat.Presentation: [psMargins] :: PresentationSettings -> !Maybe MarginSettings
- Patat.Presentation: [psSpeakerNotes] :: PresentationSettings -> !Maybe Settings
+ Patat.Presentation: [psSpeakerNotes] :: PresentationSettings -> !Maybe SpeakerNotesSettings
- Patat.Presentation.Internal: Margins :: !Maybe (FlexibleNum Int) -> !Maybe (FlexibleNum Int) -> Margins
+ Patat.Presentation.Internal: Margins :: Int -> Int -> Int -> Margins
- Patat.Presentation.Internal: Presentation :: !FilePath -> !EncodingFallback -> ![Inline] -> ![Inline] -> !PresentationSettings -> !Seq Slide -> !Seq Breadcrumbs -> !Index -> !SyntaxMap -> Presentation
+ Patat.Presentation.Internal: Presentation :: !FilePath -> !EncodingFallback -> ![Inline] -> ![Inline] -> !PresentationSettings -> !Seq Slide -> !Seq Breadcrumbs -> !Seq PresentationSettings -> !Seq (Maybe TransitionGen) -> !Index -> !SyntaxMap -> Presentation
- Patat.Presentation.Internal: PresentationSettings :: !Maybe (FlexibleNum Int) -> !Maybe (FlexibleNum Int) -> !Maybe Margins -> !Maybe Bool -> !Maybe Theme -> !Maybe Bool -> !Maybe (FlexibleNum Int) -> !Maybe Int -> !Maybe ExtensionList -> !Maybe ImageSettings -> !Maybe Bool -> !Maybe EvalSettingsMap -> !Maybe Bool -> !Maybe [FilePath] -> !Maybe Settings -> PresentationSettings
+ Patat.Presentation.Internal: PresentationSettings :: !Maybe (FlexibleNum Int) -> !Maybe (FlexibleNum Int) -> !Maybe MarginSettings -> !Maybe Bool -> !Maybe Theme -> !Maybe Bool -> !Maybe (FlexibleNum Int) -> !Maybe Int -> !Maybe ExtensionList -> !Maybe ImageSettings -> !Maybe Bool -> !Maybe EvalSettingsMap -> !Maybe Bool -> !Maybe [FilePath] -> !Maybe SpeakerNotesSettings -> !Maybe TransitionSettings -> PresentationSettings
- Patat.Presentation.Internal: Slide :: !SpeakerNotes -> !SlideContent -> Slide
+ Patat.Presentation.Internal: Slide :: !Comment -> !SlideContent -> Slide
- Patat.Presentation.Internal: [mLeft] :: Margins -> !Maybe (FlexibleNum Int)
+ Patat.Presentation.Internal: [mLeft] :: Margins -> Int
- Patat.Presentation.Internal: [mRight] :: Margins -> !Maybe (FlexibleNum Int)
+ Patat.Presentation.Internal: [mRight] :: Margins -> Int
- Patat.Presentation.Internal: [psMargins] :: PresentationSettings -> !Maybe Margins
+ Patat.Presentation.Internal: [psMargins] :: PresentationSettings -> !Maybe MarginSettings
- Patat.Presentation.Internal: [psSpeakerNotes] :: PresentationSettings -> !Maybe Settings
+ Patat.Presentation.Internal: [psSpeakerNotes] :: PresentationSettings -> !Maybe SpeakerNotesSettings

Files

CHANGELOG.md view
@@ -1,5 +1,47 @@ # Changelog +## 0.10.0.0 (2023-10-12)++ *  Add transition effects (#149)++    This adds a framework for setting transition effects in between slides. Only+    a single transition type is implemented at this point, `slideLeft`.++    Example configuration:++        patat:+          transition:+            type: slideLeft+            frames: 24  # Optional+            duration: 1  # Seconds, optional+++ *  Allow overriding certain settings in slides (#148)++    Configuration was typically done in the metadata block of the input file,+    or in a per-user configuration.  These settings are applied to the entire+    presentation.++    We now allow selectively overriding these settings on a per-slide basis,+    by adding one or more config blocks to those slides.  Config blocks are+    comments that start with `config:`.  They can be placed anywhere in the+    slide.++        # This is a normal slide++        Normal slide content++        # This slide has a different colour header++        <!--config:+        theme:+          header: [vividYellow]+        -->++        Wow, how did that happen?++ *  Allow configuring top margin (#147)+ ## 0.9.2.0 (2023-09-26)   *  Read configuration from XDG standard directory (#146)
README.md view
@@ -15,6 +15,7 @@ - Display [speaker notes](#speaker-notes) in a second window or monitor. - [Incremental slide display](#fragmented-slides). - Experimental [images](#images) support.+- [Transition effects](#transitions). - Supports [smart slide splitting](#input-format). - [Auto advancing](#auto-advancing) with configurable delay. - Optionally [re-wrapping](#line-wrapping) text to terminal width with proper@@ -42,6 +43,7 @@ -   [Input format](#input-format) -   [Configuration](#configuration)     -   [Line wrapping](#line-wrapping)+    -   [Margins](#margins)     -   [Auto advancing](#auto-advancing)     -   [Advanced slide splitting](#advanced-slide-splitting)     -   [Fragmented slides](#fragmented-slides)@@ -50,8 +52,10 @@     -   [Pandoc Extensions](#pandoc-extensions)     -   [Images](#images)     -   [Breadcrumbs](#breadcrumbs)+    -   [Slide numbers](#slide-numbers)     -   [Evaluating code](#evaluating-code)     -   [Speaker notes](#speaker-notes)+    -   [Transitions](#transitions) -   [Trivia](#trivia)  Installation@@ -249,32 +253,61 @@  1.  For per-user configuration you can use     `$XDG_CONFIG_DIRECTORY/patat/config.yaml`-    (typically `$HOME/.config/patat/config.yaml`) or `$HOME/.patat.yaml`.+    (typically `$HOME/.config/patat/config.yaml`) or `$HOME/.patat.yaml`, for+    example:++    ```yaml+    slideNumber: false+    ```+ 2.  In the presentation file itself, using the [Pandoc metadata header].     These settings take precedence over anything specified in the per-user-    configuration file.+    configuration file.  They must be placed in a `patat:` section, so they+    don't conflict with metadata: -[YAML]: http://yaml.org/-[Pandoc metadata header]: http://pandoc.org/MANUAL.html#extension-yaml_metadata_block+    ```markdown+    ---+    title: Presentation with options+    author: John Doe+    patat:+        slideNumber: false+    ... -For example, we set an option `key` to `val` by using the following file:+    Hello world.+    ``` -```markdown-----title: Presentation with options-author: John Doe-patat:-    key: val-...+3.  Within a slide, using a comment starting with `<!--config:`.  These+    settings can override configuration for that specific slide only.+    There should not be any whitespace between `<!--` and `config:`. -Hello world.-```+    ```markdown+    # First slide -Or we can use a "plain" presentation and have the following-`$XDG_CONFIG_DIRECTORY/patat/config.yaml`:+    Slide numbers are turned on here. -    key: val+    # Second slide +    <!--config:+    slideNumber: false+    -->++    Slide numbers are turned off here.+    ```++    The following settings can **not** be set in a slide configuration block,+    and doing so will result in an error:++     -  `autoAdvanceDelay`+     -  `eval`+     -  `images`+     -  `incrementalLists`+     -  `pandocExtensions`+     -  `slideLevel`+     -  `speakerNotes`++[YAML]: http://yaml.org/+[Pandoc metadata header]: http://pandoc.org/MANUAL.html#extension-yaml_metadata_block+ ### Line wrapping  Line wrapping can be enabled by setting `wrap: true` in the configuration.  This@@ -293,16 +326,23 @@     margins:         left: 10         right: 10+        top: 5 ...  Lorem ipsum dolor sit amet, ... ``` -This example configuration will generate slides with a margin of 10 characters on the left,-and break lines 10 characters before they reach the end of the terminal's width.+This example configuration will generate slides with a margin of 10 columns on+the left, and it will wrap long lines 10 columns before the right side of the+terminal.  Additionally, there will be 5 empty lines in between the title bar+and slide content. -It is recommended to enable [line wrapping](#line-wrapping) along with this feature.+[Line wrapping](#line-wrapping) should be enabled when using non-zero `right`+margin. +By default, the `left` and `right` margin are set to 0, and the `top` margin is+set to 1.+ ### Auto advancing  By setting `autoAdvanceDelay` to a number of seconds, `patat` will automatically@@ -724,6 +764,51 @@ patat -w /tmp/notes.txt ``` +Note that speaker notes should not start with `<!--config:`, since then they+will be parsed as [configuration](#configuration) blocks.  They are allowed+to start with `<!-- config:`; the lack of whitespace matters.++### Transitions++`patat` supports transitions in between slides.  A relatively fast terminal+emulator (e.g. [Alacritty], [Kitty], [iTerm2])+is suggested when enabling this, to avoid too much flickering -- some+flickering is unavoidable since we redraw the entire screen on each frame.++```yaml+patat:+  transition:+    type: slideLeft+```++To set transitions on specific slides, use `<!--config:` blocks, as detailed+in the [configuration section](#configuration).  For example:++```markdown+# Slide one++Slide one content.++# Slide two++<!--config:+transition:+  type: slideLeft+  duration: 2+-->++Slide two content.+```++Supported transitions `type`s:++ -  `slideLeft`: slides the new slide in from right to left.++    Arguments:++     -  `frameRate`: number of frames per second.  Defaults to 24.+     -  `duration`: duration of the animation in seconds.  Defaults to 1.+ Trivia ------ @@ -740,3 +825,7 @@ [MDP]: https://github.com/visit1985/mdp [VTMC]: https://github.com/jclulow/vtmc [Literate Haskell]: https://wiki.haskell.org/Literate_programming++[Alacritty]: https://alacritty.org/+[iTerm2]: https://iterm2.com/+[Kitty]: https://sw.kovidgoyal.net/kitty/
+ lib/Control/Concurrent/Chan/Extended.hs view
@@ -0,0 +1,15 @@+module Control.Concurrent.Chan.Extended+    ( module Control.Concurrent.Chan+    , withMapChan+    ) where++import qualified Control.Concurrent.Async as Async+import           Control.Concurrent.Chan+import           Control.Monad            (forever)++withMapChan :: (a -> b) -> Chan a -> (Chan b -> IO r) -> IO r+withMapChan f chan cont = do+    new <- newChan+    Async.withAsync+        (forever $ readChan chan >>= writeChan new . f)+        (\_ -> cont new)
lib/Patat/AutoAdvance.hs view
@@ -1,11 +1,13 @@ -------------------------------------------------------------------------------- module Patat.AutoAdvance-    ( autoAdvance+    ( maybeAutoAdvance+    , autoAdvance     ) where   ---------------------------------------------------------------------------------import           Control.Concurrent      (forkIO, threadDelay)+import           Control.Concurrent      (threadDelay)+import qualified Control.Concurrent.Async as Async import qualified Control.Concurrent.Chan as Chan import           Control.Monad           (forever) import qualified Data.IORef              as IORef@@ -14,14 +16,26 @@   --------------------------------------------------------------------------------+-- | Utility to make auto advancing optional.+maybeAutoAdvance+    :: Maybe Int+    -> Chan.Chan PresentationCommand+    -> (Chan.Chan PresentationCommand -> IO a)+    -> IO a+maybeAutoAdvance Nothing             chan f = f chan+maybeAutoAdvance (Just delaySeconds) chan f = autoAdvance delaySeconds chan f+++-------------------------------------------------------------------------------- -- | This function takes an existing channel for presentation commands -- (presumably coming from human input) and creates a new one that /also/ sends -- a 'Forward' command if nothing happens for N seconds. autoAdvance     :: Int     -> Chan.Chan PresentationCommand-    -> IO (Chan.Chan PresentationCommand)-autoAdvance delaySeconds existingChan = do+    -> (Chan.Chan PresentationCommand -> IO a)+    -> IO a+autoAdvance delaySeconds existingChan f = do     let delay = delaySeconds * 1000  -- We are working with ms in this function      newChan         <- Chan.newChan@@ -29,24 +43,25 @@      -- This is a thread that copies 'existingChan' to 'newChan', and writes     -- whenever the latest command was to 'latestCommandAt'.-    _ <- forkIO $ forever $ do+    (forever $ do         cmd <- Chan.readChan existingChan         getCurrentTime >>= IORef.writeIORef latestCommandAt-        Chan.writeChan newChan cmd+        Chan.writeChan newChan cmd) `Async.withAsync` \_ -> -    -- This is a thread that waits around 'delay' seconds and then checks if-    -- there's been a more recent command.  If not, we write a 'Forward'.-    _ <- forkIO $ forever $ do-        current <- getCurrentTime-        latest  <- IORef.readIORef latestCommandAt-        let elapsed = floor $ 1000 * (current `diffUTCTime` latest) :: Int-        if elapsed >= delay-            then do-                Chan.writeChan newChan Forward-                IORef.writeIORef latestCommandAt current-                threadDelay (delay * 1000)-            else do-                let wait = delay - elapsed-                threadDelay (wait * 1000)+        -- This is a thread that waits around 'delay' seconds and then checks if+        -- there's been a more recent command.  If not, we write a 'Forward'.+        (forever $ do+            current <- getCurrentTime+            latest  <- IORef.readIORef latestCommandAt+            let elapsed = floor $ 1000 * (current `diffUTCTime` latest) :: Int+            if elapsed >= delay+                then do+                    Chan.writeChan newChan Forward+                    IORef.writeIORef latestCommandAt current+                    threadDelay (delay * 1000)+                else do+                    let wait = delay - elapsed+                    threadDelay (wait * 1000)) `Async.withAsync` \_ -> -    return newChan+        -- Continue main thread.+        f newChan
lib/Patat/Images.hs view
@@ -3,7 +3,7 @@ module Patat.Images     ( Backend     , Handle-    , new+    , withHandle     , drawImage     ) where @@ -21,16 +21,16 @@   ---------------------------------------------------------------------------------new :: ImageSettings -> IO Handle-new is-    | isBackend is == "auto" = auto+withHandle :: ImageSettings -> (Handle -> IO a) -> IO a+withHandle is f+    | isBackend is == "auto" = auto >>= f     | Just (Backend b) <- lookup (isBackend is) backends =         case A.fromJSON (A.Object $ isParams is) of-            A.Success c -> b (Explicit c)+            A.Success c -> b (Explicit c) >>= f             A.Error err -> fail $                 "Patat.Images.new: Error parsing config for " ++                 show (isBackend is) ++ " image backend: " ++ err-new is = fail $+withHandle is _ = fail $     "Patat.Images.new: Could not find " ++ show (isBackend is) ++     " image backend." 
lib/Patat/Main.hs view
@@ -8,33 +8,37 @@   ---------------------------------------------------------------------------------import           Control.Concurrent              (forkIO, killThread,-                                                  threadDelay)-import           Control.Concurrent.Chan         (Chan)-import qualified Control.Concurrent.Chan         as Chan-import           Control.Exception               (bracket)-import           Control.Monad                   (forever, unless, when)-import qualified Data.Aeson.Extended             as A-import           Data.Foldable                   (for_)-import           Data.Functor                    (($>))-import           Data.Time                       (UTCTime)-import           Data.Version                    (showVersion)-import qualified Options.Applicative             as OA-import qualified Options.Applicative.Help.Pretty as OA.PP+import           Control.Concurrent               (forkIO, threadDelay)+import qualified Control.Concurrent.Async         as Async+import           Control.Concurrent.Chan.Extended (Chan)+import qualified Control.Concurrent.Chan.Extended as Chan+import           Control.Exception                (bracket)+import           Control.Monad                    (forever, join, unless, void,+                                                   when)+import qualified Data.Aeson.Extended              as A+import           Data.Foldable                    (for_)+import           Data.Functor                     (($>))+import qualified Data.List.NonEmpty               as NonEmpty+import qualified Data.Sequence.Extended           as Seq+import           Data.Version                     (showVersion)+import qualified Options.Applicative              as OA+import qualified Options.Applicative.Help.Pretty  as OA.PP import           Patat.AutoAdvance-import qualified Patat.EncodingFallback          as EncodingFallback-import qualified Patat.Images                    as Images+import qualified Patat.EncodingFallback           as EncodingFallback+import qualified Patat.Images                     as Images import           Patat.Presentation-import qualified Patat.Presentation.SpeakerNotes as SpeakerNotes-import qualified Patat.PrettyPrint               as PP+import qualified Patat.Presentation.Comments      as Comments+import qualified Patat.PrettyPrint                as PP+import           Patat.PrettyPrint.Matrix         (hPutMatrix)+import           Patat.Transition import qualified Paths_patat import           Prelude-import qualified System.Console.ANSI             as Ansi-import           System.Directory                (doesFileExist,-                                                  getModificationTime)-import           System.Exit                     (exitFailure, exitSuccess)-import qualified System.IO                       as IO-import qualified Text.Pandoc                     as Pandoc+import qualified System.Console.ANSI              as Ansi+import           System.Directory                 (doesFileExist,+                                                   getModificationTime)+import           System.Exit                      (exitFailure, exitSuccess)+import qualified System.IO                        as IO+import qualified Text.Pandoc                      as Pandoc   --------------------------------------------------------------------------------@@ -120,6 +124,28 @@   --------------------------------------------------------------------------------+data App = App+    { aOptions      :: Options+    , aImages       :: Maybe Images.Handle+    , aSpeakerNotes :: Maybe Comments.SpeakerNotesHandle+    , aCommandChan  :: Chan AppCommand+    , aPresentation :: Presentation+    , aView         :: AppView+    }+++--------------------------------------------------------------------------------+data AppView+    = PresentationView+    | ErrorView String+    | TransitionView TransitionInstance+++--------------------------------------------------------------------------------+data AppCommand = PresentationCommand PresentationCommand | TransitionTick TransitionId+++-------------------------------------------------------------------------------- main :: IO () main = do     options <- OA.customExecParser parserPrefs parserInfo@@ -137,78 +163,121 @@      errOrPres <- readPresentation filePath     pres      <- either (errorAndExit . return) return errOrPres+    let settings = pSettings pres      unless (oForce options) assertAnsiFeatures -    -- (Maybe) initialize images backend.-    images <- traverse Images.new $ psImages $ pSettings pres+    if oDump options then+        EncodingFallback.withHandle IO.stdout (pEncodingFallback pres) $+        dumpPresentation pres+    else+        -- (Maybe) initialize images backend.+        withMaybeHandle Images.withHandle (psImages settings) $ \images -> -    -- (Maybe) initialize speaker notes.-    withMaybeHandle SpeakerNotes.with-        (psSpeakerNotes $ pSettings pres) $ \speakerNotes ->+        -- (Maybe) initialize speaker notes.+        withMaybeHandle Comments.withSpeakerNotesHandle+            (psSpeakerNotes settings) $ \speakerNotes -> -        if oDump options then-            EncodingFallback.withHandle IO.stdout (pEncodingFallback pres) $-            dumpPresentation pres-        else-            interactiveLoop options images speakerNotes pres-  where-    interactiveLoop-        :: Options -> Maybe Images.Handle -> Maybe SpeakerNotes.Handle-        -> Presentation -> IO ()-    interactiveLoop options images speakerNotes pres0 =-        interactively readPresentationCommand $ \commandChan0 -> do+        -- Read presentation commands+        interactively (readPresentationCommand) $ \commandChan0 ->          -- If an auto delay is set, use 'autoAdvance' to create a new one.-        commandChan <- case psAutoAdvanceDelay (pSettings pres0) of-            Nothing                    -> return commandChan0-            Just (A.FlexibleNum delay) -> autoAdvance delay commandChan0+        maybeAutoAdvance+            (A.unFlexibleNum <$> psAutoAdvanceDelay settings)+            commandChan0 $ \commandChan1 -> +        -- Change to AppCommand+        Chan.withMapChan PresentationCommand commandChan1 $ \commandChan ->+         -- Spawn a thread that adds 'Reload' commands based on the file time.-        mtime0 <- getModificationTime (pFilePath pres0)-        when (oWatch options) $ do-            _ <- forkIO $ watcher commandChan (pFilePath pres0) mtime0-            return ()+        withWatcher (oWatch options) commandChan (pFilePath pres)+            (PresentationCommand Reload) $ -        let loop :: Presentation -> Maybe String -> IO ()-            loop pres mbError = do-                for_ speakerNotes $ \sn -> SpeakerNotes.write sn-                    (pEncodingFallback pres)-                    (activeSpeakerNotes pres)+        loop App+            { aOptions      = options+            , aImages       = images+            , aSpeakerNotes = speakerNotes+            , aCommandChan  = commandChan+            , aPresentation = pres+            , aView         = PresentationView+            } -                size <- getDisplaySize pres-                let display = case mbError of-                        Nothing  -> displayPresentation size pres-                        Just err -> DisplayDoc $-                            displayPresentationError size pres err -                Ansi.clearScreen-                Ansi.setCursorPosition 0 0-                cleanup <- case display of-                    DisplayDoc doc -> EncodingFallback.withHandle-                        IO.stdout (pEncodingFallback pres) $-                        PP.putDoc doc $> mempty-                    DisplayImage path -> case images of-                        Nothing -> do-                            PP.putDoc $ displayPresentationError-                                 size pres "image backend not initialized"-                            pure mempty-                        Just img -> do-                            putStrLn ""-                            IO.hFlush IO.stdout-                            Images.drawImage img path+--------------------------------------------------------------------------------+loop :: App -> IO ()+loop app@App {..} = do+    for_ aSpeakerNotes $ \sn -> Comments.writeSpeakerNotes sn+        (pEncodingFallback aPresentation)+        (activeSpeakerNotes aPresentation) -                c      <- Chan.readChan commandChan-                update <- updatePresentation c pres-                cleanup-                case update of-                    ExitedPresentation        -> return ()-                    UpdatedPresentation pres' -> loop pres' Nothing-                    ErroredPresentation err   -> loop pres (Just err)+    size <- getPresentationSize aPresentation+    Ansi.clearScreen+    Ansi.setCursorPosition 0 0+    cleanup <- case aView of+        PresentationView -> case displayPresentation size aPresentation of+            DisplayDoc doc    -> drawDoc doc+            DisplayImage path -> drawImg size path+        ErrorView err -> drawDoc $+                displayPresentationError size aPresentation err+        TransitionView tr -> do+            drawMatrix (tiSize tr) . fst . NonEmpty.head $ tiFrames tr+            pure mempty -        loop pres0 Nothing+    appCmd <- Chan.readChan aCommandChan+    cleanup+    case appCmd of+        TransitionTick eid -> case aView of+            PresentationView -> loop app+            ErrorView _      -> loop app+            TransitionView tr0  -> case stepTransition eid tr0 of+                Just tr1 -> do+                    scheduleTransitionTick tr1+                    loop app {aView = TransitionView tr1}+                Nothing -> loop app {aView = PresentationView}+        PresentationCommand c -> do+            update <- updatePresentation c aPresentation+            case update of+                ExitedPresentation       -> return ()+                UpdatedPresentation pres+                    | Just tgen <- mbTransition c size aPresentation pres -> do+                        tr <- tgen+                        scheduleTransitionTick tr+                        loop app+                            {aPresentation = pres, aView = TransitionView tr}+                    | otherwise -> loop app+                        {aPresentation = pres, aView = PresentationView}+                ErroredPresentation err  ->+                    loop app {aView = ErrorView err}+  where+    drawDoc doc = EncodingFallback.withHandle+        IO.stdout (pEncodingFallback aPresentation) $+        PP.putDoc doc $> mempty+    drawImg size path =case aImages of+        Nothing -> drawDoc $ displayPresentationError+            size aPresentation "image backend not initialized"+        Just img -> do+            putStrLn ""+            IO.hFlush IO.stdout+            Images.drawImage img path+    drawMatrix size raster = hPutMatrix IO.stdout size raster +    mbTransition c size old new+        | c == Forward+        , oldSlide + 1 == newSlide+        , DisplayDoc oldDoc <- displayPresentation size old+        , DisplayDoc newDoc <- displayPresentation size new+        , Just (Just tgen) <- pTransitionGens new `Seq.safeIndex` newSlide =+            Just $ newTransition tgen size oldDoc newDoc+        | otherwise = Nothing+      where+        (oldSlide, _) = pActiveFragment old+        (newSlide, _) = pActiveFragment new +    scheduleTransitionTick tr = void $ forkIO $ do+        threadDelayDuration . snd . NonEmpty.head $ tiFrames tr+        Chan.writeChan aCommandChan $ TransitionTick $ tiId tr++ -------------------------------------------------------------------------------- -- | Utility for dealing with pecularities of stdin & interactive applications -- on the terminal.  Tries to restore the original state of the terminal as much@@ -221,7 +290,10 @@     -- ^ Application to run.     -> IO ()     -- ^ Returns when application finishes.-interactively reader app = bracket setup teardown $ \(_, _, _, chan) -> app chan+interactively reader app = bracket setup teardown $ \(_, _, chan) ->+    Async.withAsync+        (forever $ reader IO.stdin >>= Chan.writeChan chan)+        (\_ -> app chan)   where     setup = do         chan <- Chan.newChan@@ -230,30 +302,33 @@         IO.hSetEcho      IO.stdin False         IO.hSetBuffering IO.stdin IO.NoBuffering         Ansi.hideCursor-        readerThreadId <- forkIO $ forever $-            reader IO.stdin >>= Chan.writeChan chan-        return (echo, buff, readerThreadId, chan)+        return (echo, buff, chan) -    teardown (echo, buff, readerThreadId, _chan) = do+    teardown (echo, buff, _chan) = do         Ansi.showCursor         Ansi.clearScreen         Ansi.setCursorPosition 0 0-        killThread readerThreadId         IO.hSetEcho      IO.stdin echo         IO.hSetBuffering IO.stdin buff   ---------------------------------------------------------------------------------watcher :: Chan.Chan PresentationCommand -> FilePath -> UTCTime -> IO a-watcher chan filePath mtime0 = do-    -- The extra exists check helps because some editors temporarily make the-    -- file disappear while writing.-    exists <- doesFileExist filePath-    mtime1 <- if exists then getModificationTime filePath else return mtime0+withWatcher+    :: Bool -> Chan.Chan cmd -> FilePath -> cmd -> IO a -> IO a+withWatcher False _    _        _   mx = mx+withWatcher True  chan filePath cmd mx = do+    mtime0 <- getModificationTime filePath+    Async.withAsync (watcher mtime0) (\_ -> mx)+  where+    watcher mtime0 = do+        -- The extra exists check helps because some editors temporarily make+        -- the file disappear while writing.+        exists <- doesFileExist filePath+        mtime1 <- if exists then getModificationTime filePath else return mtime0 -    when (mtime1 > mtime0) $ Chan.writeChan chan Reload-    threadDelay (200 * 1000)-    watcher chan filePath mtime1+        when (mtime1 > mtime0) $ Chan.writeChan chan cmd+        threadDelay (200 * 1000)+        watcher mtime1   --------------------------------------------------------------------------------
lib/Patat/Presentation.hs view
@@ -8,7 +8,7 @@     , activeSpeakerNotes      , Size-    , getDisplaySize+    , getPresentationSize      , Display (..)     , displayPresentation
+ lib/Patat/Presentation/Comments.hs view
@@ -0,0 +1,174 @@+--------------------------------------------------------------------------------+{-# LANGUAGE GADTs                      #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings          #-}+{-# LANGUAGE TemplateHaskell            #-}+module Patat.Presentation.Comments+    ( Comment (..)+    , parse+    , remove+    , split+    , partition++    , SpeakerNotes+    , speakerNotesToText++    , SpeakerNotesHandle+    , withSpeakerNotesHandle+    , writeSpeakerNotes++    , parseSlideSettings+    ) where+++--------------------------------------------------------------------------------+import           Control.Applicative         ((<|>))+import           Control.Exception           (bracket)+import           Control.Monad               (unless, when)+import           Data.Function               (on)+import qualified Data.IORef                  as IORef+import           Data.List                   (intercalate, intersperse)+import qualified Data.Text                   as T+import qualified Data.Text.Encoding          as T+import qualified Data.Text.IO                as T+import qualified Data.Yaml                   as Yaml+import           Patat.EncodingFallback      (EncodingFallback)+import qualified Patat.EncodingFallback      as EncodingFallback+import           Patat.Presentation.Settings+import           System.Directory            (removeFile)+import qualified System.IO                   as IO+import qualified Text.Pandoc                 as Pandoc+++--------------------------------------------------------------------------------+data Comment = Comment+    { cSpeakerNotes :: SpeakerNotes+    , cConfig       :: Either String PresentationSettings+    } deriving (Show)+++--------------------------------------------------------------------------------+instance Semigroup Comment where+    l <> r = Comment+        { cSpeakerNotes = on (<>) cSpeakerNotes l r+        , cConfig       = case (cConfig l, cConfig r) of+            (Left err, _       ) -> Left err+            (Right _,  Left err) -> Left err+            (Right x,  Right y ) -> Right (x <> y)+        }+++--------------------------------------------------------------------------------+instance Monoid Comment where+    mappend = (<>)+    mempty  = Comment mempty (Right mempty)+++--------------------------------------------------------------------------------+parse :: Pandoc.Block -> Maybe Comment+parse (Pandoc.RawBlock "html" t0) =+    (do+        t1 <- T.stripPrefix "<!--config:" t0+        t2 <- T.stripSuffix "-->" t1+        pure . Comment mempty $ case Yaml.decodeEither' (T.encodeUtf8 t2) of+            Left err  -> Left (show err)+            Right obj -> Right obj) <|>+    (do+        t1 <- T.stripPrefix "<!--" t0+        t2 <- T.stripSuffix "-->" t1+        pure $ Comment (SpeakerNotes [T.strip t2]) (Right mempty))+parse _ = Nothing+++--------------------------------------------------------------------------------+remove :: [Pandoc.Block] -> [Pandoc.Block]+remove = snd . partition+++--------------------------------------------------------------------------------+-- | Take all comments from the front of the list.  Return those and the+-- remaining blocks.+split :: [Pandoc.Block] -> (Comment, [Pandoc.Block])+split = go []+  where+    go sn []                           = (mconcat (reverse sn), [])+    go sn (x : xs) | Just s <- parse x = go (s : sn) xs+    go sn xs                           = (mconcat (reverse sn), xs)+++--------------------------------------------------------------------------------+-- | Partition the list into speaker notes and other blocks.+partition :: [Pandoc.Block] -> (Comment, [Pandoc.Block])+partition = go [] []+  where+    go sn bs []                           = (mconcat (reverse sn), reverse bs)+    go sn bs (x : xs) | Just s <- parse x = go (s : sn) bs xs+    go sn bs (x : xs)                     = go sn (x : bs) xs+++--------------------------------------------------------------------------------+newtype SpeakerNotes = SpeakerNotes [T.Text]+    deriving (Eq, Monoid, Semigroup, Show)+++--------------------------------------------------------------------------------+speakerNotesToText :: SpeakerNotes -> T.Text+speakerNotesToText (SpeakerNotes sn) = T.unlines $ intersperse mempty sn+++--------------------------------------------------------------------------------+data SpeakerNotesHandle = SpeakerNotesHandle+    { snhSettings :: !SpeakerNotesSettings+    , snhActive   :: !(IORef.IORef SpeakerNotes)+    }+++--------------------------------------------------------------------------------+withSpeakerNotesHandle+    :: SpeakerNotesSettings -> (SpeakerNotesHandle -> IO a) -> IO a+withSpeakerNotesHandle settings = bracket+    (SpeakerNotesHandle settings <$> IORef.newIORef mempty)+    (\_ -> removeFile (snsFile settings))+++--------------------------------------------------------------------------------+writeSpeakerNotes+    :: SpeakerNotesHandle -> EncodingFallback -> SpeakerNotes -> IO ()+writeSpeakerNotes h encodingFallback sn = do+    change <- IORef.atomicModifyIORef' (snhActive h) $ \old -> (sn, old /= sn)+    when change $ IO.withFile (snsFile $ snhSettings h) IO.WriteMode $ \ioh ->+        EncodingFallback.withHandle ioh encodingFallback $+        T.hPutStr ioh $ speakerNotesToText sn+++--------------------------------------------------------------------------------+data Setting where+    Setting :: String -> (PresentationSettings -> Maybe a) -> Setting+++--------------------------------------------------------------------------------+unsupportedSlideSettings :: [Setting]+unsupportedSlideSettings =+    [ Setting "incrementalLists" psIncrementalLists+    , Setting "autoAdvanceDelay" psAutoAdvanceDelay+    , Setting "slideLevel"       psSlideLevel+    , Setting "pandocExtensions" psPandocExtensions+    , Setting "images"           psImages+    , Setting "eval"             psEval+    , Setting "speakerNotes"     psSpeakerNotes+    ]+++--------------------------------------------------------------------------------+parseSlideSettings :: Comment -> Either String PresentationSettings+parseSlideSettings c = do+    settings <- cConfig c+    let unsupported = do+            setting <- unsupportedSlideSettings+            case setting of+                Setting name f | Just _ <- f settings -> [name]+                Setting _    _                        -> []+    unless (null unsupported) $ Left $+        "the following settings are not supported in slide config blocks: " +++        intercalate ", " unsupported+    pure settings
lib/Patat/Presentation/Display.hs view
@@ -2,10 +2,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards   #-} module Patat.Presentation.Display-    ( Size-    , getDisplaySize--    , Display (..)+    ( Display (..)     , displayPresentation     , displayPresentationError     , dumpPresentation@@ -13,7 +10,7 @@   ---------------------------------------------------------------------------------import           Control.Monad                        (guard, mplus)+import           Control.Monad                        (guard) import qualified Data.Aeson.Extended                  as A import           Data.Char.WCWidth.Extended           (wcstrwidth) import           Data.Data.Extended                   (grecQ)@@ -21,39 +18,22 @@ import           Data.Maybe                           (fromMaybe, maybeToList) import qualified Data.Sequence.Extended               as Seq import qualified Data.Text                            as T+import qualified Patat.Presentation.Comments          as Comments import           Patat.Presentation.Display.CodeBlock import           Patat.Presentation.Display.Internal import           Patat.Presentation.Display.Table import           Patat.Presentation.Internal-import qualified Patat.Presentation.SpeakerNotes      as SpeakerNotes import           Patat.PrettyPrint                    ((<$$>), (<+>)) import qualified Patat.PrettyPrint                    as PP+import           Patat.Size import           Patat.Theme                          (Theme (..)) import qualified Patat.Theme                          as Theme import           Prelude-import qualified System.Console.Terminal.Size         as Terminal import qualified Text.Pandoc.Extended                 as Pandoc import qualified Text.Pandoc.Writers.Shared           as Pandoc   ---------------------------------------------------------------------------------data Size = Size {sRows :: Int, sCols :: Int} deriving (Show)------------------------------------------------------------------------------------getDisplaySize :: Presentation -> IO Size-getDisplaySize Presentation {..} = do-    mbWindow <- Terminal.size-    let sRows = fromMaybe 24 $-            (A.unFlexibleNum <$> psRows pSettings) `mplus`-            (Terminal.height <$> mbWindow)-        sCols = fromMaybe 72 $-            (A.unFlexibleNum <$> psColumns pSettings) `mplus`-            (Terminal.width  <$> mbWindow)-    pure $ Size {..}----------------------------------------------------------------------------------- data Display = DisplayDoc PP.Doc | DisplayImage FilePath deriving (Show)  @@ -62,13 +42,14 @@ -- the active slide number and so on. displayWithBorders     :: Size -> Presentation -> (Size -> DisplaySettings -> PP.Doc) -> PP.Doc-displayWithBorders (Size rows columns) Presentation {..} f =+displayWithBorders (Size rows columns) pres@Presentation {..} f =     (if null title         then mempty         else             let titleRemainder = columns - titleWidth - titleOffset                 wrappedTitle = PP.spaces titleOffset <> PP.string title <> PP.spaces titleRemainder in-        borders wrappedTitle <> PP.hardline <> PP.hardline) <>+        borders wrappedTitle <> PP.hardline) <>+    mconcat (replicate topMargin PP.hardline) <>     formatWith settings (f canvasSize ds) <> PP.hardline <>     PP.goToLine (rows - 2) <>     borders (PP.space <> PP.string author <> middleSpaces <> PP.string active <> PP.space) <>@@ -76,7 +57,7 @@   where     -- Get terminal width/title     (sidx, _)   = pActiveFragment-    settings    = pSettings {psColumns = Just $ A.FlexibleNum columns}+    settings    = (activeSettings pres) {psColumns = Just $ A.FlexibleNum columns}     ds          = DisplaySettings         { dsTheme     = fromMaybe Theme.defaultTheme (psTheme settings)         , dsSyntaxMap = pSyntaxMap@@ -101,7 +82,8 @@     borders     = themed ds themeBorders      -- Room left for content-    canvasSize = Size (rows - 2) columns+    topMargin  = mTop $ margins settings+    canvasSize = Size (rows - 2 - topMargin) columns      -- Compute footer.     active@@ -127,12 +109,12 @@                 prettyFragment theme fragment         Just (ActiveTitle block) -> DisplayDoc $             displayWithBorders size pres $ \canvasSize theme ->-            let pblock          = prettyBlock theme block-                (prows, pcols)  = PP.dimensions pblock-                (mLeft, mRight) = marginsOf pSettings-                offsetRow       = (sRows canvasSize `div` 2) - (prows `div` 2)-                offsetCol       = ((sCols canvasSize - mLeft - mRight) `div` 2) - (pcols `div` 2)-                spaces          = PP.NotTrimmable $ PP.spaces offsetCol in+            let pblock         = prettyBlock theme block+                (prows, pcols) = PP.dimensions pblock+                Margins {..}   = margins (activeSettings pres)+                offsetRow      = (sRows canvasSize `div` 2) - (prows `div` 2)+                offsetCol      = ((sCols canvasSize - mLeft - mRight) `div` 2) - (pcols `div` 2)+                spaces         = PP.NotTrimmable $ PP.spaces offsetCol in             mconcat (replicate (offsetRow - 3) PP.hardline) <$$>             PP.indent spaces spaces pblock @@ -160,44 +142,50 @@ -------------------------------------------------------------------------------- dumpPresentation :: Presentation -> IO () dumpPresentation pres@Presentation {..} =-    PP.putDoc $ PP.removeControls $ formatWith pSettings $+    PP.putDoc $ PP.removeControls $     PP.vcat $ L.intercalate ["{slide}"] $         map dumpSlide [0 .. length pSlides - 1]   where     dumpSlide :: Int -> [PP.Doc]     dumpSlide i = do         slide <- maybeToList $ getSlide i pres-        dumpSpeakerNotes slide <> L.intercalate ["{fragment}"]-            [ dumpFragment (i, j)-            | j <- [0 .. numFragments slide - 1]-            ]+        map (formatWith (getSettings i pres)) $+            dumpComment slide <> L.intercalate ["{fragment}"]+                [ dumpFragment (i, j)+                | j <- [0 .. numFragments slide - 1]+                ] -    dumpSpeakerNotes :: Slide -> [PP.Doc]-    dumpSpeakerNotes slide = do-        guard $ slideSpeakerNotes slide /= mempty+    dumpComment :: Slide -> [PP.Doc]+    dumpComment slide = do+        guard (Comments.cSpeakerNotes comment /= mempty)         pure $ PP.text $ "{speakerNotes: " <>-            SpeakerNotes.toText (slideSpeakerNotes slide) <> "}"+            Comments.speakerNotesToText (Comments.cSpeakerNotes comment) <> "}"+      where+        comment = slideComment slide      dumpFragment :: Index -> [PP.Doc]     dumpFragment idx =-        case displayPresentation size pres {pActiveFragment = idx} of+        case displayPresentation (getSize idx) pres {pActiveFragment = idx} of             DisplayDoc doc        -> [doc]             DisplayImage filepath -> [PP.string $ "{image: " ++ filepath ++ "}"] -    sRows = fromMaybe 24 $ A.unFlexibleNum <$> psRows pSettings-    sCols = fromMaybe 72 $ A.unFlexibleNum <$> psColumns pSettings-    size  = Size {..}+    getSize :: Index -> Size+    getSize idx =+        let settings = activeSettings pres {pActiveFragment = idx}+            sRows    = fromMaybe 24 $ A.unFlexibleNum <$> psRows settings+            sCols    = fromMaybe 72 $ A.unFlexibleNum <$> psColumns settings in+        Size {..}   -------------------------------------------------------------------------------- formatWith :: PresentationSettings -> PP.Doc -> PP.Doc formatWith ps = wrap . indent   where-    (marginLeft, marginRight) = marginsOf ps+    Margins {..} = margins ps     wrap = case (psWrap ps, psColumns ps) of-        (Just True,  Just (A.FlexibleNum col)) -> PP.wrapAt (Just $ col - marginRight)+        (Just True,  Just (A.FlexibleNum col)) -> PP.wrapAt (Just $ col - mRight)         _                                      -> id-    spaces = PP.NotTrimmable $ PP.spaces marginLeft+    spaces = PP.NotTrimmable $ PP.spaces mLeft     indent = PP.indent spaces spaces  
lib/Patat/Presentation/Interactive.hs view
@@ -89,7 +89,6 @@     = UpdatedPresentation !Presentation     | ExitedPresentation     | ErroredPresentation String-    deriving (Show)   --------------------------------------------------------------------------------
lib/Patat/Presentation/Internal.hs view
@@ -8,8 +8,9 @@     , PresentationSettings (..)     , defaultPresentationSettings +    , MarginSettings (..)     , Margins (..)-    , marginsOf+    , margins      , ExtensionList (..)     , defaultExtensionList@@ -30,29 +31,29 @@     , ActiveFragment (..)     , activeFragment     , activeSpeakerNotes++    , getSettings+    , activeSettings++    , Size+    , getPresentationSize     ) where   ---------------------------------------------------------------------------------import           Control.Monad                   (mplus)-import qualified Data.Aeson.Extended             as A-import qualified Data.Aeson.TH.Extended          as A-import qualified Data.Foldable                   as Foldable-import           Data.Function                   (on)-import qualified Data.HashMap.Strict             as HMS-import           Data.List                       (intercalate)-import           Data.Maybe                      (fromMaybe)-import           Data.Sequence.Extended          (Seq)-import qualified Data.Sequence.Extended          as Seq-import qualified Data.Text                       as T-import           Patat.EncodingFallback          (EncodingFallback)-import qualified Patat.Presentation.Instruction  as Instruction-import qualified Patat.Presentation.SpeakerNotes as SpeakerNotes-import qualified Patat.Theme                     as Theme+import qualified Data.Aeson.Extended            as A+import           Data.Maybe                     (fromMaybe)+import           Data.Sequence.Extended         (Seq)+import qualified Data.Sequence.Extended         as Seq+import           Patat.EncodingFallback         (EncodingFallback)+import qualified Patat.Presentation.Comments    as Comments+import qualified Patat.Presentation.Instruction as Instruction+import           Patat.Presentation.Settings+import           Patat.Size+import           Patat.Transition               (TransitionGen) import           Prelude-import qualified Skylighting                     as Skylighting-import qualified Text.Pandoc                     as Pandoc-import           Text.Read                       (readMaybe)+import qualified Skylighting                    as Skylighting+import qualified Text.Pandoc                    as Pandoc   --------------------------------------------------------------------------------@@ -67,204 +68,37 @@     , pAuthor           :: ![Pandoc.Inline]     , pSettings         :: !PresentationSettings     , pSlides           :: !(Seq Slide)-    , pBreadcrumbs      :: !(Seq Breadcrumbs)  -- One for each slide.+    , pBreadcrumbs      :: !(Seq Breadcrumbs)            -- One for each slide.+    , pSlideSettings    :: !(Seq PresentationSettings)   -- One for each slide.+    , pTransitionGens   :: !(Seq (Maybe TransitionGen))  -- One for each slide.     , pActiveFragment   :: !Index     , pSyntaxMap        :: !Skylighting.SyntaxMap-    } deriving (Show)-------------------------------------------------------------------------------------- | These are patat-specific settings.  That is where they differ from more--- general metadata (author, title...)-data PresentationSettings = PresentationSettings-    { psRows              :: !(Maybe (A.FlexibleNum Int))-    , psColumns           :: !(Maybe (A.FlexibleNum Int))-    , psMargins           :: !(Maybe Margins)-    , psWrap              :: !(Maybe Bool)-    , psTheme             :: !(Maybe Theme.Theme)-    , psIncrementalLists  :: !(Maybe Bool)-    , psAutoAdvanceDelay  :: !(Maybe (A.FlexibleNum Int))-    , psSlideLevel        :: !(Maybe Int)-    , psPandocExtensions  :: !(Maybe ExtensionList)-    , psImages            :: !(Maybe ImageSettings)-    , psBreadcrumbs       :: !(Maybe Bool)-    , psEval              :: !(Maybe EvalSettingsMap)-    , psSlideNumber       :: !(Maybe Bool)-    , psSyntaxDefinitions :: !(Maybe [FilePath])-    , psSpeakerNotes      :: !(Maybe SpeakerNotes.Settings)-    } deriving (Show)------------------------------------------------------------------------------------instance Semigroup PresentationSettings where-    l <> r = PresentationSettings-        { psRows              = on mplus psRows              l r-        , psColumns           = on mplus psColumns           l r-        , psMargins           = on (<>)  psMargins           l r-        , psWrap              = on mplus psWrap              l r-        , psTheme             = on (<>)  psTheme             l r-        , psIncrementalLists  = on mplus psIncrementalLists  l r-        , psAutoAdvanceDelay  = on mplus psAutoAdvanceDelay  l r-        , psSlideLevel        = on mplus psSlideLevel        l r-        , psPandocExtensions  = on mplus psPandocExtensions  l r-        , psImages            = on mplus psImages            l r-        , psBreadcrumbs       = on mplus psBreadcrumbs       l r-        , psEval              = on (<>)  psEval              l r-        , psSlideNumber       = on mplus psSlideNumber       l r-        , psSyntaxDefinitions = on (<>)  psSyntaxDefinitions l r-        , psSpeakerNotes      = on mplus psSpeakerNotes      l r-        }------------------------------------------------------------------------------------instance Monoid PresentationSettings where-    mappend = (<>)-    mempty  = PresentationSettings-                    Nothing Nothing Nothing Nothing Nothing Nothing Nothing-                    Nothing Nothing Nothing Nothing Nothing Nothing Nothing-                    Nothing------------------------------------------------------------------------------------defaultPresentationSettings :: PresentationSettings-defaultPresentationSettings = mempty-    { psMargins          = Just defaultMargins-    , psTheme            = Just Theme.defaultTheme     }   -------------------------------------------------------------------------------- data Margins = Margins-    { mLeft  :: !(Maybe (A.FlexibleNum Int))-    , mRight :: !(Maybe (A.FlexibleNum Int))+    { mTop   :: Int+    , mLeft  :: Int+    , mRight :: Int     } deriving (Show)   ---------------------------------------------------------------------------------instance Semigroup Margins where-    l <> r = Margins-        { mLeft  = mLeft  l `mplus` mLeft  r-        , mRight = mRight l `mplus` mRight r-        }------------------------------------------------------------------------------------instance Monoid Margins where-    mappend = (<>)-    mempty  = Margins Nothing Nothing------------------------------------------------------------------------------------defaultMargins :: Margins-defaultMargins = Margins-    { mLeft  = Nothing-    , mRight = Nothing+margins :: PresentationSettings -> Margins+margins ps = Margins+    { mLeft  = get 0 msLeft+    , mRight = get 0 msRight+    , mTop   = get 1 msTop     }------------------------------------------------------------------------------------marginsOf :: PresentationSettings -> (Int, Int)-marginsOf presentationSettings =-    (marginLeft, marginRight)   where-    margins    = fromMaybe defaultMargins $ psMargins presentationSettings-    marginLeft  = fromMaybe 0 (A.unFlexibleNum <$> mLeft margins)-    marginRight = fromMaybe 0 (A.unFlexibleNum <$> mRight margins)------------------------------------------------------------------------------------newtype ExtensionList = ExtensionList {unExtensionList :: Pandoc.Extensions}-    deriving (Show)------------------------------------------------------------------------------------instance A.FromJSON ExtensionList where-    parseJSON = A.withArray "FromJSON ExtensionList" $-        fmap (ExtensionList . mconcat) . mapM parseExt . Foldable.toList-      where-        parseExt = A.withText "FromJSON ExtensionList" $ \txt -> case txt of-            -- Our default extensions-            "patat_extensions" -> return (unExtensionList defaultExtensionList)--            -- Individuals-            _ -> case readMaybe ("Ext_" ++ T.unpack txt) of-                Just e  -> return $ Pandoc.extensionsFromList [e]-                Nothing -> fail $-                    "Unknown extension: " ++ show txt ++-                    ", known extensions are: " ++-                    intercalate ", " (map (drop 4 . show) allExts)-          where-            -- This is an approximation since we can't enumerate extensions-            -- anymore in the latest pandoc...-            allExts = Pandoc.extensionsToList $-                Pandoc.getAllExtensions "markdown"------------------------------------------------------------------------------------defaultExtensionList :: ExtensionList-defaultExtensionList = ExtensionList $-    Pandoc.readerExtensions Pandoc.def `mappend` Pandoc.extensionsFromList-    [ Pandoc.Ext_yaml_metadata_block-    , Pandoc.Ext_table_captions-    , Pandoc.Ext_simple_tables-    , Pandoc.Ext_multiline_tables-    , Pandoc.Ext_grid_tables-    , Pandoc.Ext_pipe_tables-    , Pandoc.Ext_raw_html-    , Pandoc.Ext_tex_math_dollars-    , Pandoc.Ext_fenced_code_blocks-    , Pandoc.Ext_fenced_code_attributes-    , Pandoc.Ext_backtick_code_blocks-    , Pandoc.Ext_inline_code_attributes-    , Pandoc.Ext_fancy_lists-    , Pandoc.Ext_four_space_rule-    , Pandoc.Ext_definition_lists-    , Pandoc.Ext_compact_definition_lists-    , Pandoc.Ext_example_lists-    , Pandoc.Ext_strikeout-    , Pandoc.Ext_superscript-    , Pandoc.Ext_subscript-    ]------------------------------------------------------------------------------------data ImageSettings = ImageSettings-    { isBackend :: !T.Text-    , isParams  :: !A.Object-    } deriving (Show)------------------------------------------------------------------------------------instance A.FromJSON ImageSettings where-    parseJSON = A.withObject "FromJSON ImageSettings" $ \o -> do-        t <- o A..: "backend"-        return ImageSettings {isBackend = t, isParams = o}------------------------------------------------------------------------------------type EvalSettingsMap = HMS.HashMap T.Text EvalSettings------------------------------------------------------------------------------------data EvalSettings = EvalSettings-    { evalCommand  :: !T.Text-    , evalReplace  :: !Bool-    , evalFragment :: !Bool-    } deriving (Show)------------------------------------------------------------------------------------instance A.FromJSON EvalSettings where-    parseJSON = A.withObject "FromJSON EvalSettings" $ \o -> EvalSettings-        <$> o A..: "command"-        <*> o A..:? "replace" A..!= False-        <*> o A..:? "fragment" A..!= True+    get def f = fromMaybe def . fmap A.unFlexibleNum $ psMargins ps >>= f   -------------------------------------------------------------------------------- data Slide = Slide-    { slideSpeakerNotes :: !SpeakerNotes.SpeakerNotes-    , slideContent      :: !SlideContent+    { slideComment :: !Comments.Comment+    , slideContent :: !SlideContent     } deriving (Show)  @@ -312,13 +146,32 @@   ---------------------------------------------------------------------------------activeSpeakerNotes :: Presentation -> SpeakerNotes.SpeakerNotes+activeSpeakerNotes :: Presentation -> Comments.SpeakerNotes activeSpeakerNotes presentation = fromMaybe mempty $ do     let (sidx, _) = pActiveFragment presentation     slide <- getSlide sidx presentation-    pure $ slideSpeakerNotes slide+    pure . Comments.cSpeakerNotes $ slideComment slide   ---------------------------------------------------------------------------------$(A.deriveFromJSON A.dropPrefixOptions ''Margins)-$(A.deriveFromJSON A.dropPrefixOptions ''PresentationSettings)+getSettings :: Int -> Presentation -> PresentationSettings+getSettings sidx pres =+    fromMaybe mempty (Seq.safeIndex (pSlideSettings pres) sidx) <>+    pSettings pres+++--------------------------------------------------------------------------------+activeSettings :: Presentation -> PresentationSettings+activeSettings pres =+    let (sidx, _) = pActiveFragment pres in getSettings sidx pres+++--------------------------------------------------------------------------------+getPresentationSize :: Presentation -> IO Size+getPresentationSize pres = do+    term <- getTerminalSize+    let rows = fromMaybe (sRows term) $ A.unFlexibleNum <$> psRows settings+        cols = fromMaybe (sCols term) $ A.unFlexibleNum <$> psColumns settings+    pure $ Size {sRows = rows, sCols = cols}+  where+    settings = activeSettings pres
lib/Patat/Presentation/Read.hs view
@@ -13,35 +13,37 @@   ---------------------------------------------------------------------------------import           Control.Monad.Except            (ExceptT (..), runExceptT,-                                                  throwError)-import           Control.Monad.Trans             (liftIO)-import qualified Data.Aeson                      as A-import qualified Data.Aeson.KeyMap               as AKM-import           Data.Bifunctor                  (first)-import           Data.Maybe                      (fromMaybe)-import           Data.Sequence.Extended          (Seq)-import qualified Data.Sequence.Extended          as Seq-import qualified Data.Text                       as T-import qualified Data.Text.Encoding              as T-import qualified Data.Yaml                       as Yaml-import           Patat.EncodingFallback          (EncodingFallback)-import qualified Patat.EncodingFallback          as EncodingFallback-import           Patat.Eval                      (eval)+import           Control.Monad.Except           (ExceptT (..), runExceptT,+                                                 throwError)+import           Control.Monad.Trans            (liftIO)+import qualified Data.Aeson                     as A+import qualified Data.Aeson.KeyMap              as AKM+import           Data.Bifunctor                 (first)+import           Data.Maybe                     (fromMaybe)+import           Data.Sequence.Extended         (Seq)+import qualified Data.Sequence.Extended         as Seq+import qualified Data.Text                      as T+import qualified Data.Text.Encoding             as T+import           Data.Traversable               (for)+import qualified Data.Yaml                      as Yaml+import           Patat.EncodingFallback         (EncodingFallback)+import qualified Patat.EncodingFallback         as EncodingFallback+import           Patat.Eval                     (eval)+import qualified Patat.Presentation.Comments    as Comments import           Patat.Presentation.Fragment-import qualified Patat.Presentation.Instruction  as Instruction+import qualified Patat.Presentation.Instruction as Instruction import           Patat.Presentation.Internal-import qualified Patat.Presentation.SpeakerNotes as SpeakerNotes+import           Patat.Transition               (parseTransitionSettings) import           Prelude-import qualified Skylighting                     as Skylighting-import           System.Directory                (XdgDirectory (XdgConfig),-                                                  doesFileExist,-                                                  getHomeDirectory,-                                                  getXdgDirectory)-import           System.FilePath                 (splitFileName, takeExtension,-                                                  (</>))-import qualified Text.Pandoc.Error               as Pandoc-import qualified Text.Pandoc.Extended            as Pandoc+import qualified Skylighting                    as Skylighting+import           System.Directory               (XdgDirectory (XdgConfig),+                                                 doesFileExist,+                                                 getHomeDirectory,+                                                 getXdgDirectory)+import           System.FilePath                (splitFileName, takeExtension,+                                                 (</>))+import qualified Text.Pandoc.Error              as Pandoc+import qualified Text.Pandoc.Extended           as Pandoc   --------------------------------------------------------------------------------@@ -131,6 +133,15 @@         !pBreadcrumbs    = collectBreadcrumbs pSlides         !pActiveFragment = (0, 0)         !pAuthor         = concat (Pandoc.docAuthors meta)+    pSlideSettings <- Seq.traverseWithIndex+        (\i ->+            first (\err -> "on slide " ++ show (i + 1) ++ ": " ++ err) .+            Comments.parseSlideSettings . slideComment)+        pSlides+    pTransitionGens <- for pSlideSettings $ \slideSettings ->+        case psTransition (slideSettings <> pSettings) of+            Nothing -> pure Nothing+            Just ts -> Just <$> parseTransitionSettings ts     return Presentation {..}  @@ -217,7 +228,7 @@ -- header that occurs before a non-header in the blocks. detectSlideLevel :: Pandoc.Pandoc -> Int detectSlideLevel (Pandoc.Pandoc _meta blocks0) =-    go 6 $ SpeakerNotes.remove blocks0+    go 6 $ Comments.remove blocks0   where     go level (Pandoc.Header n _ _ : x : xs)         | n < level && not (isHeader x) = go n xs@@ -240,7 +251,7 @@     | otherwise                              = splitAtHeaders [] blocks0   where     mkContentSlide :: [Pandoc.Block] -> [Slide]-    mkContentSlide bs0 = case SpeakerNotes.partition bs0 of+    mkContentSlide bs0 = case Comments.partition bs0 of         (_,  [])  -> [] -- Never create empty slides         (sn, bs1) -> pure . Slide sn . ContentSlide $             Instruction.fromList [Instruction.Append bs1]@@ -256,7 +267,7 @@         | i == slideLevel =             mkContentSlide (reverse acc) ++ splitAtHeaders [b] bs0         | otherwise       =-            let (sn, bs1) = SpeakerNotes.split bs0 in+            let (sn, bs1) = Comments.split bs0 in             mkContentSlide (reverse acc) ++             [Slide sn $ TitleSlide i txt] ++             splitAtHeaders [] bs1
+ lib/Patat/Presentation/Settings.hs view
@@ -0,0 +1,236 @@+--------------------------------------------------------------------------------+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings          #-}+{-# LANGUAGE TemplateHaskell            #-}+module Patat.Presentation.Settings+    ( PresentationSettings (..)+    , defaultPresentationSettings++    , MarginSettings (..)++    , ExtensionList (..)+    , defaultExtensionList++    , ImageSettings (..)++    , EvalSettingsMap+    , EvalSettings (..)++    , SpeakerNotesSettings (..)++    , TransitionSettings (..)+    ) where+++--------------------------------------------------------------------------------+import           Control.Monad                  (mplus)+import qualified Data.Aeson.Extended            as A+import qualified Data.Aeson.TH.Extended         as A+import qualified Data.Foldable                  as Foldable+import           Data.Function                  (on)+import qualified Data.HashMap.Strict            as HMS+import           Data.List                      (intercalate)+import qualified Data.Text                      as T+import qualified Patat.Theme                    as Theme+import           Prelude+import qualified Text.Pandoc                    as Pandoc+import           Text.Read                      (readMaybe)+++--------------------------------------------------------------------------------+-- | These are patat-specific settings.  That is where they differ from more+-- general metadata (author, title...)+data PresentationSettings = PresentationSettings+    { psRows              :: !(Maybe (A.FlexibleNum Int))+    , psColumns           :: !(Maybe (A.FlexibleNum Int))+    , psMargins           :: !(Maybe MarginSettings)+    , psWrap              :: !(Maybe Bool)+    , psTheme             :: !(Maybe Theme.Theme)+    , psIncrementalLists  :: !(Maybe Bool)+    , psAutoAdvanceDelay  :: !(Maybe (A.FlexibleNum Int))+    , psSlideLevel        :: !(Maybe Int)+    , psPandocExtensions  :: !(Maybe ExtensionList)+    , psImages            :: !(Maybe ImageSettings)+    , psBreadcrumbs       :: !(Maybe Bool)+    , psEval              :: !(Maybe EvalSettingsMap)+    , psSlideNumber       :: !(Maybe Bool)+    , psSyntaxDefinitions :: !(Maybe [FilePath])+    , psSpeakerNotes      :: !(Maybe SpeakerNotesSettings)+    , psTransition        :: !(Maybe TransitionSettings)+    } deriving (Show)+++--------------------------------------------------------------------------------+instance Semigroup PresentationSettings where+    l <> r = PresentationSettings+        { psRows              = on mplus psRows              l r+        , psColumns           = on mplus psColumns           l r+        , psMargins           = on (<>)  psMargins           l r+        , psWrap              = on mplus psWrap              l r+        , psTheme             = on (<>)  psTheme             l r+        , psIncrementalLists  = on mplus psIncrementalLists  l r+        , psAutoAdvanceDelay  = on mplus psAutoAdvanceDelay  l r+        , psSlideLevel        = on mplus psSlideLevel        l r+        , psPandocExtensions  = on mplus psPandocExtensions  l r+        , psImages            = on mplus psImages            l r+        , psBreadcrumbs       = on mplus psBreadcrumbs       l r+        , psEval              = on (<>)  psEval              l r+        , psSlideNumber       = on mplus psSlideNumber       l r+        , psSyntaxDefinitions = on (<>)  psSyntaxDefinitions l r+        , psSpeakerNotes      = on mplus psSpeakerNotes      l r+        , psTransition        = on mplus psTransition        l r+        }+++--------------------------------------------------------------------------------+instance Monoid PresentationSettings where+    mappend = (<>)+    mempty  = PresentationSettings+                    Nothing Nothing Nothing Nothing Nothing Nothing Nothing+                    Nothing Nothing Nothing Nothing Nothing Nothing Nothing+                    Nothing Nothing+++--------------------------------------------------------------------------------+defaultPresentationSettings :: PresentationSettings+defaultPresentationSettings = mempty+    { psMargins = Nothing+    , psTheme   = Just Theme.defaultTheme+    }+++--------------------------------------------------------------------------------+data MarginSettings = MarginSettings+    { msTop   :: !(Maybe (A.FlexibleNum Int))+    , msLeft  :: !(Maybe (A.FlexibleNum Int))+    , msRight :: !(Maybe (A.FlexibleNum Int))+    } deriving (Show)+++--------------------------------------------------------------------------------+instance Semigroup MarginSettings where+    l <> r = MarginSettings+        { msTop   = on mplus msTop   l r+        , msLeft  = on mplus msLeft  l r+        , msRight = on mplus msRight l r+        }+++--------------------------------------------------------------------------------+instance Monoid MarginSettings where+    mappend = (<>)+    mempty  = MarginSettings Nothing Nothing Nothing+++--------------------------------------------------------------------------------+newtype ExtensionList = ExtensionList {unExtensionList :: Pandoc.Extensions}+    deriving (Show)+++--------------------------------------------------------------------------------+instance A.FromJSON ExtensionList where+    parseJSON = A.withArray "FromJSON ExtensionList" $+        fmap (ExtensionList . mconcat) . mapM parseExt . Foldable.toList+      where+        parseExt = A.withText "FromJSON ExtensionList" $ \txt -> case txt of+            -- Our default extensions+            "patat_extensions" -> return (unExtensionList defaultExtensionList)++            -- Individuals+            _ -> case readMaybe ("Ext_" ++ T.unpack txt) of+                Just e  -> return $ Pandoc.extensionsFromList [e]+                Nothing -> fail $+                    "Unknown extension: " ++ show txt +++                    ", known extensions are: " +++                    intercalate ", " (map (drop 4 . show) allExts)+          where+            -- This is an approximation since we can't enumerate extensions+            -- anymore in the latest pandoc...+            allExts = Pandoc.extensionsToList $+                Pandoc.getAllExtensions "markdown"+++--------------------------------------------------------------------------------+defaultExtensionList :: ExtensionList+defaultExtensionList = ExtensionList $+    Pandoc.readerExtensions Pandoc.def `mappend` Pandoc.extensionsFromList+    [ Pandoc.Ext_yaml_metadata_block+    , Pandoc.Ext_table_captions+    , Pandoc.Ext_simple_tables+    , Pandoc.Ext_multiline_tables+    , Pandoc.Ext_grid_tables+    , Pandoc.Ext_pipe_tables+    , Pandoc.Ext_raw_html+    , Pandoc.Ext_tex_math_dollars+    , Pandoc.Ext_fenced_code_blocks+    , Pandoc.Ext_fenced_code_attributes+    , Pandoc.Ext_backtick_code_blocks+    , Pandoc.Ext_inline_code_attributes+    , Pandoc.Ext_fancy_lists+    , Pandoc.Ext_four_space_rule+    , Pandoc.Ext_definition_lists+    , Pandoc.Ext_compact_definition_lists+    , Pandoc.Ext_example_lists+    , Pandoc.Ext_strikeout+    , Pandoc.Ext_superscript+    , Pandoc.Ext_subscript+    ]+++--------------------------------------------------------------------------------+data ImageSettings = ImageSettings+    { isBackend :: !T.Text+    , isParams  :: !A.Object+    } deriving (Show)+++--------------------------------------------------------------------------------+instance A.FromJSON ImageSettings where+    parseJSON = A.withObject "FromJSON ImageSettings" $ \o -> do+        t <- o A..: "backend"+        return ImageSettings {isBackend = t, isParams = o}+++--------------------------------------------------------------------------------+type EvalSettingsMap = HMS.HashMap T.Text EvalSettings+++--------------------------------------------------------------------------------+data EvalSettings = EvalSettings+    { evalCommand  :: !T.Text+    , evalReplace  :: !Bool+    , evalFragment :: !Bool+    } deriving (Show)+++--------------------------------------------------------------------------------+instance A.FromJSON EvalSettings where+    parseJSON = A.withObject "FromJSON EvalSettings" $ \o -> EvalSettings+        <$> o A..: "command"+        <*> o A..:? "replace" A..!= False+        <*> o A..:? "fragment" A..!= True+++--------------------------------------------------------------------------------+data SpeakerNotesSettings = SpeakerNotesSettings+    { snsFile :: !FilePath+    } deriving (Show)+++--------------------------------------------------------------------------------+data TransitionSettings = TransitionSettings+    { tsType   :: !T.Text+    , tsParams :: !A.Object+    } deriving (Show)+++--------------------------------------------------------------------------------+instance A.FromJSON TransitionSettings where+    parseJSON = A.withObject "FromJSON TransitionSettings" $ \o ->+        TransitionSettings <$> o A..: "type" <*> pure o+++--------------------------------------------------------------------------------+$(A.deriveFromJSON A.dropPrefixOptions ''MarginSettings)+$(A.deriveFromJSON A.dropPrefixOptions ''SpeakerNotesSettings)+$(A.deriveFromJSON A.dropPrefixOptions ''PresentationSettings)
− lib/Patat/Presentation/SpeakerNotes.hs
@@ -1,110 +0,0 @@----------------------------------------------------------------------------------{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE OverloadedStrings          #-}-{-# LANGUAGE TemplateHaskell            #-}-module Patat.Presentation.SpeakerNotes-    ( SpeakerNotes-    , parse-    , toText-    , remove-    , split-    , partition--    , Settings-    , Handle-    , with-    , write-    ) where------------------------------------------------------------------------------------import           Control.Exception      (bracket)-import           Control.Monad          (when)-import qualified Data.Aeson.TH.Extended as A-import qualified Data.IORef             as IORef-import           Data.List              (intersperse)-import qualified Data.Text              as T-import qualified Data.Text.IO           as T-import           Patat.EncodingFallback (EncodingFallback)-import qualified Patat.EncodingFallback as EncodingFallback-import           System.Directory       (removeFile)-import qualified System.IO              as IO-import qualified Text.Pandoc            as Pandoc------------------------------------------------------------------------------------newtype SpeakerNotes = SpeakerNotes [T.Text]-    deriving (Eq, Monoid, Semigroup, Show)------------------------------------------------------------------------------------parse :: Pandoc.Block -> Maybe SpeakerNotes-parse (Pandoc.RawBlock "html" t0) = do-    t1 <- T.stripPrefix "<!--" t0-    t2 <- T.stripSuffix "-->" t1-    pure $ SpeakerNotes [T.strip t2]-parse _ = Nothing------------------------------------------------------------------------------------toText :: SpeakerNotes -> T.Text-toText (SpeakerNotes sn) = T.unlines $ intersperse mempty sn------------------------------------------------------------------------------------remove :: [Pandoc.Block] -> [Pandoc.Block]-remove = snd . partition-------------------------------------------------------------------------------------- | Take all speaker notes from the front of the list.  Return those and the--- remaining blocks.-split :: [Pandoc.Block] -> (SpeakerNotes, [Pandoc.Block])-split = go []-  where-    go sn []                           = (mconcat (reverse sn), [])-    go sn (x : xs) | Just s <- parse x = go (s : sn) xs-    go sn xs                           = (mconcat (reverse sn), xs)-------------------------------------------------------------------------------------- | Partition the list into speaker notes and other blocks.-partition :: [Pandoc.Block] -> (SpeakerNotes, [Pandoc.Block])-partition = go [] []-  where-    go sn bs []                           = (mconcat (reverse sn), reverse bs)-    go sn bs (x : xs) | Just s <- parse x = go (s : sn) bs xs-    go sn bs (x : xs)                     = go sn (x : bs) xs------------------------------------------------------------------------------------data Settings = Settings-    { sFile :: !FilePath-    } deriving (Show)------------------------------------------------------------------------------------data Handle = Handle-    { hSettings :: !Settings-    , hActive   :: !(IORef.IORef SpeakerNotes)-    }------------------------------------------------------------------------------------with :: Settings -> (Handle -> IO a) -> IO a-with settings = bracket-    (Handle settings <$> IORef.newIORef mempty)-    (\_ -> removeFile (sFile settings))------------------------------------------------------------------------------------write :: Handle -> EncodingFallback -> SpeakerNotes -> IO ()-write h encodingFallback sn = do-    change <- IORef.atomicModifyIORef' (hActive h) $ \old -> (sn, old /= sn)-    when change $ IO.withFile (sFile $ hSettings h) IO.WriteMode $ \ioh ->-        EncodingFallback.withHandle ioh encodingFallback $-        T.hPutStr ioh $ toText sn------------------------------------------------------------------------------------$(A.deriveFromJSON A.dropPrefixOptions ''Settings)
lib/Patat/PrettyPrint.hs view
@@ -14,6 +14,7 @@     , hPutDoc     , putDoc +    , char     , string     , text     , space@@ -46,292 +47,17 @@   ---------------------------------------------------------------------------------import           Control.Monad.Reader       (asks, local)-import           Control.Monad.RWS          (RWS, runRWS)-import           Control.Monad.State        (get, gets, modify)-import           Control.Monad.Writer       (tell) import           Data.Char.WCWidth.Extended (wcstrwidth) import qualified Data.List                  as L-import           Data.String                (IsString (..)) import qualified Data.Text                  as T+import           Patat.PrettyPrint.Internal import           Prelude                    hiding (null) import qualified System.Console.ANSI        as Ansi-import qualified System.IO                  as IO   ----------------------------------------------------------------------------------- | Control actions for the terminal.-data Control-    = ClearScreenControl-    | GoToLineControl Int-    deriving (Eq)-------------------------------------------------------------------------------------- | A simple chunk of text.  All ANSI codes are "reset" after printing.-data Chunk-    = StringChunk [Ansi.SGR] String-    | NewlineChunk-    | ControlChunk Control-    deriving (Eq)------------------------------------------------------------------------------------type Chunks = [Chunk]------------------------------------------------------------------------------------hPutChunk :: IO.Handle -> Chunk -> IO ()-hPutChunk h NewlineChunk = IO.hPutStrLn h ""-hPutChunk h (StringChunk codes str) = do-    Ansi.hSetSGR h (reverse codes)-    IO.hPutStr h str-    Ansi.hSetSGR h [Ansi.Reset]-hPutChunk h (ControlChunk ctrl) = case ctrl of-    ClearScreenControl -> Ansi.hClearScreen h-    GoToLineControl l  -> Ansi.hSetCursorPosition h l 0------------------------------------------------------------------------------------chunkToString :: Chunk -> String-chunkToString NewlineChunk        = "\n"-chunkToString (StringChunk _ str) = str-chunkToString (ControlChunk _)    = ""-------------------------------------------------------------------------------------- | If two neighboring chunks have the same set of ANSI codes, we can group--- them together.-optimizeChunks :: Chunks -> Chunks-optimizeChunks (StringChunk c1 s1 : StringChunk c2 s2 : chunks)-    | c1 == c2  = optimizeChunks (StringChunk c1 (s1 <> s2) : chunks)-    | otherwise =-        StringChunk c1 s1 : optimizeChunks (StringChunk c2 s2 : chunks)-optimizeChunks (x : chunks) = x : optimizeChunks chunks-optimizeChunks [] = []------------------------------------------------------------------------------------chunkLines :: Chunks -> [Chunks]-chunkLines chunks = case break (== NewlineChunk) chunks of-    (xs, _newline : ys) -> xs : chunkLines ys-    (xs, [])            -> [xs]------------------------------------------------------------------------------------data DocE d-    = String String-    | Softspace-    | Hardspace-    | Softline-    | Hardline-    | WrapAt-        { wrapAtCol :: Maybe Int-        , wrapDoc   :: d-        }-    | Ansi-        { ansiCode :: [Ansi.SGR] -> [Ansi.SGR]  -- ^ Modifies current codes.-        , ansiDoc  :: d-        }-    | Indent-        { indentFirstLine  :: LineBuffer-        , indentOtherLines :: LineBuffer-        , indentDoc        :: d-        }-    | Control Control-    deriving (Functor)------------------------------------------------------------------------------------chunkToDocE :: Chunk -> DocE Doc-chunkToDocE NewlineChunk         = Hardline-chunkToDocE (StringChunk c1 str) = Ansi (\c0 -> c1 ++ c0) (Doc [String str])-chunkToDocE (ControlChunk ctrl)  = Control ctrl------------------------------------------------------------------------------------newtype Doc = Doc {unDoc :: [DocE Doc]}-    deriving (Monoid, Semigroup)------------------------------------------------------------------------------------instance IsString Doc where-    fromString = string------------------------------------------------------------------------------------instance Show Doc where-    show = toString------------------------------------------------------------------------------------data DocEnv = DocEnv-    { deCodes  :: [Ansi.SGR]  -- ^ Most recent ones first in the list-    , deIndent :: LineBuffer  -- ^ Don't need to store first-line indent-    , deWrap   :: Maybe Int   -- ^ Wrap at columns-    }------------------------------------------------------------------------------------type DocM = RWS DocEnv Chunks LineBuffer------------------------------------------------------------------------------------data Trimmable a-    = NotTrimmable !a-    | Trimmable    !a-    deriving (Foldable, Functor, Traversable)-------------------------------------------------------------------------------------- | Note that this is reversed so we have fast append-type LineBuffer = [Trimmable Chunk]------------------------------------------------------------------------------------bufferToChunks :: LineBuffer -> Chunks-bufferToChunks = map trimmableToChunk . reverse . dropWhile isTrimmable-  where-    isTrimmable (NotTrimmable _) = False-    isTrimmable (Trimmable    _) = True--    trimmableToChunk (NotTrimmable c) = c-    trimmableToChunk (Trimmable    c) = c------------------------------------------------------------------------------------docToChunks :: Doc -> Chunks-docToChunks doc0 =-    let env0        = DocEnv [] [] Nothing-        ((), b, cs) = runRWS (go $ unDoc doc0) env0 mempty in-    optimizeChunks (cs <> bufferToChunks b)-  where-    go :: [DocE Doc] -> DocM ()--    go [] = return ()--    go (String str : docs) = do-        chunk <- makeChunk str-        modify (NotTrimmable chunk :)-        go docs--    go (Softspace : docs) = do-        hard <- softConversion Softspace docs-        go (hard : docs)--    go (Hardspace : docs) = do-        chunk <- makeChunk " "-        modify (NotTrimmable chunk :)-        go docs--    go (Softline : docs) = do-        hard <- softConversion Softline docs-        go (hard : docs)--    go (Hardline : docs) = do-        buffer <- get-        tell $ bufferToChunks buffer <> [NewlineChunk]-        indentation <- asks deIndent-        modify $ \_ -> if L.null docs then [] else indentation-        go docs--    go (WrapAt {..} : docs) = do-        local (\env -> env {deWrap = wrapAtCol}) $ go (unDoc wrapDoc)-        go docs--    go (Ansi {..} : docs) = do-        local (\env -> env {deCodes = ansiCode (deCodes env)}) $-            go (unDoc ansiDoc)-        go docs--    go (Indent {..} : docs) = do-        local (\env -> env {deIndent = indentOtherLines ++ deIndent env}) $ do-            modify (indentFirstLine ++)-            go (unDoc indentDoc)-        go docs--    go (Control ctrl : docs) = do-        tell [ControlChunk ctrl]-        go docs---    makeChunk :: String -> DocM Chunk-    makeChunk str = do-        codes <- asks deCodes-        return $ StringChunk codes str--    -- Convert 'Softspace' or 'Softline' to 'Hardspace' or 'Hardline'-    softConversion :: DocE Doc -> [DocE Doc] -> DocM (DocE Doc)-    softConversion soft docs = do-        mbWrapCol <- asks deWrap-        case mbWrapCol of-            Nothing     -> return hard-            Just maxCol -> do-                -- Slow.-                currentLine <- gets (concatMap chunkToString . bufferToChunks)-                let currentCol = wcstrwidth currentLine-                case nextWordLength docs of-                    Nothing                            -> return hard-                    Just l-                        | currentCol + 1 + l <= maxCol -> return Hardspace-                        | otherwise                    -> return Hardline-      where-        hard = case soft of-            Softspace -> Hardspace-            Softline  -> Hardline-            _         -> soft--    nextWordLength :: [DocE Doc] -> Maybe Int-    nextWordLength []                 = Nothing-    nextWordLength (String x : xs)-        | L.null x                    = nextWordLength xs-        | otherwise                   = Just (wcstrwidth x)-    nextWordLength (Softspace : xs)   = nextWordLength xs-    nextWordLength (Hardspace : xs)   = nextWordLength xs-    nextWordLength (Softline : xs)    = nextWordLength xs-    nextWordLength (Hardline : _)     = Nothing-    nextWordLength (WrapAt {..} : xs) = nextWordLength (unDoc wrapDoc   ++ xs)-    nextWordLength (Ansi   {..} : xs) = nextWordLength (unDoc ansiDoc   ++ xs)-    nextWordLength (Indent {..} : xs) = nextWordLength (unDoc indentDoc ++ xs)-    nextWordLength (Control _ : _)    = Nothing------------------------------------------------------------------------------------toString :: Doc -> String-toString = concat . map chunkToString . docToChunks-------------------------------------------------------------------------------------- | Returns the rows and columns necessary to render this document-dimensions :: Doc -> (Int, Int)-dimensions doc =-    let ls = lines (toString doc) in-    (length ls, foldr max 0 (map wcstrwidth ls))------------------------------------------------------------------------------------null :: Doc -> Bool-null doc = case unDoc doc of [] -> True; _ -> False------------------------------------------------------------------------------------hPutDoc :: IO.Handle -> Doc -> IO ()-hPutDoc h = mapM_ (hPutChunk h) . docToChunks------------------------------------------------------------------------------------putDoc :: Doc -> IO ()-putDoc = hPutDoc IO.stdout------------------------------------------------------------------------------------mkDoc :: DocE Doc -> Doc-mkDoc e = Doc [e]------------------------------------------------------------------------------------string :: String -> Doc-string = mkDoc . String  -- TODO (jaspervdj): Newline conversion+char :: Char -> Doc+char = string . pure   --------------------------------------------------------------------------------
+ lib/Patat/PrettyPrint/Internal.hs view
@@ -0,0 +1,319 @@+--------------------------------------------------------------------------------+-- | This is a small pretty-printing library.+{-# LANGUAGE DeriveFoldable             #-}+{-# LANGUAGE DeriveFunctor              #-}+{-# LANGUAGE DeriveTraversable          #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE RecordWildCards            #-}+module Patat.PrettyPrint.Internal+    ( Control (..)+    , Chunk (..)+    , Chunks+    , chunkToString+    , chunkLines++    , DocE (..)+    , chunkToDocE++    , Doc (..)+    , docToChunks++    , Trimmable (..)++    , toString+    , dimensions+    , null++    , hPutDoc+    , putDoc+    , mkDoc+    , string+    ) where+++--------------------------------------------------------------------------------+import           Control.Monad.Reader       (asks, local)+import           Control.Monad.RWS          (RWS, runRWS)+import           Control.Monad.State        (get, gets, modify)+import           Control.Monad.Writer       (tell)+import           Data.Char.WCWidth.Extended (wcstrwidth)+import qualified Data.List                  as L+import           Data.String                (IsString (..))+import           Prelude                    hiding (null)+import qualified System.Console.ANSI        as Ansi+import qualified System.IO                  as IO+++--------------------------------------------------------------------------------+-- | Control actions for the terminal.+data Control+    = ClearScreenControl+    | GoToLineControl Int+    deriving (Eq)+++--------------------------------------------------------------------------------+-- | A simple chunk of text.  All ANSI codes are "reset" after printing.+data Chunk+    = StringChunk [Ansi.SGR] String+    | NewlineChunk+    | ControlChunk Control+    deriving (Eq)+++--------------------------------------------------------------------------------+type Chunks = [Chunk]+++--------------------------------------------------------------------------------+hPutChunk :: IO.Handle -> Chunk -> IO ()+hPutChunk h NewlineChunk = IO.hPutStrLn h ""+hPutChunk h (StringChunk codes str) = do+    Ansi.hSetSGR h (reverse codes)+    IO.hPutStr h str+    Ansi.hSetSGR h [Ansi.Reset]+hPutChunk h (ControlChunk ctrl) = case ctrl of+    ClearScreenControl -> Ansi.hClearScreen h+    GoToLineControl l  -> Ansi.hSetCursorPosition h l 0+++--------------------------------------------------------------------------------+chunkToString :: Chunk -> String+chunkToString NewlineChunk        = "\n"+chunkToString (StringChunk _ str) = str+chunkToString (ControlChunk _)    = ""+++--------------------------------------------------------------------------------+-- | If two neighboring chunks have the same set of ANSI codes, we can group+-- them together.+optimizeChunks :: Chunks -> Chunks+optimizeChunks (StringChunk c1 s1 : StringChunk c2 s2 : chunks)+    | c1 == c2  = optimizeChunks (StringChunk c1 (s1 <> s2) : chunks)+    | otherwise =+        StringChunk c1 s1 : optimizeChunks (StringChunk c2 s2 : chunks)+optimizeChunks (x : chunks) = x : optimizeChunks chunks+optimizeChunks [] = []+++--------------------------------------------------------------------------------+chunkLines :: Chunks -> [Chunks]+chunkLines chunks = case break (== NewlineChunk) chunks of+    (xs, _newline : ys) -> xs : chunkLines ys+    (xs, [])            -> [xs]+++--------------------------------------------------------------------------------+data DocE d+    = String String+    | Softspace+    | Hardspace+    | Softline+    | Hardline+    | WrapAt+        { wrapAtCol :: Maybe Int+        , wrapDoc   :: d+        }+    | Ansi+        { ansiCode :: [Ansi.SGR] -> [Ansi.SGR]  -- ^ Modifies current codes.+        , ansiDoc  :: d+        }+    | Indent+        { indentFirstLine  :: LineBuffer+        , indentOtherLines :: LineBuffer+        , indentDoc        :: d+        }+    | Control Control+    deriving (Functor)+++--------------------------------------------------------------------------------+chunkToDocE :: Chunk -> DocE Doc+chunkToDocE NewlineChunk         = Hardline+chunkToDocE (StringChunk c1 str) = Ansi (\c0 -> c1 ++ c0) (Doc [String str])+chunkToDocE (ControlChunk ctrl)  = Control ctrl+++--------------------------------------------------------------------------------+newtype Doc = Doc {unDoc :: [DocE Doc]}+    deriving (Monoid, Semigroup)+++--------------------------------------------------------------------------------+instance Show Doc where+    show = toString+++--------------------------------------------------------------------------------+instance IsString Doc where+    fromString = string+++--------------------------------------------------------------------------------+data DocEnv = DocEnv+    { deCodes  :: [Ansi.SGR]  -- ^ Most recent ones first in the list+    , deIndent :: LineBuffer  -- ^ Don't need to store first-line indent+    , deWrap   :: Maybe Int   -- ^ Wrap at columns+    }+++--------------------------------------------------------------------------------+type DocM = RWS DocEnv Chunks LineBuffer+++--------------------------------------------------------------------------------+data Trimmable a+    = NotTrimmable !a+    | Trimmable    !a+    deriving (Foldable, Functor, Traversable)+++--------------------------------------------------------------------------------+-- | Note that this is reversed so we have fast append+type LineBuffer = [Trimmable Chunk]+++--------------------------------------------------------------------------------+bufferToChunks :: LineBuffer -> Chunks+bufferToChunks = map trimmableToChunk . reverse . dropWhile isTrimmable+  where+    isTrimmable (NotTrimmable _) = False+    isTrimmable (Trimmable    _) = True++    trimmableToChunk (NotTrimmable c) = c+    trimmableToChunk (Trimmable    c) = c+++--------------------------------------------------------------------------------+docToChunks :: Doc -> Chunks+docToChunks doc0 =+    let env0        = DocEnv [] [] Nothing+        ((), b, cs) = runRWS (go $ unDoc doc0) env0 mempty in+    optimizeChunks (cs <> bufferToChunks b)+  where+    go :: [DocE Doc] -> DocM ()++    go [] = return ()++    go (String str : docs) = do+        chunk <- makeChunk str+        modify (NotTrimmable chunk :)+        go docs++    go (Softspace : docs) = do+        hard <- softConversion Softspace docs+        go (hard : docs)++    go (Hardspace : docs) = do+        chunk <- makeChunk " "+        modify (NotTrimmable chunk :)+        go docs++    go (Softline : docs) = do+        hard <- softConversion Softline docs+        go (hard : docs)++    go (Hardline : docs) = do+        buffer <- get+        tell $ bufferToChunks buffer <> [NewlineChunk]+        indentation <- asks deIndent+        modify $ \_ -> if L.null docs then [] else indentation+        go docs++    go (WrapAt {..} : docs) = do+        local (\env -> env {deWrap = wrapAtCol}) $ go (unDoc wrapDoc)+        go docs++    go (Ansi {..} : docs) = do+        local (\env -> env {deCodes = ansiCode (deCodes env)}) $+            go (unDoc ansiDoc)+        go docs++    go (Indent {..} : docs) = do+        local (\env -> env {deIndent = indentOtherLines ++ deIndent env}) $ do+            modify (indentFirstLine ++)+            go (unDoc indentDoc)+        go docs++    go (Control ctrl : docs) = do+        tell [ControlChunk ctrl]+        go docs+++    makeChunk :: String -> DocM Chunk+    makeChunk str = do+        codes <- asks deCodes+        return $ StringChunk codes str++    -- Convert 'Softspace' or 'Softline' to 'Hardspace' or 'Hardline'+    softConversion :: DocE Doc -> [DocE Doc] -> DocM (DocE Doc)+    softConversion soft docs = do+        mbWrapCol <- asks deWrap+        case mbWrapCol of+            Nothing     -> return hard+            Just maxCol -> do+                -- Slow.+                currentLine <- gets (concatMap chunkToString . bufferToChunks)+                let currentCol = wcstrwidth currentLine+                case nextWordLength docs of+                    Nothing                            -> return hard+                    Just l+                        | currentCol + 1 + l <= maxCol -> return Hardspace+                        | otherwise                    -> return Hardline+      where+        hard = case soft of+            Softspace -> Hardspace+            Softline  -> Hardline+            _         -> soft++    nextWordLength :: [DocE Doc] -> Maybe Int+    nextWordLength []                 = Nothing+    nextWordLength (String x : xs)+        | L.null x                    = nextWordLength xs+        | otherwise                   = Just (wcstrwidth x)+    nextWordLength (Softspace : xs)   = nextWordLength xs+    nextWordLength (Hardspace : xs)   = nextWordLength xs+    nextWordLength (Softline : xs)    = nextWordLength xs+    nextWordLength (Hardline : _)     = Nothing+    nextWordLength (WrapAt {..} : xs) = nextWordLength (unDoc wrapDoc   ++ xs)+    nextWordLength (Ansi   {..} : xs) = nextWordLength (unDoc ansiDoc   ++ xs)+    nextWordLength (Indent {..} : xs) = nextWordLength (unDoc indentDoc ++ xs)+    nextWordLength (Control _ : _)    = Nothing+++--------------------------------------------------------------------------------+toString :: Doc -> String+toString = concat . map chunkToString . docToChunks+++--------------------------------------------------------------------------------+-- | Returns the rows and columns necessary to render this document+dimensions :: Doc -> (Int, Int)+dimensions doc =+    let ls = lines (toString doc) in+    (length ls, foldr max 0 (map wcstrwidth ls))+++--------------------------------------------------------------------------------+null :: Doc -> Bool+null doc = case unDoc doc of [] -> True; _ -> False+++--------------------------------------------------------------------------------+hPutDoc :: IO.Handle -> Doc -> IO ()+hPutDoc h = mapM_ (hPutChunk h) . docToChunks+++--------------------------------------------------------------------------------+putDoc :: Doc -> IO ()+putDoc = hPutDoc IO.stdout+++--------------------------------------------------------------------------------+mkDoc :: DocE Doc -> Doc+mkDoc e = Doc [e]+++--------------------------------------------------------------------------------+string :: String -> Doc+string = mkDoc . String  -- TODO (jaspervdj): Newline conversion?
+ lib/Patat/PrettyPrint/Matrix.hs view
@@ -0,0 +1,75 @@+--------------------------------------------------------------------------------+{-# LANGUAGE BangPatterns #-}+module Patat.PrettyPrint.Matrix+    ( Matrix+    , Cell (..)+    , emptyCell+    , docToMatrix+    , hPutMatrix+    ) where+++--------------------------------------------------------------------------------+import           Control.Monad              (unless, when)+import           Data.Char.WCWidth.Extended (wcwidth)+import qualified Data.Vector                as V+import qualified Data.Vector.Mutable        as VM+import           Patat.PrettyPrint.Internal hiding (null)+import           Patat.Size                 (Size (..))+import qualified System.Console.ANSI        as Ansi+import qualified System.IO                  as IO+++--------------------------------------------------------------------------------+data Cell = Cell [Ansi.SGR] Char deriving (Eq, Show)+++--------------------------------------------------------------------------------+type Matrix = V.Vector Cell+++--------------------------------------------------------------------------------+emptyCell :: Cell+emptyCell = Cell [] ' '+++--------------------------------------------------------------------------------+docToMatrix :: Size -> Doc -> Matrix+docToMatrix size doc = V.create $ do+    matrix <- VM.replicate (sRows size * sCols size) emptyCell+    go matrix 0 0 $ docToChunks doc+    pure matrix+  where+    go _ _ _ []                                      = pure ()+    go _ y _ _  | y >= sRows size                    = pure ()+    go r y _ (NewlineChunk : cs)                     = go r (y + 1) 0 cs+    go r y x cs | x > sCols size                     = go r (y + 1) 0 cs+    go r y x (ControlChunk ClearScreenControl  : cs) = go r y x cs  -- ?+    go r _ x (ControlChunk (GoToLineControl y) : cs) = go r y x cs+    go r y x (StringChunk _      []      : cs)       = go r y x cs+    go r y x (StringChunk codes (z : zs) : cs)       = do+        VM.write r (y * sCols size + x) (Cell codes z)+        go r y (x + wcwidth z) (StringChunk codes zs : cs)+++--------------------------------------------------------------------------------+hPutMatrix :: IO.Handle -> Size -> Matrix -> IO ()+hPutMatrix h size matrix = go 0 0 0 []+  where+    go !y !x !empties prevCodes+        | x >= sCols size     = IO.hPutStrLn h "" >> go (y + 1) 0 0 prevCodes+        | y >= sRows size     = Ansi.hSetSGR h [Ansi.Reset]+        -- Try to not print empty things (e.g. fill the screen with spaces) as+        -- an optimization.  Instead, store the number of empties and print them+        -- when something actually follows.+        | cell == emptyCell   = do+            unless (null prevCodes) $ Ansi.hSetSGR h [Ansi.Reset]+            go y (x + 1) (empties + 1) []+        | otherwise           = do+            unless (empties == 0) $ IO.hPutStr h (replicate empties ' ')+            when (prevCodes /= codes) $+                Ansi.hSetSGR h (Ansi.Reset : reverse codes)+            IO.hPutStr h [c]+            go y (x + wcwidth c) 0 codes+      where+        cell@(Cell codes c) = matrix V.! (y * sCols size + x)
+ lib/Patat/Size.hs view
@@ -0,0 +1,23 @@+--------------------------------------------------------------------------------+module Patat.Size+    ( Size (..)+    , getTerminalSize+    ) where+++--------------------------------------------------------------------------------+import           Data.Maybe                   (fromMaybe)+import qualified System.Console.Terminal.Size as Terminal+++--------------------------------------------------------------------------------+data Size = Size {sRows :: Int, sCols :: Int} deriving (Show)+++--------------------------------------------------------------------------------+getTerminalSize :: IO Size+getTerminalSize = do+    mbWindow <- Terminal.size+    let rows = fromMaybe 24 $ Terminal.height <$> mbWindow+        cols = fromMaybe 72 $ Terminal.width  <$> mbWindow+    pure $ Size {sRows = rows, sCols = cols}
+ lib/Patat/Transition.hs view
@@ -0,0 +1,42 @@+--------------------------------------------------------------------------------+{-# LANGUAGE OverloadedStrings #-}+module Patat.Transition+    ( Duration (..)+    , threadDelayDuration+    , TransitionGen+    , TransitionId+    , TransitionInstance (..)+    , parseTransitionSettings+    , newTransition+    , stepTransition+    ) where+++--------------------------------------------------------------------------------+import qualified Data.Aeson                  as A+import qualified Data.HashMap.Strict         as HMS+import           Data.Text                   (Text)+import qualified Data.Text                   as T+import           Patat.Presentation.Settings (TransitionSettings (..))+import           Patat.Transition.Internal+import qualified Patat.Transition.SlideLeft  as SlideLeft+++--------------------------------------------------------------------------------+transitions :: HMS.HashMap Text Transition+transitions = HMS.fromList+    [ ("slideLeft", Transition SlideLeft.slideLeft)+    ]+++--------------------------------------------------------------------------------+parseTransitionSettings+    :: TransitionSettings -> Either String TransitionGen+parseTransitionSettings ts = case HMS.lookup ty transitions of+    Nothing             -> Left $ "unknown transition type: " ++ show ty+    Just (Transition f) -> case A.fromJSON (A.Object $ tsParams ts) of+        A.Success conf -> Right $ f conf+        A.Error   err  -> Left $+            "could not parse " ++ T.unpack ty ++ " transition: " ++ err+  where+   ty = tsType ts
+ lib/Patat/Transition/Internal.hs view
@@ -0,0 +1,81 @@+--------------------------------------------------------------------------------+{-# LANGUAGE GADTs #-}+module Patat.Transition.Internal+    ( Duration (..)+    , threadDelayDuration++    , Transition (..)+    , TransitionGen+    , TransitionId+    , TransitionInstance (..)+    , newTransition+    , stepTransition+    ) where+++--------------------------------------------------------------------------------+import           Control.Concurrent       (threadDelay)+import qualified Data.Aeson               as A+import           Data.List.NonEmpty       (NonEmpty ((:|)))+import           Data.Unique              (Unique, newUnique)+import qualified Patat.PrettyPrint        as PP+import           Patat.PrettyPrint.Matrix+import           Patat.Size               (Size (..))+import           System.Random            (StdGen, newStdGen)+++--------------------------------------------------------------------------------+newtype Duration = Duration Double  -- Duration in seconds+    deriving (Show)+++--------------------------------------------------------------------------------+threadDelayDuration :: Duration -> IO ()+threadDelayDuration (Duration seconds) =+    threadDelay . round $ seconds * 1000 * 1000+++--------------------------------------------------------------------------------+data Transition where+    Transition :: A.FromJSON conf => (conf -> TransitionGen) -> Transition+++--------------------------------------------------------------------------------+type TransitionGen =+    Size -> Matrix -> Matrix -> StdGen -> NonEmpty (Matrix, Duration)+++--------------------------------------------------------------------------------+newtype TransitionId = TransitionId Unique deriving (Eq)+++--------------------------------------------------------------------------------+data TransitionInstance = TransitionInstance+    { tiId     :: TransitionId+    , tiSize   :: Size+    , tiFrames :: NonEmpty (Matrix, Duration)+    }+++--------------------------------------------------------------------------------+newTransition+    :: TransitionGen -> Size -> PP.Doc -> PP.Doc -> IO TransitionInstance+newTransition tgen termSize frame0 frame1 = do+    unique <- newUnique+    rgen   <- newStdGen+    let frames = tgen size matrix0 matrix1 rgen+    pure $ TransitionInstance (TransitionId unique) size frames+  where+    -- The actual part we want to animate does not cover the last row, which is+    -- always empty.+    size    = termSize {sRows = sRows termSize - 1}+    matrix0 = docToMatrix size frame0+    matrix1 = docToMatrix size frame1+++--------------------------------------------------------------------------------+stepTransition :: TransitionId -> TransitionInstance -> Maybe TransitionInstance+stepTransition transId trans | transId /= tiId trans = Just trans+stepTransition _       trans                         = case tiFrames trans of+    _ :| []     -> Nothing+    _ :| f : fs -> Just trans {tiFrames = f :| fs}
+ lib/Patat/Transition/SlideLeft.hs view
@@ -0,0 +1,59 @@+--------------------------------------------------------------------------------+{-# LANGUAGE TemplateHaskell #-}+module Patat.Transition.SlideLeft+    ( slideLeft+    ) where+++--------------------------------------------------------------------------------+import qualified Data.Aeson.Extended       as A+import qualified Data.Aeson.TH.Extended    as A+import           Data.Foldable             (for_)+import           Data.List.NonEmpty        (NonEmpty ((:|)))+import           Data.Maybe                (fromMaybe)+import qualified Data.Vector               as V+import qualified Data.Vector.Mutable       as VM+import           Patat.PrettyPrint.Matrix+import           Patat.Size                (Size (..))+import           Patat.Transition.Internal+++--------------------------------------------------------------------------------+data Config = Config+    { cDuration  :: Maybe (A.FlexibleNum Double)+    , cFrameRate :: Maybe (A.FlexibleNum Int)+    }+++--------------------------------------------------------------------------------+slideLeft :: Config -> TransitionGen+slideLeft config (Size rows cols) initial final _rgen =+    fmap (\f -> (f, Duration delay)) $+    frame 0 :| map frame [1 .. frames - 1]+  where+    duration  = fromMaybe 1  $ A.unFlexibleNum <$> cDuration  config+    frameRate = fromMaybe 24 $ A.unFlexibleNum <$> cFrameRate config++    frames = round $ duration * fromIntegral frameRate :: Int+    delay  = duration / fromIntegral (frames + 1)++    frame :: Int -> Matrix+    frame idx = V.create $ do+        ini <- V.unsafeThaw initial+        fin <- V.unsafeThaw final+        mat <- VM.replicate (rows * cols) emptyCell+        for_ [0 .. rows - 1] $ \y -> do+            VM.copy+                (VM.slice (y * cols) (cols - offset) mat)+                (VM.slice (y * cols + offset) (cols - offset) ini)+            VM.copy+                (VM.slice (y * cols + cols - offset) offset mat)+                (VM.slice (y * cols) offset fin)+        pure mat+      where+        offset = max 0 . min cols . (round :: Double -> Int) $+            fromIntegral (idx + 1) / fromIntegral frames * fromIntegral cols+++--------------------------------------------------------------------------------+$(A.deriveFromJSON A.dropPrefixOptions ''Config)
patat.cabal view
@@ -1,5 +1,5 @@ Name:                patat-Version:             0.9.2.0+Version:             0.10.0.0 Synopsis:            Terminal-based presentations using Pandoc Description:         Terminal-based presentations using Pandoc. License:             GPL-2@@ -47,12 +47,14 @@     pandoc               >= 3.1  && < 3.2,     pandoc-types         >= 1.23 && < 1.24,     process              >= 1.6  && < 1.7,+    random               >= 1.1  && < 1.3,     skylighting          >= 0.10 && < 0.15,     terminal-size        >= 0.3  && < 0.4,     text                 >= 1.2  && < 2.1,     time                 >= 1.4  && < 1.13,     unordered-containers >= 0.2  && < 0.3,     yaml                 >= 0.8  && < 0.12,+    vector               >= 0.13 && < 0.14,     wcwidth              >= 0.0  && < 0.1,     -- We don't even depend on these packages but they can break cabal install     -- because of the conflicting 'Network.URI' module.@@ -75,6 +77,7 @@     Patat.Images.W3m     Patat.Main     Patat.Presentation+    Patat.Presentation.Comments     Patat.Presentation.Display     Patat.Presentation.Display.CodeBlock     Patat.Presentation.Display.Internal@@ -84,11 +87,18 @@     Patat.Presentation.Interactive     Patat.Presentation.Internal     Patat.Presentation.Read-    Patat.Presentation.SpeakerNotes+    Patat.Presentation.Settings     Patat.PrettyPrint+    Patat.PrettyPrint.Internal+    Patat.PrettyPrint.Matrix+    Patat.Size     Patat.Theme+    Patat.Transition+    Patat.Transition.Internal+    Patat.Transition.SlideLeft    Other-modules:+    Control.Concurrent.Chan.Extended     Data.Aeson.Extended     Data.Aeson.TH.Extended     Data.Char.WCWidth.Extended