packages feed

helic 0.3.2.0 → 0.4.0.0

raw patch · 56 files changed

+763/−604 lines, 56 filesdep +incipitdep +transformersdep −aesondep −data-defaultdep −eitherdep ~exondep ~polysemy-chronosdep ~polysemy-concsetup-changedPVP ok

version bump matches the API change (PVP)

Dependencies added: incipit, transformers

Dependencies removed: aeson, data-default, either, polysemy-resume, relude, text

Dependency ranges changed: exon, polysemy-chronos, polysemy-conc, polysemy-http, polysemy-log, polysemy-process, polysemy-time

API changes (from Hackage documentation)

- Helic: clipboardEvents :: Members [Events resource XClipboardEvent, Reader GtkState, Log, Embed IO, Final IO] r => Sem r ()
- Helic: listenXClipboard :: Members [Events resource XClipboardEvent, Log, Error Text, Race, Resource, Async, Embed IO, Final IO] r => InterpreterFor (Reader GtkState) r
- Helic: withMainLoop :: Members [Log, Error Text, Race, Async, Resource, Embed IO] r => InterpreterFor (Reader GtkState) r
- Helic.Interpreter.XClipboard: clipboardEvents :: Members [Events resource XClipboardEvent, Reader GtkState, Log, Embed IO, Final IO] r => Sem r ()
- Helic.Interpreter.XClipboard: listenXClipboard :: Members [Events resource XClipboardEvent, Log, Error Text, Race, Resource, Async, Embed IO, Final IO] r => InterpreterFor (Reader GtkState) r
- Helic.Interpreter.XClipboard: subscribeToClipboard :: Members [Events resource XClipboardEvent, Reader GtkState, Log, Embed IO, Final IO] r => Clipboard -> Selection -> Sem r ()
- Helic.Interpreter.XClipboard: withMainLoop :: Members [Log, Error Text, Race, Async, Resource, Embed IO] r => InterpreterFor (Reader GtkState) r
+ Helic: Clipboard :: Selection
+ Helic: Primary :: Selection
+ Helic: Secondary :: Selection
+ Helic: data Gtk s :: Effect
+ Helic: data GtkClipboard :: Effect
+ Helic: data GtkMain (s :: Type) :: Effect
+ Helic: data Selection
+ Helic: gtkMainLoop :: Members [Scoped resource (Gtk s) !! Text, GtkMain s, Log, Race, Resource, Async] r => Sem r () -> Sem r a -> Sem r a
+ Helic: handleGtkClipboard :: Members [Log, Embed IO, Final IO] r => Display -> GtkClipboard (Sem r0) a -> Tactical effect (Sem r0) (Stop Text : r) a
+ Helic: handleGtkMain :: forall s wait restart e m r a. TimeUnit wait => TimeUnit restart => Members [Resource, Sync GtkLock, Sync StartGtkMain, Sync (GtkResource s)] r => wait -> restart -> GtkMain s m a -> Tactical e m r a
+ Helic: interpretGtk :: Members [Resource, Log, Embed IO] r => InterpreterFor (Scoped Display (Gtk Display) !! Text) r
+ Helic: interpretGtkClipboard :: Members [GtkMain Display, Log, Embed IO, Final IO] r => InterpreterFor (Scoped Display GtkClipboard !! Text) r
+ Helic: interpretGtkMain :: forall s wait restart r. TimeUnit wait => TimeUnit restart => Members [Resource, Race, Embed IO] r => wait -> restart -> InterpreterFor (GtkMain s) r
+ Helic.Data.Selection: Clipboard :: Selection
+ Helic.Data.Selection: Primary :: Selection
+ Helic.Data.Selection: Secondary :: Selection
+ Helic.Data.Selection: data Selection
+ Helic.Data.Selection: instance GHC.Classes.Ord Helic.Data.Selection.Selection
+ Helic.Data.Selection: instance GHC.Enum.Bounded Helic.Data.Selection.Selection
+ Helic.Data.Selection: instance GHC.Enum.Enum Helic.Data.Selection.Selection
+ Helic.Data.Selection: toXString :: Selection -> Text
+ Helic.Effect.Gtk: [Main] :: Gtk s m ()
+ Helic.Effect.Gtk: [Resource] :: Gtk s m s
+ Helic.Effect.Gtk: data Gtk s :: Effect
+ Helic.Effect.GtkClipboard: [Events] :: (Selection -> Text -> m ()) -> GtkClipboard m ()
+ Helic.Effect.GtkClipboard: [Read] :: Selection -> GtkClipboard m (Maybe Text)
+ Helic.Effect.GtkClipboard: [Write] :: Selection -> Text -> GtkClipboard m ()
+ Helic.Effect.GtkClipboard: data GtkClipboard :: Effect
+ Helic.Effect.GtkMain: [Access] :: m s -> GtkMain s m s
+ Helic.Effect.GtkMain: [Request] :: m s -> GtkMain s m s
+ Helic.Effect.GtkMain: [Run] :: m a -> GtkMain s m a
+ Helic.Effect.GtkMain: [Running] :: s -> GtkMain s m ()
+ Helic.Effect.GtkMain: data GtkMain (s :: Type) :: Effect
+ Helic.Gtk: clipboardRequest :: Clipboard -> (Either Text Text -> IO ()) -> IO ()
+ Helic.Gtk: clipboardText :: Members [Reader Display, Log, Stop Text, Embed IO] r => Selection -> Sem r (Maybe Text)
+ Helic.Gtk: getClipboard :: Members [Reader Display, Stop Text, Embed IO] r => Selection -> Sem r Clipboard
+ Helic.Gtk: getDisplay :: Members [Stop Text, Embed IO] r => Sem r Display
+ Helic.Gtk: gtkClipboard :: Members [Stop Text, Embed IO] r => Display -> Selection -> Sem r Clipboard
+ Helic.Gtk: gtkUi :: Members [Stop Text, Embed IO] r => Text -> IO a -> Sem r a
+ Helic.Gtk: readClipboard :: Members [Log, Stop Text, Embed IO] r => Clipboard -> Sem r (Maybe Text)
+ Helic.Gtk: setClipboardText :: Members [Reader Display, Log, Stop Text, Embed IO, Final IO] r => Selection -> Text -> Sem r ()
+ Helic.Gtk: subscribeToClipboard :: Members [Reader Display, Log, Stop Text, Embed IO, Final IO] r => (Selection -> Text -> Sem r ()) -> Selection -> Sem r ()
+ Helic.Gtk: subscribeWith :: Member (Final IO) r => Clipboard -> (Either Text Text -> Sem r ()) -> Sem r ()
+ Helic.Gtk: unsafeGtkClipboard :: MonadIO m => Display -> Selection -> m Clipboard
+ Helic.Gtk: unsafeSetClipboard :: MonadIO m => Clipboard -> Text -> m ()
+ Helic.Gtk: writeClipboard :: Members [Stop Text, Embed IO] r => Clipboard -> Text -> Sem r ()
+ Helic.GtkClipboard: subscribeEvents :: Members [Scoped s GtkClipboard !! Text, Events res XClipboardEvent, Log] r => Sem r ()
+ Helic.GtkMain: gtkMain :: Members [Scoped resource (Gtk s), GtkMain s, Resource] r => Sem r () -> Sem r ()
+ Helic.GtkMain: gtkMainLoop :: Members [Scoped resource (Gtk s) !! Text, GtkMain s, Log, Race, Resource, Async] r => Sem r () -> Sem r a -> Sem r a
+ Helic.GtkMain: gtkResource :: Members [GtkMain s, Log, Stop Text] r => Sem r s
+ Helic.GtkMain: loopGtkMain :: Members [Scoped resource (Gtk s) !! Text, GtkMain s, Resource, Log] r => Sem r () -> Sem r ()
+ Helic.Interpreter: interpreting :: forall e r a. FirstOrder e "interpret" => Sem (e : r) a -> (forall r0 x. e (Sem r0) x -> Sem r x) -> Sem r a
+ Helic.Interpreter.Gtk: bracketGtk :: Members [Resource, Log, Embed IO] r => (Display -> Sem (Stop Text : r) a) -> Sem (Stop Text : r) a
+ Helic.Interpreter.Gtk: interpretGtk :: Members [Resource, Log, Embed IO] r => InterpreterFor (Scoped Display (Gtk Display) !! Text) r
+ Helic.Interpreter.GtkClipboard: handleGtkClipboard :: Members [Log, Embed IO, Final IO] r => Display -> GtkClipboard (Sem r0) a -> Tactical effect (Sem r0) (Stop Text : r) a
+ Helic.Interpreter.GtkClipboard: interpretGtkClipboard :: Members [GtkMain Display, Log, Embed IO, Final IO] r => InterpreterFor (Scoped Display GtkClipboard !! Text) r
+ Helic.Interpreter.GtkClipboard: withGtkClipboard :: Member (Scoped resource GtkClipboard) r => InterpreterFor GtkClipboard r
+ Helic.Interpreter.GtkMain: handleGtkMain :: forall s wait restart e m r a. TimeUnit wait => TimeUnit restart => Members [Resource, Sync GtkLock, Sync StartGtkMain, Sync (GtkResource s)] r => wait -> restart -> GtkMain s m a -> Tactical e m r a
+ Helic.Interpreter.GtkMain: instance GHC.Classes.Eq Helic.Interpreter.GtkMain.GtkLock
+ Helic.Interpreter.GtkMain: instance GHC.Classes.Eq Helic.Interpreter.GtkMain.StartGtkMain
+ Helic.Interpreter.GtkMain: instance GHC.Classes.Eq s => GHC.Classes.Eq (Helic.Interpreter.GtkMain.GtkResource s)
+ Helic.Interpreter.GtkMain: instance GHC.Show.Show Helic.Interpreter.GtkMain.GtkLock
+ Helic.Interpreter.GtkMain: instance GHC.Show.Show Helic.Interpreter.GtkMain.StartGtkMain
+ Helic.Interpreter.GtkMain: instance GHC.Show.Show s => GHC.Show.Show (Helic.Interpreter.GtkMain.GtkResource s)
+ Helic.Interpreter.GtkMain: interpretGtkMain :: forall s wait restart r. TimeUnit wait => TimeUnit restart => Members [Resource, Race, Embed IO] r => wait -> restart -> InterpreterFor (GtkMain s) r
+ Helic.Interpreter.GtkMain: interpretWithGtk :: forall e s r. Members [GtkMain s, Log] r => (forall r0 x. s -> e (Sem r0) x -> Tactical e (Sem r0) (Stop Text : r) x) -> InterpreterFor (Scoped s e !! Text) r
+ Helic.Stop: tryStop :: Members [Stop Text, Embed IO] r => IO a -> Sem r a
- Helic: interpretAgentX :: Members [EventConsumer xr XClipboardEvent, Events er Event, Reader InstanceName, XClipboard, ChronosTime] r => Members [Events xr XClipboardEvent, Error Text, Race, Resource, Async, Embed IO, Final IO] r => InterpreterFor (Tagged AgentX Agent) r
+ Helic: interpretAgentX :: Members [EventConsumer xr XClipboardEvent, Events er Event, Reader InstanceName, XClipboard !! Text, ChronosTime] r => Members [Events xr XClipboardEvent, Log, Error Text, Race, Resource, Async, Embed IO, Final IO] r => InterpreterFor (Tagged AgentX Agent) r
- Helic: interpretXClipboardGtk :: Members [Reader GtkState, Log, Embed IO, Final IO] r => InterpreterFor XClipboard r
+ Helic: interpretXClipboardGtk :: Members [Scoped resource GtkClipboard !! Text, Log, Embed IO, Final IO] r => InterpreterFor (XClipboard !! Text) r
- Helic: subscribeToClipboard :: Members [Events resource XClipboardEvent, Reader GtkState, Log, Embed IO, Final IO] r => Clipboard -> Selection -> Sem r ()
+ Helic: subscribeToClipboard :: Members [Reader Display, Log, Stop Text, Embed IO, Final IO] r => (Selection -> Text -> Sem r ()) -> Selection -> Sem r ()
- Helic: transformXEvents :: Member (Reader InstanceName) r => Members [EventConsumer xr XClipboardEvent, Events er Event, XClipboard, ChronosTime, Resource, Race, Async] r => Sem r a -> Sem r a
+ Helic: transformXEvents :: Members [EventConsumer xr XClipboardEvent, Reader InstanceName] r => Members [Events er Event, XClipboard !! Text, Log, ChronosTime, Resource, Race, Async] r => Sem r a -> Sem r a
- Helic.Interpreter.AgentX: interpretAgentX :: Members [EventConsumer xr XClipboardEvent, Events er Event, Reader InstanceName, XClipboard, ChronosTime] r => Members [Events xr XClipboardEvent, Error Text, Race, Resource, Async, Embed IO, Final IO] r => InterpreterFor (Tagged AgentX Agent) r
+ Helic.Interpreter.AgentX: interpretAgentX :: Members [EventConsumer xr XClipboardEvent, Events er Event, Reader InstanceName, XClipboard !! Text, ChronosTime] r => Members [Events xr XClipboardEvent, Log, Error Text, Race, Resource, Async, Embed IO, Final IO] r => InterpreterFor (Tagged AgentX Agent) r
- Helic.Interpreter.AgentX: transformXEvents :: Member (Reader InstanceName) r => Members [EventConsumer xr XClipboardEvent, Events er Event, XClipboard, ChronosTime, Resource, Race, Async] r => Sem r a -> Sem r a
+ Helic.Interpreter.AgentX: transformXEvents :: Members [EventConsumer xr XClipboardEvent, Reader InstanceName] r => Members [Events er Event, XClipboard !! Text, Log, ChronosTime, Resource, Race, Async] r => Sem r a -> Sem r a
- Helic.Interpreter.XClipboard: interpretXClipboardGtk :: Members [Reader GtkState, Log, Embed IO, Final IO] r => InterpreterFor XClipboard r
+ Helic.Interpreter.XClipboard: interpretXClipboardGtk :: Members [Scoped resource GtkClipboard !! Text, Log, Embed IO, Final IO] r => InterpreterFor (XClipboard !! Text) r

Files

+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
changelog.md view
@@ -1,5 +1,13 @@ # Unreleased +# 0.4.0.0++* Rewrite the GTK main loop effects for better resilience when no display is available (yet).++# 0.3.1.0++* Sanitize newlines when inserting events+ # 0.3.0.0  * Add the `load` command that sets the clipboard to an event from the history.
helic.cabal view
@@ -1,11 +1,11 @@ cabal-version: 2.2 --- This file has been generated from package.yaml by hpack version 0.34.4.+-- This file has been generated from package.yaml by hpack version 0.34.6. -- -- see: https://github.com/sol/hpack  name:           helic-version:        0.3.2.0+version:        0.4.0.0 synopsis:       Clipboard Manager description:    See <https://hackage.haskell.org/package/helic/docs/Helic.html> category:       Clipboard@@ -47,14 +47,23 @@       Helic.Data.YankConfig       Helic.Effect.Agent       Helic.Effect.Client+      Helic.Effect.Gtk+      Helic.Effect.GtkClipboard+      Helic.Effect.GtkMain       Helic.Effect.History       Helic.Effect.XClipboard       Helic.Gtk+      Helic.GtkClipboard+      Helic.GtkMain+      Helic.Interpreter       Helic.Interpreter.Agent       Helic.Interpreter.AgentNet       Helic.Interpreter.AgentTmux       Helic.Interpreter.AgentX       Helic.Interpreter.Client+      Helic.Interpreter.Gtk+      Helic.Interpreter.GtkClipboard+      Helic.Interpreter.GtkMain       Helic.Interpreter.History       Helic.Interpreter.InstanceName       Helic.Interpreter.XClipboard@@ -63,15 +72,9 @@       Helic.Net.Api       Helic.Net.Client       Helic.Net.Server-      Helic.Prelude-      Helic.Prelude.Debug+      Helic.Stop       Helic.Tmux       Helic.Yank-  other-modules:-      Prelude-      Paths_helic-  autogen-modules:-      Paths_helic   hs-source-dirs:       lib   default-extensions:@@ -134,32 +137,27 @@       ViewPatterns   ghc-options: -Wall -Wredundant-constraints -Wunused-packages -fplugin=Polysemy.Plugin   build-depends:-      aeson >=1.4-    , base ==4.*+      base ==4.*     , chronos >=1.1.1-    , containers-    , data-default >=0.2-    , either >=5-    , exon >=0.2.0.1+    , exon >=0.3     , gi-gdk >=3     , gi-glib >=2     , gi-gtk >=3     , hostname >=1     , http-client >=0.5.14     , http-client-tls >=0.3.1+    , incipit >=0.1.0.3     , optparse-applicative >=0.16     , path >=0.8     , path-io >=1.6     , polysemy >=1.6-    , polysemy-chronos >=0.2.0.1-    , polysemy-conc >=0.5-    , polysemy-http >=0.5-    , polysemy-log >=0.4+    , polysemy-chronos >=0.3+    , polysemy-conc >=0.6+    , polysemy-http >=0.6+    , polysemy-log >=0.5     , polysemy-plugin >=0.4-    , polysemy-process >=0.5-    , polysemy-resume >=0.2-    , polysemy-time >=0.1.4-    , relude >=0.7+    , polysemy-process >=0.6+    , polysemy-time >=0.3     , servant >=0.18     , servant-client >=0.18     , servant-client-core >=0.18@@ -167,7 +165,7 @@     , table-layout >=0.9     , template-haskell     , terminal-size >=0.3.2.1-    , text+    , transformers     , typed-process >=0.2.6     , unix     , wai-extra >=3.1@@ -179,8 +177,6 @@  executable hel   main-is: Main.hs-  other-modules:-      Paths_helic   hs-source-dirs:       app   default-extensions:@@ -245,25 +241,25 @@   build-depends:       base ==4.*     , helic+    , incipit     , polysemy     , polysemy-plugin   mixins:       base hiding (Prelude)-    , helic hiding (Helic.Prelude)-    , helic (Helic.Prelude as Prelude)   default-language: Haskell2010  test-suite helic-unit   type: exitcode-stdio-1.0   main-is: Main.hs   other-modules:+      Helic.Dev       Helic.Test.ConfigFileTest       Helic.Test.Fixtures+      Helic.Test.GtkMainTest       Helic.Test.InsertEventTest       Helic.Test.ListenTest       Helic.Test.ListTest       Helic.Test.LoadTest-      Paths_helic   hs-source-dirs:       test   default-extensions:@@ -329,7 +325,9 @@       base ==4.*     , chronos     , containers+    , exon     , helic+    , incipit     , path     , polysemy     , polysemy-chronos@@ -342,6 +340,4 @@     , torsor   mixins:       base hiding (Prelude)-    , helic hiding (Helic.Prelude)-    , helic (Helic.Prelude as Prelude)   default-language: Haskell2010
lib/Helic.hs view
@@ -9,22 +9,32 @@   Agents,   -- ** XClipboard   XClipboard,+  -- ** Gtk+  Gtk,+  -- ** GtkMain+  GtkMain,+  -- ** GtkClipboard+  GtkClipboard,    -- * Interpreters   interpretAgentNet,   interpretAgentX,   interpretAgentTmux,   interpretXClipboardGtk,+  interpretGtk,+  interpretGtkMain,+  handleGtkMain,+  interpretGtkClipboard,+  handleGtkClipboard,    -- * Data   Event,+  Selection (..),    -- * Utilities   transformXEvents,-  withMainLoop,   subscribeToClipboard,-  clipboardEvents,-  listenXClipboard,+  gtkMainLoop,   Api,   serve,   listen,@@ -32,19 +42,24 @@  ) where +import Prelude hiding (listen)+ import Helic.Data.Event (Event)+import Helic.Data.Selection (Selection (..)) import Helic.Effect.Agent (Agent, AgentTag, Agents)+import Helic.Effect.Gtk (Gtk)+import Helic.Effect.GtkClipboard (GtkClipboard)+import Helic.Effect.GtkMain (GtkMain) import Helic.Effect.XClipboard (XClipboard)+import Helic.Gtk (subscribeToClipboard)+import Helic.GtkMain (gtkMainLoop) import Helic.Interpreter.AgentNet (interpretAgentNet) import Helic.Interpreter.AgentTmux (interpretAgentTmux) import Helic.Interpreter.AgentX (interpretAgentX, transformXEvents)-import Helic.Interpreter.XClipboard (-  clipboardEvents,-  interpretXClipboardGtk,-  listenXClipboard,-  subscribeToClipboard,-  withMainLoop,-  )+import Helic.Interpreter.Gtk (interpretGtk)+import Helic.Interpreter.GtkClipboard (handleGtkClipboard, interpretGtkClipboard)+import Helic.Interpreter.GtkMain (handleGtkMain, interpretGtkMain)+import Helic.Interpreter.XClipboard (interpretXClipboardGtk) import Helic.Listen (listen) import Helic.Net.Api (Api, serve) import Helic.Yank (yank)
lib/Helic/App.hs view
@@ -1,22 +1,16 @@+{-# options_haddock prune #-}++-- |App entry points.+-- Internal. module Helic.App where +import Conc (Critical, interpretAtomic, interpretEventsChan, interpretSync, withAsync_) import Polysemy.Chronos (ChronosTime) import qualified Polysemy.Conc as Conc-import Polysemy.Conc (-  Critical,-  Interrupt,-  interpretAtomic,-  interpretEventsChan,-  interpretSync,-  withAsync_,-  ) import Polysemy.Http (Manager) import Polysemy.Http.Interpreter.Manager (interpretManager)-import Polysemy.Log (-  Log,-  Logger,-  )-import Polysemy.Time (GhcTime)+import Polysemy.Log (Logger)+import Polysemy.Time (GhcTime, MilliSeconds (MilliSeconds), Seconds (Seconds))  import Helic.Data.Config (Config (Config)) import Helic.Data.Event (Event)@@ -28,13 +22,18 @@ import qualified Helic.Effect.Client as Client import Helic.Effect.Client (Client) import qualified Helic.Effect.History as History+import Helic.GtkClipboard (subscribeEvents)+import Helic.GtkMain (gtkMainLoop) import Helic.Interpreter.AgentNet (interpretAgentNet) import Helic.Interpreter.AgentTmux (interpretAgentTmux) import Helic.Interpreter.AgentX (interpretAgentX) import Helic.Interpreter.Client (interpretClientNet)+import Helic.Interpreter.Gtk (interpretGtk)+import Helic.Interpreter.GtkClipboard (interpretGtkClipboard)+import Helic.Interpreter.GtkMain (interpretGtkMain) import Helic.Interpreter.History (interpretHistory) import Helic.Interpreter.InstanceName (interpretInstanceName)-import Helic.Interpreter.XClipboard (interpretXClipboardGtk, listenXClipboard)+import Helic.Interpreter.XClipboard (interpretXClipboardGtk) import Helic.List (list) import Helic.Net.Api (serve) import Helic.Yank (yank)@@ -68,7 +67,10 @@   interpretAtomic mempty $   interpretInstanceName name $   interpretManager $-  listenXClipboard $+  interpretGtk $+  interpretGtkMain (MilliSeconds 500) (Seconds 10) $+  interpretGtkClipboard $+  gtkMainLoop subscribeEvents $   interpretXClipboardGtk $   interpretAgentX $   interpretAgentNet $
lib/Helic/Cli.hs view
@@ -11,9 +11,7 @@   interpretInterrupt,   interpretRace,   )-import Polysemy.Error (errorToIOFinal) import Polysemy.Log (-  Log,   LogEntry,   LogMessage,   Logger,@@ -26,7 +24,7 @@   ) import qualified Polysemy.Log.Data.DataLog as DataLog import Polysemy.Time (GhcTime, MilliSeconds (MilliSeconds), interpretTimeGhc)-import System.IO (hLookAhead)+import System.IO (hLookAhead, stdin)  import Helic.App (AppStack, IOStack, listApp, listenApp, loadApp, yankApp) import Helic.Cli.Options (Command (List, Listen, Load, Yank), Conf (Conf), parser)@@ -40,7 +38,7 @@   Sem (Error Text : r) () ->   Sem r () logError =-  traverseLeft DataLog.error <=< errorToIOFinal+  either DataLog.error pure <=< errorToIOFinal  interpretLog ::   Maybe Bool ->
lib/Helic/Cli/Options.hs view
@@ -3,6 +3,7 @@ -- |CLI Options, Internal module Helic.Cli.Options where +import Exon (exon) import Options.Applicative (   CommandFields,   Mod,@@ -23,6 +24,7 @@   ) import Options.Applicative.Types (readerAsk) import Path (Abs, File, Path, parseAbsFile)+import Prelude hiding (Mod)  import Helic.Data.ListConfig (ListConfig (ListConfig)) import Helic.Data.LoadConfig (LoadConfig (LoadConfig))
lib/Helic/Config/File.hs view
@@ -4,10 +4,10 @@ module Helic.Config.File where  import Data.Yaml (decodeFileEither, prettyPrintParseException)+import Exon (exon) import Path (Abs, File, Path, Rel, absfile, relfile, toFilePath, (</>)) import Path.IO (XdgDirectory (XdgConfig), doesFileExist, getXdgDir) import qualified Polysemy.Log as Log-import Polysemy.Log (Log)  import Helic.Data.Config (Config) @@ -17,7 +17,7 @@   Sem r Config parseFileConfig (toFilePath -> path) = do   Log.debug [exon|Reading config file #{toText path}|]-  fromEither =<< mapLeft formatError <$> embed (decodeFileEither path)+  fromEither =<< first formatError <$> embed (decodeFileEither path)   where     formatError exc =       toText [exon|invalid config file: #{prettyPrintParseException exc}|]
lib/Helic/Data/AgentId.hs view
@@ -1,10 +1,13 @@ {-# options_haddock prune #-}+ -- |AgentId Data Type, Internal module Helic.Data.AgentId where +import Polysemy.Time.Json (json)+ newtype AgentId =   AgentId { unAgentId :: Text }   deriving stock (Eq, Show)   deriving newtype (IsString) -defaultJson ''AgentId+json ''AgentId
lib/Helic/Data/Config.hs view
@@ -1,7 +1,10 @@ {-# options_haddock prune #-}+ -- |Config Data Type, Internal module Helic.Data.Config where +import Polysemy.Time.Json (json)+ import Helic.Data.NetConfig (NetConfig) import Helic.Data.TmuxConfig (TmuxConfig) @@ -16,4 +19,4 @@   deriving stock (Eq, Show, Generic)   deriving anyclass (Default) -defaultJson ''Config+json ''Config
lib/Helic/Data/Event.hs view
@@ -1,9 +1,10 @@--- |Event Data Type+-- |The data type 'Event' consists of a yank text and metadata identifying its source and time. module Helic.Data.Event where  import qualified Chronos import Polysemy.Chronos (ChronosTime) import qualified Polysemy.Time as Time+import Polysemy.Time.Json (json)  import Helic.Data.AgentId (AgentId) import Helic.Data.InstanceName (InstanceName)@@ -22,7 +23,7 @@   }   deriving stock (Eq, Show) -defaultJson ''Event+json ''Event  -- |Construct an event for the current host and time. now ::
lib/Helic/Data/GtkState.hs view
@@ -1,4 +1,5 @@ {-# options_haddock prune #-}+ -- |GtkState Data Type, Internal module Helic.Data.GtkState where 
lib/Helic/Data/Host.hs view
@@ -1,10 +1,13 @@ {-# options_haddock prune #-}+ -- |Host Data Type, Internal module Helic.Data.Host where +import Polysemy.Time.Json (json)+ newtype Host =   Host { unHost :: Text }   deriving stock (Eq, Show)   deriving newtype (IsString) -defaultJson ''Host+json ''Host
lib/Helic/Data/InstanceName.hs view
@@ -1,10 +1,13 @@ {-# options_haddock prune #-}+ -- |InstanceName Data Type, Internal module Helic.Data.InstanceName where +import Polysemy.Time.Json (json)+ newtype InstanceName =   InstanceName { unInstanceName :: Text }   deriving stock (Eq, Show, Generic)   deriving newtype (IsString) -defaultJson ''InstanceName+json ''InstanceName
lib/Helic/Data/NetConfig.hs view
@@ -1,7 +1,10 @@ {-# options_haddock prune #-}+ -- |NetConfig Data Type, Internal module Helic.Data.NetConfig where +import Polysemy.Time.Json (json)+ import Helic.Data.Host (Host)  newtype Timeout =@@ -9,7 +12,7 @@   deriving stock (Eq, Show, Generic)   deriving newtype (Num, Real, Enum, Integral, Ord) -defaultJson ''Timeout+json ''Timeout  data NetConfig =   NetConfig {@@ -20,4 +23,4 @@   deriving stock (Eq, Show, Generic)   deriving anyclass (Default) -defaultJson ''NetConfig+json ''NetConfig
lib/Helic/Data/Selection.hs view
@@ -1,15 +1,20 @@-{-# options_haddock prune #-}--- |Selection Data Type, Internal+-- |The data type 'Selection' enumerates the different types of basic clipboards that X11 operates on.+-- Internal. module Helic.Data.Selection where +-- |This type enumerates the different types of basic clipboards that X11 operates on. data Selection =+  -- |Usually the target of explicit copy commands (ctrl-c).   Clipboard   |+  -- |Stores the cursor selection.   Primary   |+  -- |Only used in exotic situations.   Secondary-  deriving stock (Eq, Show)+  deriving stock (Eq, Show, Ord, Enum, Bounded) +-- |Convert a 'Selection' into the string that X11 uses to identify it. toXString :: Selection -> Text toXString = \case   Clipboard -> "CLIPBOARD"
lib/Helic/Data/TmuxConfig.hs view
@@ -1,8 +1,10 @@ {-# options_haddock prune #-}+ -- |TmuxConfig Data Type, Internal module Helic.Data.TmuxConfig where  import Path (Abs, File, Path)+import Polysemy.Time.Json (json)  data TmuxConfig =   TmuxConfig {@@ -12,4 +14,4 @@   deriving stock (Eq, Show, Generic)   deriving anyclass (Default) -defaultJson ''TmuxConfig+json ''TmuxConfig
lib/Helic/Effect/Agent.hs view
@@ -1,9 +1,7 @@ {-# options_haddock prune #-}--- |The Agent Effect-module Helic.Effect.Agent where -import GHC.TypeLits (KnownSymbol, symbolVal)-import Polysemy.Tagged (Tagged)+-- |The 'Agent' effect abstracts a clipboard synchronization target.+module Helic.Effect.Agent where  import Helic.Data.AgentId (AgentId (AgentId)) import Helic.Data.Event (Event)
lib/Helic/Effect/Client.hs view
@@ -1,8 +1,6 @@ -- |Client Effect, Internal module Helic.Effect.Client where -import Polysemy (makeSem_)- import Helic.Data.Event (Event)  -- |An abstraction of the API, used by the @list@ command.
+ lib/Helic/Effect/Gtk.hs view
@@ -0,0 +1,13 @@+{-# options_haddock prune #-}++-- |The effect 'Gtk' is a utility for running the GTK main loop in a resource-safe manner.+module Helic.Effect.Gtk where++-- |This effect is a utility for running the GTK main loop in a resource-safe manner.+data Gtk s :: Effect where+  -- |Run the Gtk main loop, blocking.+  Main :: Gtk s m ()+  -- |Return the default resource, usually a 'GI.Gdk.Display'.+  Resource :: Gtk s m s++makeSem ''Gtk
+ lib/Helic/Effect/GtkClipboard.hs view
@@ -0,0 +1,18 @@+{-# options_haddock prune #-}++-- |The effect 'GtkClipboard' allows an app to read from, write to, and subscribe to events from a clipboard.+module Helic.Effect.GtkClipboard where++import Helic.Data.Selection (Selection)++-- |This effect 'GtkClipboard' allows an app to read from, write to, and subscribe to events from a clipboard.+-- It is intended to be scoped with a GTK display by 'Helic.interpretWithGtk'.+data GtkClipboard :: Effect where+  -- |Fetch the text content of the X11 clipboard identified by the argument.+  Read :: Selection -> GtkClipboard m (Maybe Text)+  -- |Set the text content of the X11 clipboard identified by the first argument.+  Write :: Selection -> Text -> GtkClipboard m ()+  -- |Listen to clipboard events and invoke the callback.+  Events :: (Selection -> Text -> m ()) -> GtkClipboard m ()++makeSem ''GtkClipboard
+ lib/Helic/Effect/GtkMain.hs view
@@ -0,0 +1,23 @@+{-# options_haddock prune #-}++-- |An effect for concurrently controlling the lifecycle of a GTK main loop and accessing its resource.+module Helic.Effect.GtkMain where++-- |This effect is a communication bridge between 'Helic.Gtk' and GTK functionality effects like 'Helic.GtkClipboard'.+-- It does not directly interact with the GTK API, but allows a scope to ensure that the GTK main loop is running and to+-- access its resource (usually a display handle).+data GtkMain (s :: Type) :: Effect where+  -- |If a resource is currently available, return it.+  -- Otherwise, execute the supplied action.+  -- Should be used to bracket 'Request'.+  Access :: m s -> GtkMain s m s+  -- |Trigger the execution of the GTK main loop, then wait for its resource to be available.+  -- If it does not, execute the supplied action.+  Request :: m s -> GtkMain s m s+  -- |Bracket an action that runs the GTK main loop by clearing the resource, running the supplied action, then clearing+  -- the resource again and waiting for the next request.+  Run :: m a -> GtkMain s m a+  -- |Store the main loop resource in the state to mark the loop as running.+  Running :: s -> GtkMain s m ()++makeSem ''GtkMain
lib/Helic/Effect/History.hs view
@@ -1,7 +1,6 @@ -- |History Effect, Internal module Helic.Effect.History where -import Polysemy (makeSem_) import Helic.Data.Event (Event)  -- |The core actions of the 'Event' history.
lib/Helic/Gtk.hs view
@@ -1,44 +1,45 @@-{-# options_haddock prune #-}---- |GTK Helpers, Internal+-- |General GTK API adapter functions.+-- Internal. module Helic.Gtk where  import qualified Control.Exception as Base+import Exon (exon) import qualified GI.GLib as Glib+import qualified GI.Gdk as GiGdk import qualified GI.Gdk as Gdk import GI.Gdk (Display)+import qualified GI.Gtk as GiGtk import qualified GI.Gtk as GI-import Polysemy.Final (embedFinal, withWeavingToFinal)-import Polysemy.Log (Log)+import Polysemy.Final (withWeavingToFinal)+import qualified Polysemy.Log as Log -import qualified Helic.Data.GtkState as GtkState-import Helic.Data.GtkState (GtkState) import qualified Helic.Data.Selection as Selection-import Helic.Data.Selection (Selection (Clipboard, Primary, Secondary))+import Helic.Data.Selection (Selection)+import Helic.Stop (tryStop) +-- |Safe wrapper around calls to ght GTK API.+-- This schedules an 'IO' action for execution on the GTK main loop thread, which is crucial for some actions to avoid+-- horrible crashes.+-- Since this results in asynchronous execution, an 'MVar' is used to extract the result.+-- Catches all exception and converts them to 'Stop'. gtkUi ::-  Member (Embed IO) r =>+  Members [Stop Text, Embed IO] r =>+  Text ->   IO a ->   Sem r a-gtkUi ma = do-  result <- newEmptyMVar-  _ <- Gdk.threadsAddIdle Glib.PRIORITY_DEFAULT do-    a <- ma-    False <$ putMVar result a-  takeMVar result--gtkUiSem ::-  Member (Final IO) r =>-  Sem r a ->-  Sem r a-gtkUiSem ma = do-  withWeavingToFinal \ s wv _ -> do-    result <- newEmptyMVar-    void $ Gdk.threadsAddIdle Glib.PRIORITY_DEFAULT do-      wv ((embedFinal . putMVar result =<< ma) <$ s)-      pure False-    (<$ s) <$> takeMVar result+gtkUi desc ma = do+  result <- embed newEmptyMVar+  let+    recovering :: IO x -> IO x+    recovering =+      flip Base.onException (putMVar result Nothing)+  _ <- tryStop $ recovering $ Gdk.threadsAddIdle Glib.PRIORITY_DEFAULT do+    putMVar result . Just =<< recovering ma+    pure False+  stopNote [exon|Gtk ui thread computation '#{desc}' failed|] =<< embed (takeMVar result) +-- |Accesses a clipboard by creating the appropriate X11 atom structure.+-- Does not catch exceptions. unsafeGtkClipboard ::   MonadIO m =>   Display ->@@ -48,24 +49,17 @@   selection <- Gdk.atomIntern (Selection.toXString name) False   GI.clipboardGetForDisplay display selection +-- |Return a GTK clipboard, converting all exceptions to 'Stop'. gtkClipboard ::-  Member (Embed IO) r =>+  Members [Stop Text, Embed IO] r =>   Display ->   Selection ->-  Sem r (Either Text GI.Clipboard)+  Sem r GI.Clipboard gtkClipboard display name =-  tryAny (unsafeGtkClipboard display name)--unsafeSubscribe ::-  MonadIO m =>-  GI.Clipboard ->-  (Either Text Text -> IO ()) ->-  m ()-unsafeSubscribe clipboard handle =-  void $ GI.onClipboardOwnerChange clipboard \ _ -> do-    Base.catch @SomeException (GI.clipboardRequestText clipboard (const (traverse_ (handle . Right)))) \ e ->-      handle (Left (show e))+  tryStop (unsafeGtkClipboard display name) +-- |Request the text contents of a GTK clipboard, catching all exceptions, and passing the result to a handler.+-- If the clipboard is empty or an exception was thrown, the value passed to the handler is 'Left', otherwise 'Right'. clipboardRequest ::   GI.Clipboard ->   (Either Text Text -> IO ()) ->@@ -77,48 +71,32 @@     run =       GI.clipboardRequestText clipboard (const (handle . maybeToRight "no clipboard text")) -subscribe ::+-- |Registers a callback for the "owner change" event of a GTK clipboard, which is triggered whenever a client updates+-- the text.+-- The callback then fetches the current text and passes it to the supplied handler as 'Right', or a 'Left' if an+-- exception was thrown.+subscribeWith ::   Member (Final IO) r =>   GI.Clipboard ->   (Either Text Text -> Sem r ()) ->   Sem r ()-subscribe clipboard handle =+subscribeWith clipboard handle =   withWeavingToFinal \ s wv _ -> do     let lower ma = void (wv (ma <$ s))     s <$ GI.onClipboardOwnerChange clipboard \ _ ->       clipboardRequest clipboard (lower . handle) -clipboardFor ::-  Member (Reader GtkState) r =>-  Selection ->-  Sem r GI.Clipboard-clipboardFor = \case-  Clipboard -> asks GtkState.clipboard-  Primary -> asks GtkState.primary-  Secondary -> asks GtkState.secondary--unsafeGetClipboard ::-  MonadIO m =>-  GI.Clipboard ->-  m (Maybe Text)-unsafeGetClipboard clipboard =-  GI.clipboardWaitForText clipboard--getClipboard ::-  Members [Log, Embed IO] r =>+-- |Safely request the text contents of a clipboard by scheduling an action on the UI thread and converting exceptions+-- into 'Stop'.+readClipboard ::+  Members [Log, Stop Text, Embed IO] r =>   GI.Clipboard ->   Sem r (Maybe Text)-getClipboard clipboard =-  gtkUi (unsafeGetClipboard clipboard)--getClipboardFor ::-  Members [Reader GtkState, Log, Embed IO] r =>-  Selection ->-  Sem r (Maybe Text)-getClipboardFor sel = do-  cb <- clipboardFor sel-  getClipboard cb+readClipboard =+  gtkUi "readClipboard" . GI.clipboardWaitForText +-- |Update the text contents of a clipboard.+-- Does not catch exceptions. unsafeSetClipboard ::   MonadIO m =>   GI.Clipboard ->@@ -127,31 +105,62 @@ unsafeSetClipboard clipboard text =   GI.clipboardSetText clipboard text (-1) -setClipboard ::-  Member (Embed IO) r =>+-- |Safely update the text contents of a clipboard by scheduling an action on the UI thread and converting exceptions+-- into 'Stop'.+writeClipboard ::+  Members [Stop Text, Embed IO] r =>   GI.Clipboard ->   Text ->   Sem r ()-setClipboard clipboard text =-  gtkUi (unsafeSetClipboard clipboard text)+writeClipboard clipboard =+  gtkUi "writeClipboard" . unsafeSetClipboard clipboard -setClipboardFor ::-  Members [Reader GtkState, Log, Embed IO, Final IO] r =>+-- |Obtain the default GTK display, converting exceptions into 'Stop'.+getDisplay ::+  Members [Stop Text, Embed IO] r =>+  Sem r Display+getDisplay =+  stopNote "couldn't get a GTK display" =<< tryStop GiGdk.displayGetDefault++-- |Obtain a GTK clipboard handle for a specific 'Selection'+getClipboard ::+  Members [Reader Display, Stop Text, Embed IO] r =>   Selection ->-  Text ->+  Sem r GiGtk.Clipboard+getClipboard selection = do+  display <- ask+  gtkClipboard display selection++-- |Listen to clipboard events for a specific source, like "primary selection", and pass them to the callback.+subscribeToClipboard ::+  Members [Reader Display, Log, Stop Text, Embed IO, Final IO] r =>+  (Selection -> Text -> Sem r ()) ->+  Selection ->   Sem r ()-setClipboardFor sel text = do-  cb <- clipboardFor sel-  setClipboard cb text+subscribeToClipboard f selection = do+  cb <- getClipboard selection+  subscribeWith cb \case+    Right t ->+      f selection t+    Left e ->+      Log.warn [exon|GTK: #{e}|] -syncXClipboard ::-  Members [Reader GtkState, Log, Embed IO, Final IO] r =>-  Text ->+-- |Fetch the text contents of the GTK clipboard corresponding to the specified X11 selection, converting exceptions+-- into 'Stop'.+clipboardText ::+  Members [Reader Display, Log, Stop Text, Embed IO] r =>   Selection ->+  Sem r (Maybe Text)+clipboardText =+  readClipboard <=< getClipboard++-- |Update the text contents of the GTK clipboard corresponding to the specified X11 selection, converting exceptions+-- into 'Stop'.+setClipboardText ::+  Members [Reader Display, Log, Stop Text, Embed IO, Final IO] r =>+  Selection ->+  Text ->   Sem r ()-syncXClipboard text = \case-  Clipboard ->-    unit-  _ -> do-    cb <- asks GtkState.clipboard-    setClipboard cb text+setClipboardText sel text = do+  cb <- getClipboard sel+  writeClipboard cb text
+ lib/Helic/GtkClipboard.hs view
@@ -0,0 +1,24 @@+-- |Utilities for 'GtkClipboard'.+-- Internal.+module Helic.GtkClipboard where++import Exon (exon)+import qualified Polysemy.Log as Log++import Helic.Data.XClipboardEvent (XClipboardEvent (XClipboardEvent))+import qualified Helic.Effect.GtkClipboard as GtkClipboard+import Helic.Effect.GtkClipboard (GtkClipboard)+import Helic.Interpreter.GtkClipboard (withGtkClipboard)++-- |Registers a callback with GTK's clipboard event system that converts each update into an 'XClipboardEvent' published+-- through 'Events'.+subscribeEvents ::+  Members [Scoped s GtkClipboard !! Text, Events res XClipboardEvent, Log] r =>+  Sem r ()+subscribeEvents =+  resuming failure $ withGtkClipboard do+    GtkClipboard.events \ selection t ->+      publish (XClipboardEvent t selection)+  where+    failure e =+      Log.error [exon|Subscribing to Gtk events failed: #{e}|]
+ lib/Helic/GtkMain.hs view
@@ -0,0 +1,57 @@+-- |API for the GTK main loop.+-- Internal.+module Helic.GtkMain where++import Exon (exon)+import Polysemy.Conc (withAsync_)+import qualified Polysemy.Log as Log++import qualified Helic.Effect.Gtk as Gtk+import Helic.Effect.Gtk (Gtk)+import qualified Helic.Effect.GtkMain as GtkMain+import Helic.Effect.GtkMain (GtkMain)++-- |Run the GTK main loop.+-- Before that, initialize the GTK client environment, store the default display in the state of 'GtkMain', and execute+-- the user-supplied initialization action.+gtkMain ::+  Members [Scoped resource (Gtk s), GtkMain s, Resource] r =>+  Sem r () ->+  Sem r ()+gtkMain onInit =+  scoped do+    GtkMain.running =<< Gtk.resource+    raise onInit+    Gtk.main++-- |Run the GTK main loop in an infinite loop, recovering from errors by logging them.+-- After the loop has failed or was terminated, the default implementation waits for 10 seconds before restarting it,+-- but can be forced to start when a consumer tries to use it.+loopGtkMain ::+  Members [Scoped resource (Gtk s) !! Text, GtkMain s, Resource, Log] r =>+  Sem r () ->+  Sem r ()+loopGtkMain onInit =+  forever do+    GtkMain.run do+      gtkMain (raise onInit) !! \ e ->+        Log.error [exon|Gtk main loop failed: #{e}|]++-- |Acquire a GTK resource by first examining the value currently stored in 'GtkMain', and if there is none, requesting+-- the GTK main loop to be started.+gtkResource ::+  Members [GtkMain s, Log, Stop Text] r =>+  Sem r s+gtkResource =+  GtkMain.access do+    Log.info "Gtk main loop inactive, requesting restart"+    GtkMain.request (stop "Gtk main loop didn't start") <* Log.info "Gtk main loop started"++-- |Run 'loopGtkMain' in a thread.+gtkMainLoop ::+  Members [Scoped resource (Gtk s) !! Text, GtkMain s, Log, Race, Resource, Async] r =>+  Sem r () ->+  Sem r a ->+  Sem r a+gtkMainLoop onInit =+  withAsync_ (loopGtkMain onInit)
+ lib/Helic/Interpreter.hs view
@@ -0,0 +1,16 @@+-- |Interpretation combinators.+-- Internal.+module Helic.Interpreter where++import Polysemy.Internal.CustomErrors (FirstOrder)++-- |Flipped version of 'interpret'.+interpreting ::+  ∀ e r a .+  FirstOrder e "interpret" =>+  Sem (e : r) a ->+  (∀ r0 x . e (Sem r0) x -> Sem r x) ->+  Sem r a+interpreting s h =+  interpret h s+{-# inline interpreting #-}
lib/Helic/Interpreter/Agent.hs view
@@ -1,10 +1,9 @@ -- |Simple Agent Interpreter, Internal module Helic.Interpreter.Agent where -import Polysemy.Tagged (Tagged, untag)- import Helic.Data.Event (Event) import Helic.Effect.Agent (Agent (Update))+import Helic.Interpreter (interpreting)  -- |Interpret 'Agent' with an action. interpretAgent ::
lib/Helic/Interpreter/AgentNet.hs view
@@ -1,15 +1,13 @@ -- |Agent Interpreter for Network, Internal module Helic.Interpreter.AgentNet where -import Polysemy.Conc (Interrupt) import Polysemy.Http (Manager) import qualified Polysemy.Log as Log-import Polysemy.Log (Log)-import Polysemy.Tagged (Tagged, untag)  import Helic.Data.Event (Event (source)) import Helic.Data.NetConfig (NetConfig (NetConfig)) import Helic.Effect.Agent (Agent (Update), AgentNet, agentIdNet)+import Helic.Interpreter (interpreting) import Helic.Net.Client (sendTo)  -- |Interpret 'Agent' using remote hosts as targets.@@ -24,4 +22,4 @@     Update e -> do       NetConfig _ timeout hosts <- ask       for_ (fold hosts) \ host ->-        traverseLeft Log.debug =<< runError (sendTo timeout host e { source = agentIdNet })+        either Log.debug pure =<< runError (sendTo timeout host e { source = agentIdNet })
lib/Helic/Interpreter/AgentTmux.hs view
@@ -1,17 +1,19 @@ -- |Agent Interpreter for Tmux, Internal module Helic.Interpreter.AgentTmux where +import Exon (exon)+import qualified Log import Path (Abs, File, Path, toFilePath)-import Polysemy.Log (Log)-import Polysemy.Process.Interpreter.ProcessIOE (interpretProcessIOE)-import Polysemy.Tagged (Tagged, untag) import qualified System.Process.Typed as Process import System.Process.Typed (ProcessConfig)  import qualified Helic.Data.TmuxConfig as TmuxConfig import Helic.Data.TmuxConfig (TmuxConfig) import Helic.Effect.Agent (Agent (Update), AgentTmux)+import Helic.Interpreter (interpreting) import Helic.Tmux (sendToTmux)+import Polysemy.Process (interpretProcessByteStringNative)+import Polysemy.Process.Data.ProcessError (ProcessError)  -- |Process definition for running `tmux load-buffer -`. tmuxProc ::@@ -36,8 +38,9 @@   InterpreterFor (Tagged AgentTmux Agent) r interpretAgentTmux sem = do   exe <- asks TmuxConfig.exe-  interpretProcessIOE True 64 (tmuxProc exe) $+  interpretProcessByteStringNative True 64 (tmuxProc exe) $     interpreting (raiseUnder (untag sem)) \case-      Update e ->+      Update event ->         whenM enableTmux do-          sendToTmux e+          sendToTmux event !! \ (e :: ProcessError) ->+            Log.error [exon|Sending to tmux: #{show e}|]
lib/Helic/Interpreter/AgentX.hs view
@@ -1,10 +1,11 @@ -- |Agent Interpreter for X11, Internal module Helic.Interpreter.AgentX where +import qualified Conc+import Conc (withAsync_)+import Exon (exon) import Polysemy.Chronos (ChronosTime)-import qualified Polysemy.Conc as Conc-import Polysemy.Conc (EventConsumer, Events, withAsync_)-import Polysemy.Tagged (Tagged, untag)+import qualified Polysemy.Log as Log  import qualified Helic.Data.Event as Event import Helic.Data.Event (Event (Event))@@ -13,18 +14,20 @@ import Helic.Effect.Agent (Agent (Update), AgentX, agentIdX) import qualified Helic.Effect.XClipboard as XClipboard import Helic.Effect.XClipboard (XClipboard)+import Helic.Interpreter (interpreting)  -- |Listen for 'XClipboardEvent's and publish them as 'Event's. transformXEvents ::-  Member (Reader InstanceName) r =>-  Members [EventConsumer xr XClipboardEvent, Events er Event, XClipboard, ChronosTime, Resource, Race, Async] r =>+  Members [EventConsumer xr XClipboardEvent, Reader InstanceName] r =>+  Members [Events er Event, XClipboard !! Text, Log, ChronosTime, Resource, Race, Async] r =>   Sem r a ->   Sem r a transformXEvents =   withAsync_ do     Conc.subscribeLoop \case       XClipboardEvent text selection -> do-        XClipboard.sync text selection+        XClipboard.sync text selection !! \ e ->+          Log.error [exon|Syncing the X clipboard failed: #{e}|]         ev <- Event.now agentIdX text         Conc.publish ev @@ -32,11 +35,12 @@ -- This interpreter also runs a thread that converts events generated by the 'XClipboard' interpreter -- ('XClipboardEvent') to the main 'Event' type. interpretAgentX ::-  Members [EventConsumer xr XClipboardEvent, Events er Event, Reader InstanceName, XClipboard, ChronosTime] r =>-  Members [Events xr XClipboardEvent, Error Text, Race, Resource, Async, Embed IO, Final IO] r =>+  Members [EventConsumer xr XClipboardEvent, Events er Event, Reader InstanceName, XClipboard !! Text, ChronosTime] r =>+  Members [Events xr XClipboardEvent, Log, Error Text, Race, Resource, Async, Embed IO, Final IO] r =>   InterpreterFor (Tagged AgentX Agent) r interpretAgentX sem =   transformXEvents $   interpreting (untag sem) \case     Update Event {..} ->-      XClipboard.set content+      XClipboard.set content !! \ e ->+        Log.error [exon|Setting the X clipboard failed: #{e}|]
lib/Helic/Interpreter/Client.hs view
@@ -3,7 +3,6 @@  import Polysemy.Http (Manager) import qualified Polysemy.Http.Effect.Manager as Manager-import Polysemy.Log (Log) import Servant.Client (mkClientEnv, runClientM)  import Helic.Data.Event (Event)@@ -28,7 +27,7 @@       runError (sendTo timeout host event)     Load event -> do       env <- mkClientEnv <$> Manager.get <*> localhostUrl-      result <- mapLeft show <$> embed (runClientM (Api.load event) env)+      result <- first show <$> embed (runClientM (Api.load event) env)       pure (result >>= maybeToRight "There is no event for that index")  -- |Interpret 'Client' with a constant list of 'Event's and no capability to yank.
+ lib/Helic/Interpreter/Gtk.hs view
@@ -0,0 +1,45 @@+-- |Native interpreter for 'Gtk'.+-- Internal.+module Helic.Interpreter.Gtk where++import qualified GI.Gdk as GiGdk+import GI.Gdk (Display)+import qualified GI.Gtk as GiGtk+import Polysemy.Conc (interpretScopedResumable)+import qualified Polysemy.Log as Log++import qualified Helic.Effect.Gtk as Gtk+import Helic.Effect.Gtk (Gtk)+import Helic.Gtk (getDisplay)+import Helic.Stop (tryStop)++-- |Initialize GTK, run the scoped action, then tear down the GTK environment.+bracketGtk ::+  Members [Resource, Log, Embed IO] r =>+  (Display -> Sem (Stop Text : r) a) ->+  Sem (Stop Text : r) a+bracketGtk =+  bracket acquire release+  where+    acquire = do+      unlessM (fst <$> tryStop (GiGtk.initCheck Nothing)) do+        stop "GTK intialization failed"+      getDisplay+    release display = do+      Log.debug "Quitting the GTK main loop"+      ignoreException do+        GiGdk.displayFlush display+        GiGdk.displayClose display+      tryStop GiGtk.mainQuit++-- |Interpret 'Gtk' natively, using the "GI.Gtk" and "Gi.Gdk" libraries.+-- This uses 'Scoped' to bracket the initialization and termination of the GTK environment.+interpretGtk ::+  Members [Resource, Log, Embed IO] r =>+  InterpreterFor (Scoped Display (Gtk Display) !! Text) r+interpretGtk =+  interpretScopedResumable bracketGtk \ display -> \case+    Gtk.Main ->+      GiGtk.main+    Gtk.Resource ->+      pure display
+ lib/Helic/Interpreter/GtkClipboard.hs view
@@ -0,0 +1,44 @@+-- |Native interpreters for 'GtkClipboard', for scoped interpretation with 'interpretWithGtk'.+module Helic.Interpreter.GtkClipboard where++import GI.Gdk (Display)++import qualified Helic.Effect.GtkClipboard as GtkClipboard+import Helic.Effect.GtkClipboard (GtkClipboard)+import Helic.Effect.GtkMain (GtkMain)+import Helic.Gtk (clipboardText, setClipboardText, subscribeToClipboard)+import Helic.Interpreter.GtkMain (interpretWithGtk)++-- |Specialization of 'scoped' to 'GtkClipboard' for syntactic sugar.+withGtkClipboard ::+  Member (Scoped resource GtkClipboard) r =>+  InterpreterFor GtkClipboard r+withGtkClipboard =+  scoped++-- |This handler for 'GtkClipboard' depends on a 'Display', which should optimally be provided by a 'Scoped'+-- interpreter to ensure safe acquisition of the resource.+-- The effect then needs to be scoped using 'withGtkClipboard'.+-- The default implementation for this purpose is 'interpretWithGtk'.+handleGtkClipboard ::+  Members [Log, Embed IO, Final IO] r =>+  Display ->+  GtkClipboard (Sem r0) a ->+  Tactical effect (Sem r0) (Stop Text : r) a+handleGtkClipboard display = \case+  GtkClipboard.Read selection ->+    pureT =<< runReader display (clipboardText selection)+  GtkClipboard.Write selection text ->+    pureT =<< runReader display (setClipboardText selection text)+  GtkClipboard.Events f -> do+    let f' s t = void (raise (runTSimple (f s t)))+    runReader display do+      for_ @[] [minBound..maxBound] (subscribeToClipboard f')+    pureT ()++-- |Native interpreter for 'GtkClipboard' that requires the effect to be used within a 'withGtkClipboard' region.+interpretGtkClipboard ::+  Members [GtkMain Display, Log, Embed IO, Final IO] r =>+  InterpreterFor (Scoped Display GtkClipboard !! Text) r+interpretGtkClipboard =+  interpretWithGtk handleGtkClipboard
+ lib/Helic/Interpreter/GtkMain.hs view
@@ -0,0 +1,87 @@+{-# options_haddock prune #-}++-- |An interpreter for 'GtkMain' that uses 'MVar's.+-- Internal.+module Helic.Interpreter.GtkMain where++import Polysemy.Conc (interpretScopedResumableH, interpretSync, interpretSyncAs, lock)+import qualified Polysemy.Conc.Sync as Sync++import qualified Helic.Effect.GtkMain as GtkMain+import Helic.Effect.GtkMain (GtkMain)+import Helic.GtkMain (gtkResource)++data GtkLock =+  GtkLock+  deriving stock (Eq, Show)++data StartGtkMain =+  StartGtkMain+  deriving stock (Eq, Show)++newtype GtkResource s =+  GtkResource { unGtkResource :: s }+  deriving stock (Eq, Show)++-- TODO Access needs to be a scope to ensure it must be executed+-- this means that access/request and run/running must be two separate effects++-- |Interpret the GTK main loop communication bridge with 'MVar's.+handleGtkMain ::+  ∀ s wait restart e m r a .+  TimeUnit wait =>+  TimeUnit restart =>+  Members [Resource, Sync GtkLock, Sync StartGtkMain, Sync (GtkResource s)] r =>+  wait ->+  restart ->+  GtkMain s m a ->+  Tactical e m r a+handleGtkMain wait restart = \case+  GtkMain.Access ms -> do+    lock GtkLock do+      Sync.try >>= \case+        Just (GtkResource s) ->+          pureT s+        Nothing ->+          runTSimple ms+  GtkMain.Request ms -> do+    Sync.clear @(GtkResource _)+    Sync.putTry StartGtkMain+    Sync.wait wait >>= \case+      Just (GtkResource s) ->+        pureT s+      Nothing ->+        runTSimple ms+  GtkMain.Run ma -> do+    Sync.clear @StartGtkMain+    Sync.clear @(GtkResource _)+    runTSimple ma <* do+      Sync.clear @(GtkResource _)+      Sync.takeWait @StartGtkMain restart+  GtkMain.Running s ->+    pureT =<< Sync.putBlock (GtkResource s)++-- |Interpret the GTK main loop communication bridge with 'MVar's.+interpretGtkMain ::+  ∀ s wait restart r .+  TimeUnit wait =>+  TimeUnit restart =>+  Members [Resource, Race, Embed IO] r =>+  wait ->+  restart ->+  InterpreterFor (GtkMain s) r+interpretGtkMain wait restart =+  interpretSync .+  interpretSyncAs GtkLock .+  interpretSync @StartGtkMain .+  interpretH (handleGtkMain wait restart) .+  raiseUnder3++-- |Scope an effect that uses a GTK main loop resource by acquiring it via 'GtkMain'.+interpretWithGtk ::+  ∀ e s r .+  Members [GtkMain s, Log] r =>+  (∀ r0 x . s -> e (Sem r0) x -> Tactical e (Sem r0) (Stop Text : r) x) ->+  InterpreterFor (Scoped s e !! Text) r+interpretWithGtk =+  interpretScopedResumableH (=<< gtkResource)
lib/Helic/Interpreter/History.hs view
@@ -6,18 +6,17 @@ import qualified Chronos import qualified Data.Sequence as Seq import Data.Sequence (Seq ((:|>)), (!?), (|>))-import Polysemy.AtomicState (atomicState')+import qualified Data.Text as Text+import Exon (exon) import Polysemy.Chronos (ChronosTime) import qualified Polysemy.Log as Log-import Polysemy.Log (Log)-import Polysemy.Tagged (Tagged, tag) import qualified Polysemy.Time as Time import Polysemy.Time (Seconds (Seconds), convert) import Polysemy.Time.Diff (diff)  import Helic.Data.AgentId (AgentId (AgentId)) import qualified Helic.Data.Event as Event-import Helic.Data.Event (Event (Event, time))+import Helic.Data.Event (Event (Event, content, time)) import Helic.Data.InstanceName (InstanceName) import qualified Helic.Effect.Agent as Agent import Helic.Effect.Agent (Agent, AgentName, AgentNet, AgentTag, AgentTmux, AgentX, Agents, agentIdNet, agentName)@@ -60,6 +59,14 @@     newer (Event _ _ t _) =       diff now t <= convert (Seconds 1) +sanitizeNewlines :: Text -> Text+sanitizeNewlines =+  Text.replace "\r" "\n" . Text.replace "\r\n" "\n"++sanitize :: Event -> Event+sanitize e@Event {content} =+  e { content = sanitizeNewlines content }+ -- |Append an event to the history unless the newest event contains the same text or there was an event within the last -- second that contained the same text, to avoid clobbering due to cycles induced by external programs. appendIfValid ::@@ -67,10 +74,10 @@   Event ->   Seq Event ->   Maybe (Seq Event)-appendIfValid now e = \case+appendIfValid now (sanitize -> e@Event {content}) = \case   Seq.Empty ->     Just (Seq.singleton e)-  _ :|> Event _ _ _ newest | newest == Event.content e ->+  _ :|> Event _ _ _ newest | newest == content ->     Nothing   hist | inRecent now e hist ->     Nothing
lib/Helic/Interpreter/InstanceName.hs view
@@ -1,6 +1,7 @@ -- |Reader Interpreter for InstanceName, Internal module Helic.Interpreter.InstanceName where +import Exon (exon) import Network.HostName (getHostName) import qualified Polysemy.Error as Polysemy 
lib/Helic/Interpreter/XClipboard.hs view
@@ -1,86 +1,28 @@--- |XClipboard Interpreter, Internal+-- |Interpreter for 'XClipboard' using GTK.+-- Internal. module Helic.Interpreter.XClipboard where -import qualified GI.Gdk as Gdk-import qualified GI.Gtk as GI-import qualified Polysemy.Conc as Conc-import Polysemy.Conc (Events, withAsync_)-import qualified Polysemy.Log as Log-import Polysemy.Log (Log)-import Polysemy.Resource (bracket)--import qualified Helic.Data.GtkState as GtkState-import Helic.Data.GtkState (GtkState (GtkState))-import Helic.Data.Selection (Selection (Clipboard, Primary, Secondary))-import Helic.Data.XClipboardEvent (XClipboardEvent (XClipboardEvent))+import Helic.Data.Selection (Selection (Clipboard))+import qualified Helic.Effect.GtkClipboard as GtkClipboard+import Helic.Effect.GtkClipboard (GtkClipboard) import Helic.Effect.XClipboard (XClipboard (Current, Set, Sync))-import qualified Helic.Gtk as Gtk-import Helic.Gtk (getClipboardFor, gtkClipboard, setClipboardFor, syncXClipboard)---- |Execute a GTK main loop in a baackground thread and interpret @'Reader' 'GtkState'@.--- The clipboards stored in the state need the main loop running to work properly.--- The main loop is killed after the interpreted program terminates.-withMainLoop ::-  Members [Log, Error Text, Race, Async, Resource, Embed IO] r =>-  InterpreterFor (Reader GtkState) r-withMainLoop prog = do-  bracket acquire release \ display -> do-    clipboard <- fromEither =<< gtkClipboard display Clipboard-    primary <- fromEither =<< gtkClipboard display Primary-    secondary <- fromEither =<< gtkClipboard display Secondary-    runReader (GtkState clipboard primary secondary display) (withAsync_ GI.main prog)-  where-    acquire = do-      _ <- embed (GI.init Nothing)-      note "couldn't get a GTK display" =<< Gdk.displayGetDefault-    release display = do-      Log.debug [exon|Quitting the GTK main loop|]-      Gdk.displayFlush display-      Gdk.displayClose display-      GI.mainQuit---- |Listen to clipboard events for a specific source, like "primary selection", and publish them via 'Events'.-subscribeToClipboard ::-  Members [Events resource XClipboardEvent, Reader GtkState, Log, Embed IO, Final IO] r =>-  GI.Clipboard ->-  Selection ->-  Sem r ()-subscribeToClipboard clipboard selection =-  Gtk.subscribe clipboard \case-    Right t ->-      Conc.publish (XClipboardEvent t selection)-    Left e ->-      Log.warn [exon|GTK: #{e}|]---- |Listen to clipboard events and publish them via 'Events'.-clipboardEvents ::-  Members [Events resource XClipboardEvent, Reader GtkState, Log, Embed IO, Final IO] r =>-  Sem r ()-clipboardEvents = do-  GtkState {..} <- ask-  subscribeToClipboard clipboard Clipboard-  subscribeToClipboard primary Primary-  subscribeToClipboard secondary Secondary---- |Run a GTK main loop and listen to clipboard events, publishing them via 'Events'.-listenXClipboard ::-  Members [Events resource XClipboardEvent, Log, Error Text, Race, Resource, Async, Embed IO, Final IO] r =>-  InterpreterFor (Reader GtkState) r-listenXClipboard sem =-  withMainLoop do-    clipboardEvents-    sem+import Helic.Interpreter.GtkClipboard (withGtkClipboard)  -- |Interpret 'XClipboard' using a GTK backend.--- This uses the @gi-gtk@ library to access the X11 clipboard.+-- This uses the library @gi-gtk@ to access the X11 clipboard. interpretXClipboardGtk ::-  Members [Reader GtkState, Log, Embed IO, Final IO] r =>-  InterpreterFor XClipboard r+  Members [Scoped resource GtkClipboard !! Text, Log, Embed IO, Final IO] r =>+  InterpreterFor (XClipboard !! Text) r interpretXClipboardGtk = do-  interpret \case+  interpretResumable \case     Current ->-      getClipboardFor Clipboard+      restop $ withGtkClipboard do+        GtkClipboard.read Clipboard     Set text ->-      setClipboardFor Clipboard text-    Sync text selection ->-      syncXClipboard text selection+      restop $ withGtkClipboard do+        (GtkClipboard.write Clipboard text)+    Sync _ Clipboard ->+      unit+    Sync text _ ->+      restop $ withGtkClipboard do+        GtkClipboard.write Clipboard text
lib/Helic/List.hs view
@@ -6,6 +6,7 @@ import Chronos (Datetime (Datetime), SubsecondPrecision (SubsecondPrecisionFixed), builder_HMS, timeToDatetime) import qualified Data.Text as Text import Data.Text.Lazy.Builder (toLazyText)+import Exon (exon) import qualified System.Console.Terminal.Size as TerminalSize import Text.Layout.Table (center, column, expandUntil, fixedCol, left, right, rowG, tableString, titlesH, unicodeRoundS) @@ -76,4 +77,4 @@   Members [Reader ListConfig, Client, Error Text, Embed IO] r =>   Sem r () list =-  putStrLn =<< buildList+  embed . putStrLn =<< buildList
lib/Helic/Listen.hs view
@@ -4,8 +4,9 @@ module Helic.Listen where  import qualified Polysemy.Conc as Conc-import Polysemy.Conc (EventConsumer, Sync, interpretSync, withAsync_)+import Polysemy.Conc (interpretSync, withAsync_) import qualified Polysemy.Conc.Sync as Sync+import Prelude hiding (listen)  import Helic.Data.Event (Event) import qualified Helic.Effect.History as History
lib/Helic/Net/Api.hs view
@@ -1,8 +1,6 @@ -- |HTTP API of the Daemon, Internal module Helic.Net.Api where -import Polysemy.Conc (Interrupt, Sync)-import Polysemy.Log (Log) import Servant (Get, JSON, NoContent (NoContent), PostCreated, PutAccepted, ReqBody, type (:<|>) ((:<|>)), type (:>)) import Servant.Server (Context (EmptyContext), ServerT) @@ -30,7 +28,7 @@ server =   History.get   :<|>-  as NoContent . History.receive+  (NoContent <$) . History.receive   :<|>   History.load 
lib/Helic/Net/Client.hs view
@@ -3,11 +3,11 @@ -- |HTTP Client, Internal module Helic.Net.Client where +import Exon (exon) import qualified Polysemy.Conc as Conc import Polysemy.Http (Manager) import qualified Polysemy.Http.Effect.Manager as Manager import qualified Polysemy.Log as Log-import Polysemy.Log (Log) import Polysemy.Time (MilliSeconds (MilliSeconds)) import Servant (NoContent, type (:<|>) ((:<|>))) import Servant.Client (BaseUrl, ClientM, client, mkClientEnv, parseBaseUrl, runClientM)@@ -39,7 +39,7 @@     env =       mkClientEnv mgr url     req =-      fmap (mapLeft show) <$> tryAny (runClientM (yank event) env)+      fmap (first show) <$> tryAny (runClientM (yank event) env)   void . fromEither =<< fromEither =<< Conc.timeoutAs_ (Left "timed out") timeout req  localhost ::
lib/Helic/Net/Server.hs view
@@ -1,7 +1,10 @@ {-# options_haddock prune #-}+ -- |HTTP Server Plumbing, Internal module Helic.Net.Server where +import Control.Monad.Trans.Except (ExceptT (ExceptT))+import Exon (exon) import qualified Network.Wai.Handler.Warp as Warp import Network.Wai.Handler.Warp (   defaultSettings,@@ -12,12 +15,10 @@   setPort,   ) import Network.Wai.Middleware.RequestLogger (logStdout)-import Polysemy.Conc (Interrupt, Sync) import qualified Polysemy.Conc.Effect.Interrupt as Interrupt-import qualified Polysemy.Conc.Effect.Sync as Sync+import qualified Polysemy.Conc.Sync as Sync import Polysemy.Final (withWeavingToFinal) import qualified Polysemy.Log as Log-import Polysemy.Log (Log) import Servant (   Context,   DefaultErrorFormatters,
− lib/Helic/Prelude.hs
@@ -1,189 +0,0 @@-{-# language NoImplicitPrelude #-}-{-# options_haddock prune, hide #-}--- |Prelude, Internal--module Helic.Prelude (-  module Data.Aeson,-  module Data.Aeson.TH,-  module Data.Default,-  module Data.Either.Combinators,-  module Data.Foldable,-  module Data.Kind,-  module Exon,-  module GHC.Err,-  module GHC.TypeLits,-  module Helic.Prelude,-  module Helic.Prelude.Debug,-  module Polysemy,-  module Polysemy.Async,-  module Polysemy.AtomicState,-  module Polysemy.Conc,-  module Polysemy.Error,-  module Polysemy.Internal.Tactics,-  module Polysemy.Reader,-  module Polysemy.Resource,-  module Polysemy.State,-  module Relude,-) where--import Control.Exception (try)-import qualified Data.Aeson as Aeson-import Data.Aeson (FromJSON (parseJSON), SumEncoding (UntaggedValue), ToJSON (toJSON), Value, camelTo2)-import Data.Aeson.TH (deriveFromJSON, deriveJSON)-import Data.Default (Default (def))-import Data.Either.Combinators (mapLeft)-import Data.Foldable (foldl, traverse_)-import Data.Kind (Type)-import Exon (exon)-import GHC.Err (undefined)-import GHC.TypeLits (Symbol)-import qualified Language.Haskell.TH.Syntax as TH-import Polysemy (-  Effect,-  EffectRow,-  Embed,-  Final,-  InterpreterFor,-  InterpretersFor,-  Member,-  Members,-  Sem,-  WithTactics,-  bindT,-  embed,-  embedToFinal,-  interpret,-  interpretH,-  makeSem,-  pureT,-  raise,-  raise2Under,-  raise3Under,-  raiseUnder,-  raiseUnder2,-  raiseUnder3,-  reinterpret,-  runFinal,-  runT,-  )-import Polysemy.Async (Async, async, asyncToIOFinal, await, sequenceConcurrently)-import Polysemy.AtomicState (AtomicState, atomicGet, atomicGets, atomicModify', atomicPut, runAtomicStateTVar)-import Polysemy.Conc (Race)-import Polysemy.Error (Error, fromEither, fromExceptionVia, mapError, note, runError, throw)-import Polysemy.Internal.CustomErrors (FirstOrder)-import Polysemy.Internal.Kind (Append)-import Polysemy.Internal.Tactics (liftT)-import Polysemy.Reader (Reader, ask, asks, runReader)-import Polysemy.Resource (Resource, resourceToIOFinal, runResource)-import Polysemy.State (State, evalState, get, gets, modify, modify', put, runState)-import Relude hiding (-  Reader,-  State,-  Type,-  ask,-  asks,-  evalState,-  filterM,-  get,-  gets,-  hoistEither,-  modify,-  modify',-  put,-  readFile,-  runReader,-  runState,-  state,-  trace,-  traceShow,-  undefined,-  )--import Helic.Prelude.Debug (dbg, dbgs, dbgsWith, dbgs_, tr, trs, trs')--unit ::-  Applicative f =>-  f ()-unit =-  pure ()-{-# inline unit #-}--tryAny ::-  Member (Embed IO) r =>-  IO a ->-  Sem r (Either Text a)-tryAny =-  embed . fmap (mapLeft show) . try @SomeException-{-# inline tryAny #-}--catchAny ::-  Member (Embed IO) r =>-  IO a ->-  (Text -> Sem r a) ->-  Sem r a-catchAny ma handle =-  either handle pure =<< tryAny ma-{-# inline catchAny #-}--basicOptions :: Aeson.Options-basicOptions =-  Aeson.defaultOptions {-    Aeson.fieldLabelModifier = dropWhile ('_' ==)-  }--jsonOptions :: Aeson.Options-jsonOptions =-  basicOptions {-    Aeson.unwrapUnaryRecords = True-  }--untaggedOptions :: Aeson.Options-untaggedOptions =-  jsonOptions {-    Aeson.sumEncoding = UntaggedValue-  }--defaultJson :: TH.Name -> TH.Q [TH.Dec]-defaultJson =-  deriveJSON jsonOptions--lowerMinusJson :: TH.Name -> TH.Q [TH.Dec]-lowerMinusJson =-  deriveJSON jsonOptions {-    Aeson.constructorTagModifier = camelTo2 '-'-  }--unaryRecordJson :: TH.Name -> TH.Q [TH.Dec]-unaryRecordJson =-  deriveJSON basicOptions--type a ++ b =-  Append a b--traverseLeft ::-  Applicative m =>-  (a -> m b) ->-  Either a b ->-  m b-traverseLeft f =-  either f pure-{-# inline traverseLeft #-}--interpreting ::-  ∀ e r a .-  FirstOrder e "interpret" =>-  Sem (e : r) a ->-  (∀ r0 x . e (Sem r0) x -> Sem r x) ->-  Sem r a-interpreting s h =-  interpret h s-{-# inline interpreting #-}--as ::-  Functor m =>-  a ->-  m b ->-  m a-as =-  (<$)-{-# inline as #-}
− lib/Helic/Prelude/Debug.hs
@@ -1,103 +0,0 @@-{-# language NoImplicitPrelude #-}-{-# options_haddock prune #-}--- |Debug Printing, Internal--module Helic.Prelude.Debug where--import qualified Data.Text as Text-import Exon (exon)-import GHC.Stack (SrcLoc (..))-import Relude-import System.IO.Unsafe (unsafePerformIO)--srcLoc :: CallStack -> SrcLoc-srcLoc = \case-  (getCallStack -> (_, loc) : _) -> loc-  _ -> error "Debug.srcLoc: empty CallStack"--debugPrint ::-  SrcLoc ->-  Text ->-  IO ()-debugPrint SrcLoc{ srcLocModule = (toText -> slm), ..} msg =-  putStrLn [exon|#{toString moduleName}:#{show srcLocStartLine} #{toString msg}|]-  where-    moduleName =-      fromMaybe slm $ listToMaybe $ reverse $ Text.splitOn "." slm--debugPrintWithLoc ::-  Monad m =>-  SrcLoc ->-  Text ->-  m ()-debugPrintWithLoc loc msg = do-  () <- return (unsafePerformIO (debugPrint loc msg))-  pure ()--dbg ::-  HasCallStack =>-  Monad m =>-  Text ->-  m ()-dbg =-  debugPrintWithLoc (srcLoc callStack)-{-# inline dbg #-}--dbgsWith ::-  HasCallStack =>-  Monad m =>-  Show a =>-  Text ->-  a ->-  m ()-dbgsWith prefix a =-  debugPrintWithLoc (srcLoc callStack) [exon|#{prefix}: #{show @Text a}|]-{-# inline dbgsWith #-}--dbgs ::-  HasCallStack =>-  Monad m =>-  Show a =>-  a ->-  m ()-dbgs a =-  debugPrintWithLoc (srcLoc callStack) (show a)-{-# inline dbgs_ #-}--dbgs_ ::-  HasCallStack =>-  Monad m =>-  Show a =>-  a ->-  m a-dbgs_ a =-  a <$ debugPrintWithLoc (srcLoc callStack) (show a)-{-# inline dbgs #-}--tr ::-  HasCallStack =>-  Text ->-  a ->-  a-tr msg a =-  unsafePerformIO (a <$ debugPrint (srcLoc callStack) msg)-{-# INLINE tr #-}--trs ::-  Show a =>-  HasCallStack =>-  a ->-  a-trs a =-  unsafePerformIO (a <$ debugPrint (srcLoc callStack) (show a))-{-# INLINE trs #-}--trs' ::-  Show b =>-  HasCallStack =>-  b ->-  a ->-  a-trs' b a =-  unsafePerformIO (a <$ debugPrint (srcLoc callStack) (show b))-{-# INLINE trs' #-}
+ lib/Helic/Stop.hs view
@@ -0,0 +1,11 @@+-- |Combinators for 'Stop'.+-- Internal.+module Helic.Stop where++-- |Catch all exceptions in an 'IO' action, embed it into a 'Sem' and convert exceptions to 'Stop'.+tryStop ::+  Members [Stop Text, Embed IO] r =>+  IO a ->+  Sem r a+tryStop =+  stopEither <=< tryAny
lib/Helic/Tmux.hs view
@@ -1,24 +1,19 @@ {-# options_haddock prune #-}+ -- |Tmux Helpers, Internal module Helic.Tmux where -import Polysemy.Conc (Scoped)-import qualified Polysemy.Log as Log-import Polysemy.Log (Log) import qualified Polysemy.Process as Process import Polysemy.Process (Process) import Polysemy.Process.Effect.Process (withProcess)-import Polysemy.Resume (type (!!), (!!))  import Helic.Data.Event (Event (Event))  sendToTmux ::-  ∀ err o e resource r .-  Show err =>-  Members [Scoped resource (Process ByteString o e !! err), Log] r =>+  ∀ o e resource r .+  Members [Scoped resource (Process ByteString o e), Log] r =>   Event ->   Sem r () sendToTmux (Event _ _ _ text) =   withProcess do-    Process.send (encodeUtf8 text) !! \ e ->-      Log.error [exon|failed to send data to tmux: #{show e}|]+    Process.send (encodeUtf8 text)
lib/Helic/Yank.hs view
@@ -4,7 +4,8 @@ module Helic.Yank where  import qualified Data.Text.IO as Text-import Polysemy.Chronos.Time (ChronosTime)+import Polysemy.Chronos (ChronosTime)+import System.IO (stdin)  import Helic.Data.AgentId (AgentId (AgentId)) import qualified Helic.Data.Event as Event
− lib/Prelude.hs
@@ -1,9 +0,0 @@-{-# language NoImplicitPrelude #-}-{-# options_haddock prune #-}--- |Prelude, Internal--module Prelude (-  module Helic.Prelude,-) where--import Helic.Prelude
+ test/Helic/Dev.hs view
@@ -0,0 +1,22 @@+module Helic.Dev where++import Polysemy.Conc (withAsync_)+import qualified Polysemy.Time as Time+import Polysemy.Time (Seconds (Seconds))+import System.Environment (setEnv)++import Helic.App (listenApp)+import Helic.Cli (interpretLog, runIO)+import Helic.Data.Config (Config (Config))+import Helic.Data.NetConfig (NetConfig (NetConfig))++conf :: Config+conf =+  Config (Just "dev") (Just def) (Just (NetConfig (Just 11111) Nothing Nothing)) Nothing (Just True)++main :: IO ()+main =+  runIO (withAsync_ setenv (interpretLog (Just True) (listenApp conf)))+  where+    setenv =+      Time.sleep (Seconds 12) *> embed (setEnv "DISPLAY" ":0")
test/Helic/Test/ConfigFileTest.hs view
@@ -1,3 +1,5 @@+{-# options_ghc -Wno-unused-imports #-}+ module Helic.Test.ConfigFileTest where  import Path (Abs, File, Rel, absfile, relfile)
+ test/Helic/Test/GtkMainTest.hs view
@@ -0,0 +1,87 @@+module Helic.Test.GtkMainTest where++import qualified Data.Set as Set+import Polysemy.Chronos (ChronosTime, interpretTimeChronos)+import Polysemy.Conc (interpretAtomic, interpretEventsChan, interpretRace, interpretScopedResumable)+import Polysemy.Log (interpretLogNull)+import Polysemy.Test (Hedgehog, UnitTest, assertEq, evalEither, runTestAuto)+import qualified Polysemy.Time as Time+import Polysemy.Time (MilliSeconds (MilliSeconds), Seconds (Seconds))++import qualified Helic.Data.Event as Event+import Helic.Data.Event (Event)+import Helic.Data.XClipboardEvent (XClipboardEvent)+import qualified Helic.Effect.Agent as Agent+import Helic.Effect.Agent (AgentX, agentIdNet)+import qualified Helic.Effect.Gtk as Gtk+import Helic.Effect.Gtk (Gtk)+import qualified Helic.Effect.GtkClipboard as GtkClipboard+import Helic.Effect.GtkClipboard (GtkClipboard)+import Helic.GtkClipboard (subscribeEvents)+import Helic.GtkMain (gtkMainLoop)+import Helic.Interpreter.AgentX (interpretAgentX)+import Helic.Interpreter.GtkMain (interpretGtkMain, interpretWithGtk)+import Helic.Interpreter.InstanceName (interpretInstanceName)+import Helic.Interpreter.XClipboard (interpretXClipboardGtk)++handleGtkClipboardTest ::+  Member (AtomicState [Text]) r =>+  () ->+  GtkClipboard (Sem r0) a ->+  Tactical effect (Sem r0) (Stop Text : r) a+handleGtkClipboardTest _ = \case+  GtkClipboard.Read _ -> do+    pureT (Just "here")+  GtkClipboard.Write _ t ->+    pureT =<< atomicModify' (t :)+  GtkClipboard.Events _ -> do+    pureT ()++bracketGtk ::+  Member (AtomicState Bool) r =>+  (() -> Sem (Stop Text : r) a) ->+  Sem (Stop Text : r) a+bracketGtk f = do+  unlessM atomicGet do+    stop "no display"+  f ()++interpretGtk ::+  Members [AtomicState Bool, ChronosTime] r =>+  InterpreterFor (Scoped () (Gtk ()) !! Text) r+interpretGtk =+  interpretScopedResumable bracketGtk \ () -> \case+    Gtk.Main ->+      Time.sleep (Seconds 10)+    Gtk.Resource ->+      unit++gtkMainTest ::+  Members [Hedgehog IO, Resource, Embed IO, Final IO] r =>+  Sem r (Either Text ())+gtkMainTest =+  asyncToIOFinal $+  errorToIOFinal $+  interpretRace $+  interpretTimeChronos $+  interpretLogNull $+  interpretInstanceName (Just "test") $+  interpretEventsChan @XClipboardEvent $+  interpretEventsChan @Event $+  interpretAtomic [] $+  interpretAtomic False $+  interpretGtk $+  interpretGtkMain (MilliSeconds 50) (MilliSeconds 100) $+  interpretWithGtk @GtkClipboard handleGtkClipboardTest $+  gtkMainLoop subscribeEvents $+  interpretXClipboardGtk $+  interpretAgentX do+    tag @AgentX . Agent.update =<< Event.now agentIdNet "not running"+    atomicPut True+    let pub = tag @AgentX . Agent.update <=< Event.now agentIdNet . show+    sequenceConcurrently @[] (pub <$> [1..5 :: Int])+    assertEq ["1", "2", "3", "4", "5"] . Set.fromList =<< atomicGet @[Text]++test_gtkMain :: UnitTest+test_gtkMain =+  runTestAuto (evalEither =<< gtkMainTest)
test/Helic/Test/InsertEventTest.hs view
@@ -3,10 +3,11 @@ import qualified Chronos import Chronos (datetimeToTime) import Data.Sequence ((|>))-import Polysemy.Chronos (interpretTimeChronos)+import Exon (exon)+import Polysemy.Chronos (interpretTimeChronosConstant) import Polysemy.Test (UnitTest, assertJust, runTestAuto, (===)) import qualified Polysemy.Time as Time-import Polysemy.Time (MilliSeconds (MilliSeconds), convert, mkDatetime)+import Polysemy.Time (Days (Days), MilliSeconds (MilliSeconds), convert, mkDatetime) import Torsor (add)  import Helic.Data.Event (Event (Event))@@ -24,16 +25,25 @@ event2 =   Event "me" "test" old "event2" +eventMixedNl :: Event+eventMixedNl =+  Event "me" "test" old ("line1\r\nline2\rline3\nline4" <> [exon|
line5|])++eventNl :: Event+eventNl =+  Event "me" "test" old "line1\nline2\nline3\nline4\nline5"+ historyLatest :: Seq Event historyLatest =   [event2, event1]  test_insertEvent :: UnitTest test_insertEvent =-  runTestAuto $ interpretTimeChronos do+  runTestAuto $ interpretTimeChronosConstant (add (convert (Days 1)) old) do     now <- Time.now     assertJust [Event "me" "test" now "string"] (appendIfValid now (Event "me" "test" now "string") mempty)     Nothing === appendIfValid now event1 historyLatest     assertJust (historyLatest |> event2) (appendIfValid now event2 historyLatest)     Nothing === appendIfValid (add (convert (MilliSeconds 100)) old) event2 historyLatest     assertJust (historyLatest |> event2) (appendIfValid (add (convert (MilliSeconds 1100)) old) event2 historyLatest)+    assertJust (historyLatest |> eventNl) (appendIfValid now eventMixedNl historyLatest)
test/Helic/Test/ListTest.hs view
@@ -1,7 +1,7 @@ module Helic.Test.ListTest where +import Exon(exon) import Polysemy.Chronos (interpretTimeChronosConstant)-import Polysemy.Error (errorToIOFinal) import Polysemy.Test (UnitTest, assertRight, runTestAuto)  import Helic.Data.AgentId (AgentId (AgentId))
test/Helic/Test/ListenTest.hs view
@@ -4,8 +4,6 @@ import Polysemy.Chronos (interpretTimeChronosConstant) import qualified Polysemy.Conc as Conc import Polysemy.Conc (-  Events,-  Queue,   interpretAtomic,   interpretEventsChan,   interpretQueueTBM,
test/Main.hs view
@@ -1,6 +1,7 @@ module Main where  import Helic.Test.ConfigFileTest (test_readConfigFile)+import Helic.Test.GtkMainTest (test_gtkMain) import Helic.Test.InsertEventTest (test_insertEvent) import Helic.Test.ListTest (test_list) import Helic.Test.ListenTest (test_listen)@@ -15,7 +16,8 @@     unitTest "parse a config file" test_readConfigFile,     unitTest "listen for events, filter duplicates from network feedback" test_listen,     unitTest "print the history" test_list,-    unitTest "load an old event to the clipboard" test_load+    unitTest "load an old event to the clipboard" test_load,+    unitTest "restart the gtk main loop when requested after failure" test_gtkMain   ]  main :: IO ()