@@ -55,9 +55,7 @@ import Control.Monad.Fix (MonadFix)
5555import Control.Monad.Trans (lift)
5656import Control.Monad.Trans.Reader (ReaderT, runReaderT, asks, ask)
5757import Control.Monad.Trans.Writer (WriterT, runWriterT, censor, tell)
58−import Data.Char
5958import Data.Default
60−import Data.Maybe (fromMaybe)
6159import Data.Text (Text)
6260import qualified Data.Text as T
6361import Graphics.Vty (Image, Attr)
@@ -65,6 +63,8 @@ import qualified Graphics.Vty as V
6563import Reflex
6664
6765import Reflex.Vty.Host
66+import Reflex.Vty.Widget.Text (TextZipper(..), DisplayLines(..))
67+import qualified Reflex.Vty.Widget.Text as TZ
6868
6969-- | The context within which a 'VtyWidget' runs
7070data VtyWidgetCtx t = VtyWidgetCtx
@@ -448,166 +448,49 @@ wrapWithOffset maxWidth n xs =
448448 let (firstLine, rest) = T.splitAt (maxWidth - n) xs
449449 in firstLine : (fmap (T.take maxWidth) . takeWhile (not . T.null) . iterate (T.drop maxWidth) $ rest)
450450
451−wrapInputState
452− :: Int -- ^ Maximum line length
453− -> Attr -- ^ Normal string attributes
454− -> Attr -- ^ Cursor character attributes
455− -> InputState -- ^ The input to render
456− -> ([Image], Int)
457−wrapInputState maxWidth attrs cursorAttrs (InputState before after) =
458− let -- Break up the InputState into:
459− -- * A top section of lines before the line containing the cursor
460− -- * A part of the line containing the cursor that comes immediately before the cursor
461− -- * The cursor character (i.e., the character the cursor is on top of)
462− -- * The remainder of the line containing the cursor
463− -- * The bottom section of lines after the line containing the cursor
464− (top, midBefore, cursorChar, midAfter, bottom) = case (initLast before', headTail after') of
465− (Nothing, Nothing) -> ([], "", ' ', "", [])
466− (Just (top', mid), Nothing) -> (top', mid, ' ', "", [])
467− (Nothing, Just (mid, bottom')) -> case T.uncons mid of
468− Nothing -> ([], "", ' ', "", bottom')
469− Just (c, rest) -> ([], "", c, rest, bottom')
470− (Just (top', midB), Just (midA, bottom')) -> case T.uncons midA of
471− Nothing -> (top', midB, ' ', "", bottom')
472− Just (c, rest) -> (top', midB, c, rest, bottom')
473− wrappedTop = wrap top
474− wrappedBottom = wrap bottom
475− (midBeforeTop, midBefore') = fromMaybe ([], "") $ initLast $ wrap [midBefore]
476− cursorAfterEOL = T.length midBefore' == maxWidth
477− (midAfter', midAfterBottom) =
478− let offset = if cursorAfterEOL then 1 else T.length midBefore' + 1
479− in case wrapWithOffset maxWidth offset midAfter of
480− [] -> ("", [])
481− x:xs -> (x, xs)
482− cursor = V.char cursorAttrs cursorChar
483− images = mconcat
484− [ vstring <$> wrappedTop
485− , vstring <$> midBeforeTop
486− , if cursorAfterEOL
487− then
488− [ vstring midBefore'
489− , V.horizCat
490− [ cursor
491− , vstring midAfter'
492− ]
493− ]
494− else
495− [ V.horizCat
496− [ vstring midBefore'
497− , cursor
498− , vstring midAfter'
499− ]
500− ]
501− , vstring <$> midAfterBottom
502− , vstring <$> wrappedBottom
503− ]
504− cursorY = length wrappedTop + length midBeforeTop + if cursorAfterEOL then 1 else 0
505− in (images, cursorY)
506− where
507− before' = T.split (=='\n') before
508− after' = T.split (=='\n') after
509− vstring = V.string attrs . T.unpack
510− wrap :: [Text] -> [Text]
511− wrap = concatMap (wrapWithOffset maxWidth 0)
512− headTail :: [a] -> Maybe (a, [a])
513− headTail = \case
514− [] -> Nothing
515− x:xs -> Just (x, xs)
516− initLast :: [a] -> Maybe ([a], a)
517− initLast = \case
518− [] -> Nothing
519− (x:xs) -> case initLast xs of
520− Nothing -> Just ([], x)
521− Just (ys, y) -> Just (x:ys, y)
522−
523−data InputState = InputState
524− { _inputState_beforeCursor :: Text
525− , _inputState_afterCursor :: Text
526− }
527−
528−inputValue :: InputState -> Text
529−inputValue (InputState a b) = a <> b
530−
531−updateInputState :: V.Event -> InputState -> InputState
532−updateInputState ev i@(InputState before after) = case ev of
533− -- Regular characters
534− V.EvKey (V.KChar k) [] -> InputState (T.snoc before k) after
535− -- Deletion buttons
536− V.EvKey V.KBS [] -> InputState (if T.null before then T.empty else T.init before) after
537− V.EvKey V.KDel [] -> case T.uncons after of
538− Nothing -> i
539− Just (_, a) -> InputState before a
540− -- Key combinations
541− V.EvKey (V.KChar 'u') [V.MCtrl] -> InputState mempty mempty
542− V.EvKey (V.KChar 'w') [V.MCtrl] -> InputState (T.dropWhileEnd (not . isSpace) $ T.dropWhileEnd isSpace $ before) after
543− -- Arrow keys
544− V.EvKey V.KRight [] -> case T.uncons after of
545− Nothing -> i
546− Just (a, as) -> InputState (T.snoc before a) as
547− V.EvKey V.KLeft [] -> case T.unsnoc before of
548− Nothing -> i
549− Just (bs, b) -> InputState bs (T.cons b after)
550− V.EvKey V.KUp [] ->
551− let (pre, post) = T.breakOnEnd "\n" before
552− (top, previousLine) = T.breakOnEnd "\n" (T.dropEnd 1 pre)
553− (previousLineBefore, previousLineAfter) = T.splitAt (T.length post) previousLine
554− in if T.null pre
555− then i
556− else InputState (top <> previousLineBefore) (previousLineAfter <> "\n" <> post <> after)
557− V.EvKey V.KDown [] ->
558− let offset = T.length $ T.takeWhileEnd (/='\n') before
559− (pre, post) = T.breakOn "\n" after
560− (nextLine, rest) = T.breakOn "\n" (T.drop 1 post)
561− (nextLineBefore, nextLineAfter) = T.splitAt offset nextLine
562− in if T.null post
563− then i
564− else InputState (before <> pre <> "\n" <> nextLineBefore) (nextLineAfter <> rest)
565− V.EvKey V.KHome [] ->
566− let (pre, post) = T.breakOnEnd "\n" before
567− in InputState pre (post <> after)
568− V.EvKey V.KEnd [] ->
569− let (pre, post) = T.breakOn "\n" after
570− in InputState (before <> pre) post
571− _ -> i
572−
573451data TextInputConfig t = TextInputConfig
574− { _textInputConfig_initialValue :: Text
575− , _textInputConfig_modifyInputState :: Event t (InputState -> InputState)
452+ { _textInputConfig_initialValue :: TextZipper
453+ , _textInputConfig_modify :: Event t (TextZipper -> TextZipper)
576454 }
577455
578456instance Reflex t => Default (TextInputConfig t) where
579− def = TextInputConfig "" never
457+ def = TextInputConfig TZ.empty never
580458
581−textInput :: (Reflex t, MonadHold t m, MonadFix m) => TextInputConfig t -> VtyWidget t m (Dynamic t Text)
459+textInput
460+ :: (Reflex t, MonadHold t m, MonadFix m)
461+ => TextInputConfig t
462+ -> VtyWidget t m (Dynamic t Text)
582463textInput cfg = do
583464 i <- input
584− v <- foldDyn ($) (InputState (_textInputConfig_initialValue cfg) mempty) $ mergeWith (.)
585− [ updateInputState <$> i
586− , _textInputConfig_modifyInputState cfg
465+ v <- foldDyn ($) (_textInputConfig_initialValue cfg) $ mergeWith (.)
466+ [ TZ.updateTextZipper <$> i
467+ , _textInputConfig_modify cfg
587468 ]
588469 dw <- displayWidth
589470 dh <- displayHeight
590− let img = (\w s -> wrapInputState w V.defAttr (V.withStyle V.defAttr V.reverseVideo) s) -- TODO cursor ought to blink
591− <$> dw
592− <*> v
593− y <- holdUniqDyn $ snd <$> img
471+ let rows = (\w s -> TZ.displayLines w s) <$> dw <*> v
472+ img = TZ.images . _displayLines_spans <$> rows
473+ y <- holdUniqDyn $ _displayLines_cursorY <$> rows
594474 let newScrollTop st (h, cursorY)
595475 | cursorY < st = cursorY
596476 | cursorY >= st + h = cursorY - h + 1
597477 | otherwise = st
598478 rec let hy = attachWith newScrollTop (current scrollTop) $ updated $ zipDyn dh y
599479 scrollTop <- holdDyn 0 hy
600− tellImages $ (\(imgs, _) st -> (:[]) . V.vertCat $ drop st imgs) <$> current img <*> current scrollTop
601− return $ inputValue <$> v
480+ tellImages $ (\imgs st -> (:[]) . V.vertCat $ drop st imgs) <$> current img <*> current scrollTop
481+ return $ TZ.value <$> v
602482
603−multilineTextInput :: (Reflex t, MonadHold t m, MonadFix m) => TextInputConfig t -> VtyWidget t m (Dynamic t Text)
483+multilineTextInput
484+ :: (Reflex t, MonadHold t m, MonadFix m)
485+ => TextInputConfig t
486+ -> VtyWidget t m (Dynamic t Text)
604487multilineTextInput cfg = do
605488 i <- input
606489 textInput $ cfg
607− { _textInputConfig_modifyInputState = mergeWith (.)
490+ { _textInputConfig_modify = mergeWith (.)
608491 [ fforMaybe i $ \case
609− V.EvKey V.KEnter [] -> Just $ \(InputState before after) -> InputState (T.snoc before '\n') after
492+ V.EvKey V.KEnter [] -> Just $ TZ.insert "\n"
610493 _ -> Nothing
611− , _textInputConfig_modifyInputState cfg
494+ , _textInputConfig_modify cfg
612495 ]
613496 }