{-# LANGUAGE DefaultSignatures, FlexibleContexts, ScopedTypeVariables, TypeOperators, DeriveGeneric, FlexibleInstances, ScopedTypeVariables, OverloadedStrings, UndecidableInstances, OverlappingInstances #-} module Data.Editable (editor, Editable, Parseable(..)) where import GHC.Generics import Graphics.Vty.Widgets.All import qualified Data.Text as T import Graphics.Vty hiding (Button) import Control.Concurrent import Text.Read import Data.Monoid import Data.Typeable import Data.IORef -- | A type is parseable if you can: -- -- * From a string return either a value or an error message. -- -- * Represent a value as a string. -- -- * Showing a value then reading it yields the same value. -- -- * The type can be pretty printed. -- -- With overlapping instances, you get this instance for free for any -- type that is in 'Show', 'Read' and 'Typeable'. The 'String' instance is also -- provided so quotes are not required. class Parseable a where reader :: String -> Either String a shower :: a -> String typeName :: a -> String instance Parseable [Char] where reader = Right shower = id typeName _ = "String" instance (Show a, Read a, Typeable a) => Parseable a where reader = readEither shower = show typeName = show . typeRep . proxy where proxy :: a -> Proxy a proxy _ = Proxy -- | Launch an editor for a value with @editor@. -- Editable can be derived with @instance Editable a@ so long as: -- -- * @a@ instances 'Generic' (i.e. have @deriving Generics@ on the type). -- -- * All the constructors' fields' types are 'Parseable'. class Editable a where -- | Launch an interactive editor for a value. editor :: a -> IO a default editor :: (Generic a, GEditable (Rep a)) => a -> IO a editor = fmap to . geditor Nothing Nothing . from class GEditable f where geditor :: Maybe String -> Maybe String -> f a -> IO (f a) instance (Parseable e) => GEditable (K1 i e) where geditor t c = fmap K1 . (\x -> edit t c Nothing x) . unK1 instance (GEditable e, Constructor c) => GEditable (M1 C c e) where geditor t _ x = fmap M1 . geditor t (Just $ conName x) $ unM1 x instance (GEditable e, Datatype c) => GEditable (M1 D c e) where geditor _ c x = fmap M1 . geditor (Just $ datatypeName x) c $ unM1 x instance (GEditable e, Selector c) => GEditable (M1 S c e) where geditor t c = fmap M1 . geditor t c . unM1 instance (GEditable b, GEditable c) => GEditable (b :*: c) where geditor t d (b :*: c) = do l <- geditor t d b r <- geditor t d c return (l :*: r) instance (GEditable b, GEditable c) => GEditable (b :+: c) where geditor t c (L1 l) = fmap L1 $ geditor t c l geditor t c (R1 r) = fmap R1 $ geditor t c r instance GEditable U1 where geditor _ _ U1 = do putStrLn "Editing () yields ()" -- not so true, can't pick ⊥ return U1 -- the vty editor edit :: Parseable a => Maybe String -> Maybe String -> Maybe String -> a -> IO a edit datatype fieldName pError initialV = do -- To stop VTY from catching GHCI's first enter keypress threadDelay 1 isBottom <- newIORef False e <- editWidget setEditText e (T.pack (shower initialV)) setEditCursorPosition (0, length (shower initialV)) e fg <- newFocusGroup _ <- addToFocusGroup fg e be <- bordered =<< boxFixed 40 1 e c <- centered =<< ((plainText $"Data type: " <> maybe "unknown" T.pack datatype) <--> plainText ("Constructor: " <> maybe "unknown" T.pack fieldName) <--> plainText ("Field type: " <> (T.pack (typeName initialV))) <--> plainText (maybe "" (T.pack . (++) "Parse error: ") pError) <--> (return be) <--> plainText "Push ESC to use ⊥." >>= withBoxSpacing 1 ) coll <- newCollection _ <- addToCollection coll c fg fg `onKeyPressed` \_ k _ -> case k of KEsc -> shutdownUi >> writeIORef isBottom True >> return True KEnter -> shutdownUi >> return True _ -> return False runUi coll defaultContext isb <- readIORef isBottom if isb then return undefined else do res <- T.unpack `fmap` getEditText e case reader res of Right x -> return x Left er -> do edit datatype fieldName (Just $ "Failed to parse: " ++ show res ++ "\n" ++ er) initialV