matterhorn-50200.17.0: src/Matterhorn/Draw/InputPreview.hs
{-# LANGUAGE RankNTypes #-}
module Matterhorn.Draw.InputPreview
( inputPreview
)
where
import Prelude ()
import Matterhorn.Prelude
import Brick
import Brick.Widgets.Border
import Brick.Widgets.Edit
import Control.Arrow ( (>>>) )
import qualified Data.Text as T
import Data.Text.Zipper ( insertChar, getText, gotoEOL )
import Data.Time.Calendar ( fromGregorian )
import Data.Time.Clock ( UTCTime(..) )
import qualified Graphics.Vty as Vty
import Lens.Micro.Platform ( SimpleGetter, to )
import Network.Mattermost.Types ( ServerTime(..), UserId, TeamId
)
import Matterhorn.Constants
import Matterhorn.Draw.Messages
import Matterhorn.Draw.RichText
import Matterhorn.Themes
import Matterhorn.Types
import Matterhorn.Types.RichText ( parseMarkdown, TeamBaseURL )
inputPreview :: ChatState
-> SimpleGetter ChatState (EditState Name)
-> TeamId
-> Name
-> HighlightSet
-> Widget Name
inputPreview st editWhich tId vpName hs
| not $ st^.csResources.crConfiguration.configShowMessagePreviewL = emptyWidget
| otherwise = thePreview
where
uId = myUserId st
-- Insert a cursor sentinel into the input text just before
-- rendering the preview. We use the inserted sentinel (which is
-- not rendered) to get brick to ensure that the line the cursor is
-- on is visible in the preview viewport. We put the sentinel at
-- the *end* of the line because it will still influence markdown
-- parsing and can create undesirable/confusing churn in the
-- rendering while the cursor moves around. If the cursor is at the
-- end of whatever line the user is editing, that is very unlikely
-- to be a problem.
curContents = getText $ (gotoEOL >>> insertChar cursorSentinel) $
st^.editWhich.esEditor.editContentsL
eName = getName $ st^.editWhich.esEditor
curStr = T.intercalate "\n" curContents
overrideTy = case st^.editWhich.esEditMode of
Editing _ ty -> Just ty
_ -> Nothing
baseUrl = serverBaseUrl st tId
previewMsg = previewFromInput baseUrl overrideTy uId curStr
thePreview = let noPreview = str "(No preview)"
msgPreview = case previewMsg of
Nothing -> noPreview
Just pm -> if T.null curStr
then noPreview
else prview pm $ getParentMessage st pm
tag = MessagePreviewViewport eName
prview m p = freezeBorders $
renderMessage MessageData
{ mdMessage = m
, mdUserName = m^.mUser.to (printableNameForUserRef st)
, mdParentMessage = p
, mdParentUserName = p >>= (^.mUser.to (printableNameForUserRef st))
, mdHighlightSet = hs
, mdEditThreshold = Nothing
, mdShowOlderEdits = False
, mdRenderReplyParent = True
, mdRenderReplyIndent = True
, mdIndentBlocks = True
, mdThreadState = NoThread
, mdShowReactions = True
, mdMessageWidthLimit = Nothing
, mdMyUsername = myUsername st
, mdMyUserId = myUserId st
, mdWrapNonhighlightedCodeBlocks = True
, mdTruncateVerbatimBlocks = Nothing
, mdClickableNameTag = tag
}
in (maybePreviewViewport vpName msgPreview) <=>
hBorderWithLabel (withDefAttr clientEmphAttr $ str "[Preview ↑]")
previewFromInput :: TeamBaseURL -> Maybe MessageType -> UserId -> Text -> Maybe Message
previewFromInput _ _ _ s | s == T.singleton cursorSentinel = Nothing
previewFromInput baseUrl overrideTy uId s =
-- If it starts with a slash but not /me, this has no preview
-- representation
let isCommand = "/" `T.isPrefixOf` s
isEmoteCmd = "/me " `T.isPrefixOf` s
content = if isEmoteCmd
then T.stripStart $ T.drop 3 s
else s
msgTy = fromMaybe (if isEmoteCmd then CP Emote else CP NormalPost) overrideTy
in if isCommand && not isEmoteCmd
then Nothing
else Just $ Message { _mText = parseMarkdown (Just baseUrl) content
, _mMarkdownSource = content
, _mUser = UserI False uId
, _mDate = ServerTime $ UTCTime (fromGregorian 1970 1 1) 0
-- The date is not used for preview
-- rendering, but we need to provide one.
-- Ideally we'd just use today's date, but
-- the rendering function is pure so we
-- can't.
, _mType = msgTy
, _mPending = False
, _mDeleted = False
, _mAttachments = mempty
, _mInReplyToMsg = NotAReply
, _mMessageId = Nothing
, _mReactions = mempty
, _mOriginalPost = Nothing
, _mFlagged = False
, _mPinned = False
, _mChannelId = Nothing
}
maybePreviewViewport :: Name -> Widget Name -> Widget Name
maybePreviewViewport n w =
Widget Greedy Fixed $ do
result <- render w
case (Vty.imageHeight $ result^.imageL) > previewMaxHeight of
False -> return result
True ->
render $ vLimit previewMaxHeight $ viewport n Vertical $
(resultToWidget result)