{- ----------------------------------------------------------------------------- Copyright 2020 Kevin P. Barry Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ----------------------------------------------------------------------------- -} -- Author: Kevin P. Barry [ta0kira@gmail.com] -- | A wrapping text-editor with dynamic sizing for -- . {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE Trustworthy #-} module WEditorBrick.WrappingEditor ( WrappingEditor, WrappingEditorAction, WrappingEditorDoer, doWrappingEditor, dumpWrappingEditor, genericWrappingEditor, handleWrappingEditor, mapWrappingEditor, newWrappingEditor, renderWrappingEditor, ) where import Brick.Main import Brick.Types import Brick.Widgets.Core import Graphics.Vty.Input import Lens.Micro import WEditor.Base import WEditor.Document -- | Create a new 'WrappingEditor' using the default editor component. newWrappingEditor :: FixedFontParser a c => a -> n -> [[c]] -> WrappingEditor c n newWrappingEditor b n cs = genericWrappingEditor n $ editDocument b $ map UnparsedPara cs -- | Create a new 'WrappingEditor' using a custom editor component. genericWrappingEditor :: (FixedFontViewer a c, FixedFontEditor a c) => n -> a -> WrappingEditor c n genericWrappingEditor = WrappingEditor -- | Any action that updates the editor state. type WrappingEditorAction c = forall a. (FixedFontViewer a c, FixedFontEditor a c) => a -> a -- | Update the editor state. mapWrappingEditor :: WrappingEditorAction c -> WrappingEditor c n -> WrappingEditor c n mapWrappingEditor f (WrappingEditor name editor) = WrappingEditor name (f editor) -- | Any action that reads the editor state. type WrappingEditorDoer c b = forall a. (FixedFontViewer a c, FixedFontEditor a c) => a -> b -- | Read from the editor state. doWrappingEditor :: WrappingEditorDoer c b -> WrappingEditor c n -> b doWrappingEditor f (WrappingEditor _ editor) = f editor -- | Dump the final contents of the edited document. dumpWrappingEditor :: WrappingEditor c n -> [[c]] dumpWrappingEditor = map upText . doWrappingEditor exportData -- | Render the editor as a 'Widget'. renderWrappingEditor :: (Ord n, Show n) => Bool -> WrappingEditor Char n -> Widget n renderWrappingEditor focus editor = doWrappingEditor edit editor where edit e = Widget Greedy Greedy $ do ctx <- getContext let width = ctx^.availWidthL let height = ctx^.availHeightL -- NOTE: Resizing is a no-op if the size is unchanged. let e' = if height > 0 then viewerResizeAction (width,height) e else e render $ viewport (getName editor) Vertical $ setCursor e' $ textArea width height e' where setCursor | focus = showCursor (getName editor) . Location . getCursor | otherwise = const id textArea w h = vBox . lineFill w h . map (strFill w) . getVisible strFill w cs = str $ take w $ cs ++ repeat ' ' lineFill w h ls = take h $ ls ++ repeat (strFill w "") -- | Update the editor based on Brick events. handleWrappingEditor :: (Eq n) => WrappingEditor Char n -> Event -> EventM n (WrappingEditor Char n) handleWrappingEditor editor event = do extent <- lookupExtent (getName editor) return $ mapWrappingEditor (action . resizeAction extent) editor where action :: EditorAction Char action = case event of EvKey KBS [] -> editorBackspaceAction EvKey KDel [] -> editorDeleteAction EvKey KDown [] -> editorDownAction EvKey KEnd [] -> editorEndAction EvKey KEnter [] -> editorEnterAction EvKey KHome [] -> editorHomeAction EvKey KLeft [] -> editorLeftAction EvKey KPageDown [] -> editorPageDownAction EvKey KPageUp [] -> editorPageUpAction EvKey KRight [] -> editorRightAction EvKey KUp [] -> editorUpAction EvKey (KChar c) [] | not (c `elem` "\t\r\n") -> editorAppendAction [c] _ -> id resizeAction (Just ext) | snd (extentSize ext) > 0 = viewerResizeAction (extentSize ext) resizeAction _ = id -- | Editor widget for use with Brick. data WrappingEditor c n = forall a. (FixedFontViewer a c, FixedFontEditor a c) => WrappingEditor { weName :: n, weEditor :: a } instance Show n => Show (WrappingEditor c n) where show (WrappingEditor name editor) = "WrappingEditor { name: " ++ show name ++ ", size: " ++ show (getViewSize editor) ++ ", cursor: " ++ show (getCursor editor) ++ ", point: " ++ show (getEditPoint editor) ++ " }" instance Named (WrappingEditor c n) n where getName = weName