chiasma 0.2.0.0 → 0.10.0.0
raw patch · 127 files changed
+3095/−2504 lines, 127 filesdep +exondep +extradep +first-class-familiesdep −aesondep −chiasmadep −composition-extradep ~attoparsecdep ~basedep ~compositionsetup-changedPVP ok
version bump matches the API change (PVP)
Dependencies added: exon, extra, first-class-families, path, polysemy, polysemy-conc, polysemy-log, polysemy-plugin, polysemy-process, polysemy-time, prelate
Dependencies removed: aeson, chiasma, composition-extra, conduit, conduit-extra, cornea, data-default, deepseq, directory, either, exceptions, filepath, free, hedgehog, mtl, posix-pty, process, relude, resourcet, split, stm-chans, stm-conduit, tasty, tasty-hedgehog, unix, unliftio, unliftio-core
Dependency ranges changed: attoparsec, base, composition, lens, parsers, prettyprinter, prettyprinter-ansi-terminal, random, typed-process, uuid
API changes (from Hackage documentation)
Files
- LICENSE +1/−1
- Setup.hs +2/−0
- chiasma.cabal +88/−301
- integration/Chiasma/Test/IndividualProcessTest.hs +0/−42
- integration/Chiasma/Test/ProcessTest.hs +0/−42
- integration/Chiasma/Test/RenderTest.hs +0/−216
- integration/Chiasma/Test/TmuxStreamTest.hs +0/−44
- integration/Chiasma/Test/Util.hs +0/−19
- integration/Main.hs +0/−21
- lib/Chiasma.hs +12/−0
- lib/Chiasma/Api/Class.hs +0/−19
- lib/Chiasma/Class/CmdArgs.hs +55/−0
- lib/Chiasma/Codec.hs +60/−23
- lib/Chiasma/Codec/Data/Client.hs +13/−0
- lib/Chiasma/Codec/Data/Pane.hs +6/−3
- lib/Chiasma/Codec/Data/PaneCoords.hs +4/−4
- lib/Chiasma/Codec/Data/PaneDetail.hs +0/−18
- lib/Chiasma/Codec/Data/PaneMode.hs +3/−2
- lib/Chiasma/Codec/Data/PanePid.hs +3/−2
- lib/Chiasma/Codec/Data/Session.hs +4/−2
- lib/Chiasma/Codec/Data/Window.hs +2/−1
- lib/Chiasma/Codec/Decode.hs +37/−44
- lib/Chiasma/Codec/Query.hs +5/−8
- lib/Chiasma/Command/Pane.hs +100/−113
- lib/Chiasma/Command/Session.hs +45/−17
- lib/Chiasma/Command/Window.hs +54/−28
- lib/Chiasma/Control/IO/Unsafe.hs +1/−1
- lib/Chiasma/Data/Axis.hs +31/−0
- lib/Chiasma/Data/CapturePaneParams.hs +84/−0
- lib/Chiasma/Data/Cmd.hs +4/−4
- lib/Chiasma/Data/CodecError.hs +8/−0
- lib/Chiasma/Data/Conduit.hs +0/−10
- lib/Chiasma/Data/CopyModeParams.hs +32/−0
- lib/Chiasma/Data/DecodeError.hs +26/−0
- lib/Chiasma/Data/Direction.hs +22/−0
- lib/Chiasma/Data/Event.hs +7/−0
- lib/Chiasma/Data/Ident.hs +11/−15
- lib/Chiasma/Data/KillPaneParams.hs +24/−0
- lib/Chiasma/Data/LayoutError.hs +6/−0
- lib/Chiasma/Data/Maybe.hs +0/−17
- lib/Chiasma/Data/PaneSelection.hs +22/−0
- lib/Chiasma/Data/Panes.hs +105/−0
- lib/Chiasma/Data/PipePaneParams.hs +38/−0
- lib/Chiasma/Data/ProcessError.hs +5/−0
- lib/Chiasma/Data/ReceiveEvent.hs +6/−0
- lib/Chiasma/Data/RenderError.hs +7/−3
- lib/Chiasma/Data/ResizePaneParams.hs +58/−0
- lib/Chiasma/Data/SelectParams.hs +65/−0
- lib/Chiasma/Data/SelectWindowParams.hs +38/−0
- lib/Chiasma/Data/SendKeysParams.hs +78/−0
- lib/Chiasma/Data/SessionGroupId.hs +6/−0
- lib/Chiasma/Data/SessionParams.hs +72/−0
- lib/Chiasma/Data/SplitParams.hs +84/−0
- lib/Chiasma/Data/Target.hs +32/−0
- lib/Chiasma/Data/TmuxCommand.hs +213/−0
- lib/Chiasma/Data/TmuxError.hs +13/−29
- lib/Chiasma/Data/TmuxId.hs +22/−12
- lib/Chiasma/Data/TmuxNative.hs +10/−0
- lib/Chiasma/Data/TmuxOutputBlock.hs +13/−0
- lib/Chiasma/Data/TmuxQuery.hs +6/−0
- lib/Chiasma/Data/TmuxRequest.hs +24/−0
- lib/Chiasma/Data/TmuxResponse.hs +6/−0
- lib/Chiasma/Data/TmuxThunk.hs +12/−12
- lib/Chiasma/Data/View.hs +1/−1
- lib/Chiasma/Data/Views.hs +8/−10
- lib/Chiasma/Data/WindowParams.hs +56/−0
- lib/Chiasma/Data/WindowSelection.hs +19/−0
- lib/Chiasma/Data/WindowState.hs +1/−1
- lib/Chiasma/Data/WithPaneId.hs +39/−0
- lib/Chiasma/Effect/Codec.hs +32/−0
- lib/Chiasma/Effect/TmuxApi.hs +12/−0
- lib/Chiasma/Effect/TmuxClient.hs +28/−0
- lib/Chiasma/Event.hs +58/−0
- lib/Chiasma/Function.hs +7/−0
- lib/Chiasma/Interpreter/Codec.hs +50/−0
- lib/Chiasma/Interpreter/ProcessOutput.hs +39/−0
- lib/Chiasma/Interpreter/Pure.hs +13/−0
- lib/Chiasma/Interpreter/TmuxApi.hs +90/−0
- lib/Chiasma/Interpreter/TmuxClient.hs +201/−0
- lib/Chiasma/Lens/Tree.hs +18/−19
- lib/Chiasma/Lens/Where.hs +2/−6
- lib/Chiasma/Monad/EvalFreeT.hs +42/−42
- lib/Chiasma/Monad/IndividualProcess.hs +0/−40
- lib/Chiasma/Monad/Stream.hs +0/−80
- lib/Chiasma/Monad/Tmux.hs +39/−39
- lib/Chiasma/Native/Api.hs +0/−67
- lib/Chiasma/Native/Parse.hs +1/−1
- lib/Chiasma/Native/Process.hs +0/−57
- lib/Chiasma/Native/StreamParse.hs +0/−65
- lib/Chiasma/Native/TmuxOutputBlock.hs +46/−0
- lib/Chiasma/Pack.hs +44/−41
- lib/Chiasma/Pane.hs +9/−6
- lib/Chiasma/Path.hs +16/−0
- lib/Chiasma/Prelude.hs +0/−71
- lib/Chiasma/Render.hs +21/−15
- lib/Chiasma/Session.hs +18/−21
- lib/Chiasma/Test/File.hs +0/−31
- lib/Chiasma/Test/Screenshot.hs +0/−67
- lib/Chiasma/Test/Tmux.hs +0/−121
- lib/Chiasma/Tmux.hs +89/−0
- lib/Chiasma/TmuxApi.hs +7/−0
- lib/Chiasma/TmuxNative.hs +68/−0
- lib/Chiasma/Ui/Data/Measure.hs +10/−9
- lib/Chiasma/Ui/Data/RenderableTree.hs +10/−9
- lib/Chiasma/Ui/Data/Tree.hs +10/−11
- lib/Chiasma/Ui/Data/TreeModError.hs +2/−3
- lib/Chiasma/Ui/Data/View.hs +25/−22
- lib/Chiasma/Ui/Data/ViewGeometry.hs +3/−4
- lib/Chiasma/Ui/Data/ViewState.hs +1/−1
- lib/Chiasma/Ui/Lens/Ident.hs +9/−11
- lib/Chiasma/Ui/Measure.hs +25/−22
- lib/Chiasma/Ui/Measure/Balance.hs +49/−34
- lib/Chiasma/Ui/Measure/Weights.hs +15/−17
- lib/Chiasma/Ui/ShowTree.hs +9/−5
- lib/Chiasma/Ui/ViewTree.hs +30/−31
- lib/Chiasma/View.hs +41/−26
- lib/Chiasma/View/State.hs +4/−8
- lib/Chiasma/Window.hs +93/−88
- lib/Prelude.hs +0/−5
- readme.md +0/−3
- test/Chiasma/Test/CodecTest.hs +0/−24
- test/Chiasma/Test/LensTest.hs +0/−102
- test/Chiasma/Test/OutputParseTest.hs +0/−29
- test/Chiasma/Test/PinTest.hs +0/−98
- test/Chiasma/Test/StreamParseTest.hs +0/−30
- test/Chiasma/Test/Util.hs +0/−14
- test/Main.hs +0/−24
@@ -1,4 +1,4 @@-Copyright (c) 2020 Torsten Schmits+Copyright (c) 2022 Torsten Schmits Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
@@ -1,37 +1,36 @@ 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.7. -- -- see: https://github.com/sol/hpack name: chiasma-version: 0.2.0.0-synopsis: tmux api-description: Please see the README on GitHub at <https://github.com/tek/chiasma>+version: 0.10.0.0+synopsis: A tmux client for Polysemy+description: See https://hackage.haskell.org/package/chiasma/docs/Chiasma.html category: Terminal-homepage: https://github.com/tek/chiasma-hs#readme-bug-reports: https://github.com/tek/chiasma-hs/issues+homepage: https://github.com/tek/chiasma#readme+bug-reports: https://github.com/tek/chiasma/issues author: Torsten Schmits-maintainer: tek@tryp.io-copyright: 2021 Torsten Schmits+maintainer: hackage@tryp.io+copyright: 2022 Torsten Schmits license: BSD-2-Clause-Patent license-file: LICENSE build-type: Simple-extra-source-files:- readme.md source-repository head type: git- location: https://github.com/tek/chiasma-hs+ location: https://github.com/tek/chiasma library exposed-modules:- Chiasma.Api.Class+ Chiasma+ Chiasma.Class.CmdArgs Chiasma.Codec Chiasma.Codec.Data+ Chiasma.Codec.Data.Client Chiasma.Codec.Data.Pane Chiasma.Codec.Data.PaneCoords- Chiasma.Codec.Data.PaneDetail Chiasma.Codec.Data.PaneMode Chiasma.Codec.Data.PanePid Chiasma.Codec.Data.Session@@ -42,36 +41,71 @@ Chiasma.Command.Session Chiasma.Command.Window Chiasma.Control.IO.Unsafe+ Chiasma.Data.Axis+ Chiasma.Data.CapturePaneParams Chiasma.Data.Cmd- Chiasma.Data.Conduit+ Chiasma.Data.CodecError+ Chiasma.Data.CopyModeParams+ Chiasma.Data.DecodeError+ Chiasma.Data.Direction+ Chiasma.Data.Event Chiasma.Data.Ident+ Chiasma.Data.KillPaneParams+ Chiasma.Data.LayoutError Chiasma.Data.List- Chiasma.Data.Maybe+ Chiasma.Data.Panes+ Chiasma.Data.PaneSelection+ Chiasma.Data.PipePaneParams+ Chiasma.Data.ProcessError+ Chiasma.Data.ReceiveEvent Chiasma.Data.RenderError+ Chiasma.Data.ResizePaneParams+ Chiasma.Data.SelectParams+ Chiasma.Data.SelectWindowParams+ Chiasma.Data.SendKeysParams+ Chiasma.Data.SessionGroupId+ Chiasma.Data.SessionParams+ Chiasma.Data.SplitParams+ Chiasma.Data.Target+ Chiasma.Data.TmuxCommand Chiasma.Data.TmuxError Chiasma.Data.TmuxId+ Chiasma.Data.TmuxNative+ Chiasma.Data.TmuxOutputBlock+ Chiasma.Data.TmuxQuery+ Chiasma.Data.TmuxRequest+ Chiasma.Data.TmuxResponse Chiasma.Data.TmuxThunk Chiasma.Data.View Chiasma.Data.Views+ Chiasma.Data.WindowParams+ Chiasma.Data.WindowSelection Chiasma.Data.WindowState+ Chiasma.Data.WithPaneId+ Chiasma.Effect.Codec+ Chiasma.Effect.TmuxApi+ Chiasma.Effect.TmuxClient+ Chiasma.Event+ Chiasma.Function+ Chiasma.Interpreter.Codec+ Chiasma.Interpreter.ProcessOutput+ Chiasma.Interpreter.Pure+ Chiasma.Interpreter.TmuxApi+ Chiasma.Interpreter.TmuxClient Chiasma.Lens.Tree Chiasma.Lens.Where Chiasma.Monad.EvalFreeT- Chiasma.Monad.IndividualProcess- Chiasma.Monad.Stream Chiasma.Monad.Tmux- Chiasma.Native.Api Chiasma.Native.Parse- Chiasma.Native.Process- Chiasma.Native.StreamParse+ Chiasma.Native.TmuxOutputBlock Chiasma.Pack Chiasma.Pane- Chiasma.Prelude+ Chiasma.Path Chiasma.Render Chiasma.Session- Chiasma.Test.File- Chiasma.Test.Screenshot- Chiasma.Test.Tmux+ Chiasma.Tmux+ Chiasma.TmuxApi+ Chiasma.TmuxNative Chiasma.Ui.Data.Measure Chiasma.Ui.Data.RenderableTree Chiasma.Ui.Data.Tree@@ -89,14 +123,10 @@ Chiasma.View Chiasma.View.State Chiasma.Window- other-modules:- Prelude- Paths_chiasma- autogen-modules:- Paths_chiasma hs-source-dirs: lib default-extensions:+ ImpredicativeTypes AllowAmbiguousTypes ApplicativeDo BangPatterns@@ -110,11 +140,14 @@ DeriveFoldable DeriveFunctor DeriveGeneric+ DeriveLift DeriveTraversable DerivingStrategies+ DerivingVia DisambiguateRecordFields DoAndIfThenElse DuplicateRecordFields+ EmptyCase EmptyDataDecls ExistentialQuantification FlexibleContexts@@ -129,120 +162,9 @@ MultiParamTypeClasses MultiWayIf NamedFieldPuns- OverloadedStrings+ OverloadedLabels OverloadedLists- PackageImports- PartialTypeSignatures- PatternGuards- PatternSynonyms- PolyKinds- QuantifiedConstraints- QuasiQuotes- RankNTypes- RecordWildCards- RecursiveDo- ScopedTypeVariables- StandaloneDeriving- TemplateHaskell- TupleSections- TypeApplications- TypeFamilies- TypeFamilyDependencies- TypeOperators- TypeSynonymInstances- UndecidableInstances- UnicodeSyntax- ViewPatterns- build-depends:- aeson >=1.4 && <1.6- , attoparsec ==0.13.*- , base ==4.*- , bytestring- , composition ==1.0.*- , composition-extra ==2.0.*- , conduit ==1.3.*- , conduit-extra ==1.3.*- , containers- , cornea ==0.4.*- , data-default ==0.7.*- , deepseq- , directory- , either >=5.0.1 && <5.1- , exceptions- , filepath- , free ==5.1.*- , lens ==4.*- , mtl- , parsec- , parsers ==0.12.*- , posix-pty ==0.2.*- , prettyprinter >=1.6 && <1.8- , prettyprinter-ansi-terminal ==1.1.*- , process ==1.6.*- , random ==1.1.*- , relude >=0.7 && <1.2- , resourcet ==1.2.*- , split ==0.2.*- , stm-chans ==3.0.*- , stm-conduit ==4.0.*- , text- , transformers- , typed-process ==0.2.*- , unix- , unliftio ==0.2.*- , unliftio-core >=0.1 && <0.3- , uuid ==1.3.*- mixins:- base hiding (Prelude)- default-language: Haskell2010--test-suite chiasma-integration- type: exitcode-stdio-1.0- main-is: Main.hs- other-modules:- Chiasma.Test.IndividualProcessTest- Chiasma.Test.ProcessTest- Chiasma.Test.RenderTest- Chiasma.Test.TmuxStreamTest- Chiasma.Test.Util- Paths_chiasma- hs-source-dirs:- integration- default-extensions:- AllowAmbiguousTypes- ApplicativeDo- BangPatterns- BinaryLiterals- BlockArguments- ConstraintKinds- DataKinds- DefaultSignatures- DeriveAnyClass- DeriveDataTypeable- DeriveFoldable- DeriveFunctor- DeriveGeneric- DeriveTraversable- DerivingStrategies- DisambiguateRecordFields- DoAndIfThenElse- DuplicateRecordFields- EmptyDataDecls- ExistentialQuantification- FlexibleContexts- FlexibleInstances- FunctionalDependencies- GADTs- GeneralizedNewtypeDeriving- InstanceSigs- KindSignatures- LambdaCase- LiberalTypeSynonyms- MultiParamTypeClasses- MultiWayIf- NamedFieldPuns OverloadedStrings- OverloadedLists PackageImports PartialTypeSignatures PatternGuards@@ -253,126 +175,7 @@ RankNTypes RecordWildCards RecursiveDo- ScopedTypeVariables- StandaloneDeriving- TemplateHaskell- TupleSections- TypeApplications- TypeFamilies- TypeFamilyDependencies- TypeOperators- TypeSynonymInstances- UndecidableInstances- UnicodeSyntax- ViewPatterns- ghc-options: -threaded -rtsopts -with-rtsopts=-N- build-depends:- aeson >=1.4 && <1.6- , attoparsec ==0.13.*- , base ==4.*- , bytestring- , chiasma- , composition ==1.0.*- , composition-extra ==2.0.*- , conduit ==1.3.*- , conduit-extra ==1.3.*- , containers- , cornea ==0.4.*- , data-default ==0.7.*- , deepseq- , directory- , either >=5.0.1 && <5.1- , exceptions- , filepath- , free ==5.1.*- , hedgehog- , lens ==4.*- , mtl- , parsec- , parsers ==0.12.*- , posix-pty ==0.2.*- , prettyprinter >=1.6 && <1.8- , prettyprinter-ansi-terminal ==1.1.*- , process ==1.6.*- , random ==1.1.*- , relude >=0.7 && <1.2- , resourcet ==1.2.*- , split ==0.2.*- , stm-chans ==3.0.*- , stm-conduit ==4.0.*- , tasty- , tasty-hedgehog- , text- , transformers- , typed-process ==0.2.*- , unix- , unliftio ==0.2.*- , unliftio-core >=0.1 && <0.3- , uuid ==1.3.*- mixins:- base hiding (Prelude)- , chiasma (Chiasma.Prelude as Prelude)- , chiasma hiding (Chiasma.Prelude)- default-language: Haskell2010--test-suite chiasma-unit- type: exitcode-stdio-1.0- main-is: Main.hs- other-modules:- Chiasma.Test.CodecTest- Chiasma.Test.LensTest- Chiasma.Test.OutputParseTest- Chiasma.Test.PinTest- Chiasma.Test.StreamParseTest- Chiasma.Test.Util- Paths_chiasma- hs-source-dirs:- test- default-extensions:- AllowAmbiguousTypes- ApplicativeDo- BangPatterns- BinaryLiterals- BlockArguments- ConstraintKinds- DataKinds- DefaultSignatures- DeriveAnyClass- DeriveDataTypeable- DeriveFoldable- DeriveFunctor- DeriveGeneric- DeriveTraversable- DerivingStrategies- DisambiguateRecordFields- DoAndIfThenElse- DuplicateRecordFields- EmptyDataDecls- ExistentialQuantification- FlexibleContexts- FlexibleInstances- FunctionalDependencies- GADTs- GeneralizedNewtypeDeriving- InstanceSigs- KindSignatures- LambdaCase- LiberalTypeSynonyms- MultiParamTypeClasses- MultiWayIf- NamedFieldPuns- OverloadedStrings- OverloadedLists- PackageImports- PartialTypeSignatures- PatternGuards- PatternSynonyms- PolyKinds- QuantifiedConstraints- QuasiQuotes- RankNTypes- RecordWildCards- RecursiveDo+ RoleAnnotations ScopedTypeVariables StandaloneDeriving TemplateHaskell@@ -385,52 +188,36 @@ UndecidableInstances UnicodeSyntax ViewPatterns- ghc-options: -threaded -rtsopts -with-rtsopts=-N+ ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Widentities -Wunused-packages -fplugin=Polysemy.Plugin build-depends:- aeson >=1.4 && <1.6- , attoparsec ==0.13.*- , base ==4.*+ attoparsec >=0.13+ , base >=4.12 && <5 , bytestring- , chiasma- , composition ==1.0.*- , composition-extra ==2.0.*- , conduit ==1.3.*- , conduit-extra ==1.3.*+ , composition >=1.0 , containers- , cornea ==0.4.*- , data-default ==0.7.*- , deepseq- , directory- , either >=5.0.1 && <5.1- , exceptions- , filepath- , free ==5.1.*- , hedgehog- , lens ==4.*- , mtl+ , exon+ , extra+ , first-class-families+ , lens >=4 , parsec- , parsers ==0.12.*- , posix-pty ==0.2.*- , prettyprinter >=1.6 && <1.8- , prettyprinter-ansi-terminal ==1.1.*- , process ==1.6.*- , random ==1.1.*- , relude >=0.7 && <1.2- , resourcet ==1.2.*- , split ==0.2.*- , stm-chans ==3.0.*- , stm-conduit ==4.0.*- , tasty- , tasty-hedgehog+ , parsers >=0.12+ , path+ , polysemy+ , polysemy-conc >=0.9+ , polysemy-log >=0.7+ , polysemy-plugin+ , polysemy-process >=0.9+ , polysemy-time >=0.5+ , prelate >=0.2+ , prettyprinter >=1.6+ , prettyprinter-ansi-terminal >=1.1+ , random >=1.1 , text , transformers- , typed-process ==0.2.*- , unix- , unliftio ==0.2.*- , unliftio-core >=0.1 && <0.3- , uuid ==1.3.*+ , typed-process >=0.2+ , uuid >=1.3 mixins: base hiding (Prelude)- , chiasma (Chiasma.Prelude as Prelude)- , chiasma hiding (Chiasma.Prelude)+ , prelate (Prelate as Prelude)+ , prelate hiding (Prelate) default-language: Haskell2010
@@ -1,42 +0,0 @@-module Chiasma.Test.IndividualProcessTest where--import Chiasma.Codec.Data (Pane(Pane), Window(Window))-import Chiasma.Data.TmuxError (TmuxError)-import Chiasma.Data.TmuxId (PaneId(..), WindowId(..))-import Chiasma.Monad.IndividualProcess (TmuxProg, runTmux)-import qualified Chiasma.Monad.Tmux as Tmux (read, write)-import Chiasma.Native.Api (TmuxNative(..))-import Hedgehog ((===))--import Chiasma.Test.Tmux (tmuxSpec)-import Chiasma.Test.Util (UnitTest)--prog :: TmuxProg ([Pane], [Pane], [Window])-prog = do- panes1 <- Tmux.read "list-panes" ["-t", "%0"]- Tmux.write "new-window" []- Tmux.write "new-window" []- wins <- Tmux.read "list-windows" []- panes <- Tmux.read "list-panes" ["-a"]- return (panes1, panes, wins)--p :: Int -> Pane-p i = Pane (PaneId i) 240 60--w :: Int -> Window-w i = Window (WindowId i) 240 60--runProg :: TmuxNative -> IO (Either TmuxError ([Pane], [Pane], [Window]))-runProg api = runExceptT $ runTmux api prog--test_individual :: UnitTest-test_individual = do- result :: Either TmuxError ([Pane], [Pane], [Window]) <- liftIO (tmuxSpec runProg)- Right ([p0], [p0, p1, p2], [w0, w1, w2]) === result- where- p0 = p 0- p1 = p 1- p2 = p 2- w0 = w 0- w1 = w 1- w2 = w 2
@@ -1,42 +0,0 @@-module Chiasma.Test.ProcessTest where--import Chiasma.Data.Conduit (createSinkFlush)-import Chiasma.Native.Api (TmuxNative(..))-import Chiasma.Native.Process (socketArg)-import Conduit (ConduitT, Flush(..), mapC, runConduit, sinkList, yieldMany, (.|))-import Data.ByteString.Internal (packChars)-import qualified Data.Conduit.Combinators as Conduit (concatMap, take)-import Data.Conduit.Process.Typed (- Process,- createSource,- getStdin,- getStdout,- proc,- setStdin,- setStdout,- withProcessTerm,- )-import qualified Data.Text as Text (splitOn)-import Hedgehog (assert)--import Chiasma.Test.Tmux (tmuxSpec)-import Chiasma.Test.Util (UnitTest)--run :: Process (ConduitT (Flush ByteString) Void IO ()) (ConduitT () ByteString IO ()) () -> IO [Text]-run prc = do- let stdin' = mapC (fmap packChars) .| getStdin prc- let stdout' = getStdout prc- runConduit $ yieldMany ([Chunk "list-panes\n"] :: [Flush String]) .| stdin'- runConduit $ yieldMany ([Flush] :: [Flush String]) .| stdin'- runConduit $ stdout' .| Conduit.take 2 .| mapC decodeUtf8 .| Conduit.concatMap (Text.splitOn "\n") .| sinkList--test_process :: UnitTest-test_process = do- out <- liftIO $ tmuxSpec $ \(TmuxNative sock) -> do- withProcessTerm (pc (args sock)) run- assert (length out > 3)- where- pc =- setStdin createSinkFlush . setStdout createSource . proc "tmux"- args sock =- toString <$> socketArg sock ++ ["-C", "attach"]
@@ -1,216 +0,0 @@-module Chiasma.Test.RenderTest where--import Chiasma.Codec.Data.PaneDetail (PaneDetail(..))-import Chiasma.Command.Pane (panesAs)-import Chiasma.Command.Session (activateSession)-import Chiasma.Data.Ident (Ident(Str))-import Chiasma.Data.TmuxError (TmuxError)-import Chiasma.Data.TmuxId (PaneId(PaneId), SessionId(SessionId), WindowId(WindowId))-import Chiasma.Data.View (viewIdent)-import qualified Chiasma.Data.View as Tmux (View(View))-import Chiasma.Data.Views (Views(Views))-import Chiasma.Monad.Stream (runTmux)-import Chiasma.Native.Api (TmuxNative(..))-import Chiasma.Render (render)-import Chiasma.Ui.Data.View (Tree(..), TreeSub(..), ViewTree, consLayout, consLayoutVertical)-import qualified Chiasma.Ui.Data.View as Ui (Pane(Pane), View(View))-import Chiasma.Ui.Data.ViewGeometry (ViewGeometry(..))-import Chiasma.Ui.Data.ViewState (ViewState(ViewState))-import Chiasma.Ui.ViewTree (togglePane)-import qualified Chiasma.Ui.ViewTree as ToggleResult (ToggleResult(Success))-import Hedgehog (evalEither, (===))-import Test.Tasty (TestTree, testGroup)-import UnliftIO (throwString)-import UnliftIO.Directory (getCurrentDirectory)--import Chiasma.Test.Tmux (sleep, tmuxSpec)-import Chiasma.Test.Util (UnitTest, integrationTest)--id0, id1, id2, id3 :: Ident-id0 = Str "0"-id1 = Str "1"-id2 = Str "2"-id3 = Str "3"--views :: Views-views =- Views- [Tmux.View id0 (Just (SessionId 0))]- [Tmux.View id0 (Just (WindowId 0))]- [Tmux.View id0 (Just (PaneId 0))]- []--renderOnce :: ViewTree -> TmuxNative -> FilePath -> Views -> IO Views-renderOnce tree api cwd vs = do- r <- runExceptT $ runStateT (runTmux api $ render cwd id1 id1 tree) vs- either (throwString . show) (return . snd) r--safeRender :: Views -> ViewTree -> TmuxNative -> IO Views-safeRender vs tree api = do- cwd <- getCurrentDirectory- vs1 <- renderOnce tree api cwd vs- _ <- runExceptT @TmuxError $ runTmux api $ activateSession 0- _ <- runExceptT @TmuxError $ runTmux api $ activateSession 1- sleep 1- renderOnce tree api cwd vs1--runRender :: ViewTree -> TmuxNative -> IO ([Tmux.View PaneId], Either TmuxError [PaneDetail])-runRender tree api = do- (Views _ _ vs2 _) <- safeRender views tree api- sleep 1- ps <- runExceptT @TmuxError $ runTmux api panesAs- return (sortOn viewIdent vs2, sortOn paneLeft . drop 1 <$> ps)--renderSpec :: ViewTree -> [PaneDetail] -> UnitTest-renderSpec tree target = do- (_, pse) <- liftIO $ tmuxSpec $ runRender tree- ps <- evalEither pse- target === ps--treeImbalanced :: ViewTree-treeImbalanced =- Tree (consLayout id0) [- TreeLeaf (openPane id0 def),- TreeLeaf (openPane id1 (ViewGeometry Nothing Nothing (Just 150) Nothing Nothing Nothing))- ]- where- openPane i geo = Ui.View i (ViewState False) geo (Ui.Pane True False Nothing)--imbalancedTarget :: [PaneDetail]-imbalancedTarget =- [- PaneDetail { paneId = PaneId 1, paneWidth = 89, paneHeight = 60, paneTop = 0, paneLeft = 0 },- PaneDetail { paneId = PaneId 2, paneWidth = 150, paneHeight = 60, paneTop = 0, paneLeft = 90 }- ]--test_imbalanced :: UnitTest-test_imbalanced =- renderSpec treeImbalanced imbalancedTarget--treeNested :: ViewTree-treeNested =- Tree (consLayoutVertical id0) [- TreeNode $- Tree (consLayout id1) [- TreeLeaf (openPane id0 def),- TreeNode $- Tree (consLayoutVertical id2) [- TreeLeaf (openPane id1 def),- TreeLeaf (openPane id2 def)- ]- ]- ]- where- openPane i geo = Ui.View i (ViewState False) geo (Ui.Pane True False Nothing)--nestedTarget :: [PaneDetail]-nestedTarget =- [- PaneDetail { paneId = PaneId 1, paneWidth = 120, paneHeight = 60, paneTop = 0, paneLeft = 0 },- PaneDetail { paneId = PaneId 2, paneWidth = 119, paneHeight = 30, paneTop = 0, paneLeft = 121 },- PaneDetail { paneId = PaneId 3, paneWidth = 119, paneHeight = 29, paneTop = 31, paneLeft = 121 }- ]--test_nested :: UnitTest-test_nested =- renderSpec treeNested nestedTarget--treeTwoLayouts :: ViewTree-treeTwoLayouts =- Tree (consLayout id0) [- TreeNode $- Tree (consLayoutVertical id1) [- TreeLeaf (openPane id0 def)- ],- TreeNode $- Tree (consLayoutVertical id2) [- TreeLeaf (openPane id1 def)- ]- ]- where- openPane i geo = Ui.View i (ViewState False) geo (Ui.Pane True False Nothing)--twoLayoutsTarget :: [PaneDetail]-twoLayoutsTarget =- [- PaneDetail { paneId = PaneId 1, paneWidth = 120, paneHeight = 60, paneTop = 0, paneLeft = 0 },- PaneDetail { paneId = PaneId 2, paneWidth = 119, paneHeight = 60, paneTop = 0, paneLeft = 121 }- ]--test_twoLayouts :: UnitTest-test_twoLayouts =- renderSpec treeTwoLayouts twoLayoutsTarget--treePosition :: ViewTree-treePosition =- Tree (consLayout id0) [- TreeLeaf (openPane id0 1 (Just 10)),- TreeLeaf (openPane id1 4 (Just 20)),- TreeLeaf (openPane id2 2 (Just 30)),- TreeLeaf (openPane id3 3 Nothing)- ]- where- openPane i pos size =- Ui.View i (ViewState False) def { position = Just pos, fixedSize = size } (Ui.Pane True False Nothing)--positionTarget :: [PaneDetail]-positionTarget =- [- PaneDetail { paneId = PaneId 1, paneWidth = 10, paneHeight = 60, paneTop = 0, paneLeft = 0 },- PaneDetail { paneId = PaneId 2, paneWidth = 30, paneHeight = 60, paneTop = 0, paneLeft = 11 },- PaneDetail { paneId = PaneId 3, paneWidth = 177, paneHeight = 60, paneTop = 0, paneLeft = 42 },- PaneDetail { paneId = PaneId 4, paneWidth = 20, paneHeight = 60, paneTop = 0, paneLeft = 220 }- ]--test_position :: UnitTest-test_position =- renderSpec treePosition positionTarget--treeSuccessiveOpen :: ViewTree-treeSuccessiveOpen =- Tree (consLayout id0) [- TreeNode $ Tree (consLayoutVertical id1) [- TreeLeaf (pane id0 True False)- ],- TreeNode $ Tree (consLayoutVertical id2) [- TreeLeaf (pane id1 False True),- TreeLeaf (pane id2 False False)- ]- ]- where- pane i open pin =- Ui.View i (ViewState False) def (Ui.Pane open pin Nothing)--successiveOpenTarget :: [PaneDetail]-successiveOpenTarget =- [- PaneDetail { paneId = PaneId 1, paneWidth = 120, paneHeight = 60, paneTop = 0, paneLeft = 0 },- PaneDetail { paneId = PaneId 2, paneWidth = 119, paneHeight = 30, paneTop = 0, paneLeft = 121 },- PaneDetail { paneId = PaneId 3, paneWidth = 119, paneHeight = 29, paneTop = 31, paneLeft = 121 }- ]--test_successiveOpen :: UnitTest-test_successiveOpen = do- ps <- liftIO $ tmuxSpec \ api -> do- vs1 <- safeRender views treeSuccessiveOpen api- sleep 0.2- vs2 <- safeRender vs1 tree1 api- sleep 0.2- _ <- safeRender vs2 tree2 api- sleep 0.2- runExceptT @TmuxError $ runTmux api panesAs- ps1 <- evalEither (sortOn paneId . drop 1 <$> ps)- successiveOpenTarget === ps1- where- (ToggleResult.Success tree2) = togglePane id2 tree1- (ToggleResult.Success tree1) = togglePane id1 treeSuccessiveOpen--test_render :: TestTree-test_render =- testGroup "render" [- integrationTest "imbalanced tree" test_imbalanced,- integrationTest "nested panes" test_nested,- integrationTest "two layouts" test_twoLayouts,- integrationTest "fixed pane position" test_position,- integrationTest "open multiple panes" test_successiveOpen- ]
@@ -1,44 +0,0 @@-module Chiasma.Test.TmuxStreamTest where--import Chiasma.Codec.Data (Pane(Pane), Window(Window))-import Chiasma.Data.TmuxError (TmuxError)-import Chiasma.Data.TmuxId (PaneId(..), WindowId(..))-import Chiasma.Data.TmuxThunk (TmuxThunk)-import Chiasma.Monad.Stream (runTmux)-import qualified Chiasma.Monad.Tmux as Tmux (read, write)-import Chiasma.Native.Api (TmuxNative(..))-import Control.Monad.Free.Class (MonadFree)-import Hedgehog ((===))--import Chiasma.Test.Tmux (tmuxSpec)-import Chiasma.Test.Util (UnitTest)--prog :: MonadFree TmuxThunk m => m ([Pane], [Pane], [Window])-prog = do- panes1 <- Tmux.read "list-panes" ["-t", "%0"]- Tmux.write "new-window" []- Tmux.write "new-window" []- wins <- Tmux.read "list-windows" []- panes <- Tmux.read "list-panes" ["-a"]- return (panes1, panes, wins)--p :: Int -> Pane-p i = Pane (PaneId i) 240 60--w :: Int -> Window-w i = Window (WindowId i) 240 60--runProg :: TmuxNative -> IO (Either TmuxError ([Pane], [Pane], [Window]))-runProg api = runExceptT @TmuxError $ runTmux api prog--test_streamed :: UnitTest-test_streamed = do- result <- liftIO (tmuxSpec runProg)- (Right ([p0], [p0, p1, p2], [w0, w1, w2])) === result- where- p0 = p 0- p1 = p 1- p2 = p 2- w0 = w 0- w1 = w 1- w2 = w 2
@@ -1,19 +0,0 @@-module Chiasma.Test.Util where--import Hedgehog (TestT, withTests, property, test)-import Test.Tasty (TestName, TestTree)-import Test.Tasty.Hedgehog (testProperty)-import qualified System.Environment as Environment--type UnitTest = TestT IO ()--checkEnv :: UnitTest -> UnitTest-checkEnv t =- maybe unit (const t) =<< liftIO (Environment.lookupEnv "DISPLAY")--integrationTest ::- TestName ->- UnitTest ->- TestTree-integrationTest desc =- testProperty desc . withTests 1 . property . test . checkEnv
@@ -1,21 +0,0 @@-module Main where--import Chiasma.Test.IndividualProcessTest (test_individual)-import Chiasma.Test.ProcessTest (test_process)-import Chiasma.Test.RenderTest (test_render)-import Chiasma.Test.TmuxStreamTest (test_streamed)-import Chiasma.Test.Util (integrationTest)-import Test.Tasty (TestTree, defaultMain, testGroup)--tests :: TestTree-tests =- testGroup "all" [- integrationTest "use individual subprocesses for queries" test_individual,- integrationTest "tmux control process" test_process,- test_render,- integrationTest "streamed tmux commands" test_streamed- ]--main :: IO ()-main =- defaultMain tests
@@ -0,0 +1,12 @@+module Chiasma (+ TmuxApi,+ Tmux,+ Codec,+ NativeCodec,+ NativeCodecE,+ NativeCommandCodec,+ NativeCommandCodecE,+) where++import Chiasma.Effect.Codec (Codec, NativeCodec, NativeCodecE, NativeCommandCodec, NativeCommandCodecE)+import Chiasma.Effect.TmuxApi (Tmux, TmuxApi)
@@ -1,19 +0,0 @@-module Chiasma.Api.Class where--import Conduit (ConduitT, Flush)--import Chiasma.Codec.Decode (TmuxDecodeError)-import Chiasma.Data.Cmd (Cmd, Cmds)-import Chiasma.Native.StreamParse (TmuxOutputBlock)--class TmuxApi m a where- runCommands ::- a ->- (Text -> Either TmuxDecodeError b) ->- Cmds ->- m [b]-- withTmux ::- a ->- (ConduitT (Flush Cmd) Void m () -> ConduitT () TmuxOutputBlock m () -> m b) ->- m b
@@ -0,0 +1,55 @@+module Chiasma.Class.CmdArgs where++import qualified Data.Map.Strict as Map+import Exon (exon)++import Chiasma.Data.Ident (Ident, identText)++class CmdArgs a where+ cmdArgs :: a -> [Text]++flag :: [Text] -> Bool -> [Text]+flag =+ bool []++flag1 :: Text -> Bool -> [Text]+flag1 =+ flag . pure++optionWith :: Text -> (a -> Text) -> Maybe a -> [Text]+optionWith flg f =+ foldMap \ a -> [flg, f a]++option :: Text -> Maybe Text -> [Text]+option flg =+ optionWith flg id++identOption :: Text -> Maybe Ident -> [Text]+identOption flg =+ optionWith flg identText++arg :: Maybe Text -> [Text]+arg =+ maybeToList++envVars :: Map Text Text -> [Text]+envVars =+ concatMap \case+ (k, v) -> ["-e", [exon|#{k}=#{v}|]]+ .+ Map.toList++optionArgs ::+ CmdArgs a =>+ Maybe a ->+ [Text]+optionArgs =+ foldMap cmdArgs++optionArgsWith ::+ CmdArgs a =>+ Text ->+ Maybe a ->+ [Text]+optionArgsWith flg =+ foldMap \ a -> flg : cmdArgs a
@@ -1,37 +1,74 @@ module Chiasma.Codec where -import Chiasma.Data.TmuxId (PaneId, SessionId, WindowId)-import qualified Data.Text as Text (drop, dropEnd, splitOn, take, takeEnd, unwords)+import qualified Data.Text as Text import GHC.Generics (Rep, to)+import Prelude hiding (to) -import Chiasma.Codec.Decode (TmuxDataDecode(..), TmuxDecodeError(TooManyFields))-import Chiasma.Codec.Query (TmuxDataQuery(..))+import Chiasma.Codec.Decode (TmuxDataDecode (..))+import Chiasma.Codec.Query (TmuxDataQuery (..))+import qualified Chiasma.Data.DecodeError as DecodeFailure (DecodeFailure (..))+import Chiasma.Data.DecodeError (DecodeError (DecodeError), DecodeFailure)+import Chiasma.Data.TmuxId (PaneId, SessionId, WindowId)+import Chiasma.Data.TmuxQuery (TmuxQuery (TmuxQuery)) -newtype TmuxQuery =- TmuxQuery { unQ :: Text }- deriving (Eq, Show)+-- |Remove one leading and trailing space from tmux output if both are present.+tryTrim :: Text -> Maybe Text+tryTrim text = do+ (prefix, lastChar) <- Text.unsnoc text+ guard (lastChar == ' ')+ (firstChar, payload) <- Text.uncons prefix+ guard (firstChar == ' ')+ pure payload -genDecode :: (Generic a, TmuxDataDecode (Rep a)) => Text -> Either TmuxDecodeError a+trim :: Text -> Text+trim text =+ fromMaybe text (tryTrim text)++checkRemainder :: ([Text], b) -> Either DecodeFailure b+checkRemainder = \case+ ([], result) -> Right result+ (a, _) -> Left (DecodeFailure.TooManyFields a)++genDecode ::+ Generic a =>+ TmuxDataDecode (Rep a) =>+ Text ->+ Either DecodeError a genDecode fields = do- (rest, result) <- decode' (Text.splitOn " " . trim $ fields)- case rest of- [] -> return $ to result- a -> Left $ TooManyFields a- where- trim text =- if Text.take 1 text == " " && Text.takeEnd 1 text == " "- then Text.drop 1 . Text.dropEnd 1 $ text- else text+ bimap (DecodeError [fields]) to do+ checkRemainder =<< dataDecode (Text.splitOn " " (trim fields)) class TmuxCodec a where- decode :: Text -> Either TmuxDecodeError a- default decode :: (Generic a, TmuxDataDecode (Rep a)) => Text -> Either TmuxDecodeError a- decode = genDecode+ decode :: Text -> Either DecodeError a+ default decode ::+ Generic a =>+ TmuxDataDecode (Rep a) =>+ Text ->+ Either DecodeError a+ decode =+ genDecode - query :: TmuxQuery- default query :: TmuxDataQuery (Rep a) => TmuxQuery- query = TmuxQuery $ Text.unwords $ query' @(Rep a)+ query :: TmuxQuery+ default query :: TmuxDataQuery (Rep a) => TmuxQuery+ query =+ TmuxQuery (Text.unwords (dataQuery @(Rep a))) instance TmuxCodec SessionId instance TmuxCodec WindowId instance TmuxCodec PaneId++multi ::+ TmuxCodec a =>+ [Text] ->+ Either DecodeError [a]+multi =+ traverse decode++single ::+ TmuxCodec a =>+ [Text] ->+ Either DecodeError a+single = \case+ [out] -> decode out+ [] -> Left (DecodeError [] DecodeFailure.TargetMissing)+ out -> Left (DecodeError out (DecodeFailure.TooManyRecords out))
@@ -0,0 +1,13 @@+module Chiasma.Codec.Data.Client where++import Chiasma.Codec (TmuxCodec)+import Chiasma.Data.TmuxId (ClientId, SessionId)++data Client =+ Client {+ clientName :: ClientId,+ clientControlMode :: Bool,+ sessionId :: SessionId+ }+ deriving stock (Eq, Show, Generic)+ deriving anyclass (TmuxCodec)
@@ -2,15 +2,18 @@ import Chiasma.Codec (TmuxCodec) import Chiasma.Data.TmuxId (HasPaneId, PaneId)-import qualified Chiasma.Data.TmuxId as HasPaneId (HasPaneId(..))+import qualified Chiasma.Data.TmuxId as HasPaneId (HasPaneId (..)) data Pane = Pane { paneId :: PaneId, paneWidth :: Int,- paneHeight :: Int+ paneHeight :: Int,+ paneTop :: Int,+ paneLeft :: Int }- deriving (Eq, Show, Generic, TmuxCodec)+ deriving stock (Eq, Show, Generic)+ deriving anyclass (TmuxCodec) instance HasPaneId Pane where paneId = paneId
@@ -1,9 +1,8 @@ module Chiasma.Codec.Data.PaneCoords where -import Chiasma.Data.TmuxId (HasPaneId, PaneId, SessionId, WindowId)-import qualified Chiasma.Data.TmuxId as HasPaneId (HasPaneId(..))- import Chiasma.Codec (TmuxCodec)+import Chiasma.Data.TmuxId (HasPaneId, PaneId, SessionId, WindowId)+import qualified Chiasma.Data.TmuxId as HasPaneId (HasPaneId (..)) data PaneCoords = PaneCoords {@@ -11,7 +10,8 @@ windowId :: WindowId, paneId :: PaneId }- deriving (Eq, Show, Generic, TmuxCodec)+ deriving stock (Eq, Show, Generic)+ deriving anyclass (TmuxCodec) instance HasPaneId PaneCoords where paneId = paneId
@@ -1,18 +0,0 @@-module Chiasma.Codec.Data.PaneDetail where--import Chiasma.Codec (TmuxCodec)-import Chiasma.Data.TmuxId (HasPaneId, PaneId)-import qualified Chiasma.Data.TmuxId as HasPaneId (HasPaneId(..))--data PaneDetail =- PaneDetail {- paneId :: PaneId,- paneWidth :: Int,- paneHeight :: Int,- paneTop :: Int,- paneLeft :: Int- }- deriving (Eq, Show, Generic, TmuxCodec)--instance HasPaneId PaneDetail where- paneId = paneId
@@ -2,14 +2,15 @@ import Chiasma.Codec (TmuxCodec) import Chiasma.Data.TmuxId (HasPaneId, PaneId)-import qualified Chiasma.Data.TmuxId as HasPaneId (HasPaneId(paneId))+import qualified Chiasma.Data.TmuxId as HasPaneId (HasPaneId (paneId)) data PaneMode = PaneMode { paneId :: PaneId, paneMode :: Text }- deriving (Eq, Show, Generic, TmuxCodec)+ deriving stock (Eq, Show, Generic)+ deriving anyclass (TmuxCodec) instance HasPaneId PaneMode where paneId = paneId
@@ -2,14 +2,15 @@ import Chiasma.Codec (TmuxCodec) import Chiasma.Data.TmuxId (HasPaneId, PaneId)-import qualified Chiasma.Data.TmuxId as HasPaneId (HasPaneId(paneId))+import qualified Chiasma.Data.TmuxId as HasPaneId (HasPaneId (paneId)) data PanePid = PanePid { paneId :: PaneId, panePid :: Int }- deriving (Eq, Show, Generic, TmuxCodec)+ deriving stock (Eq, Show, Generic)+ deriving anyclass (TmuxCodec) instance HasPaneId PanePid where paneId = paneId
@@ -6,6 +6,8 @@ -- must be data for now because of how derivation of `TmuxCodec` is implemented data Session = Session {- sessionId :: SessionId+ sessionId :: SessionId,+ sessionName :: Text }- deriving (Eq, Show, Generic, TmuxCodec)+ deriving stock (Eq, Show, Generic)+ deriving anyclass (TmuxCodec)
@@ -9,4 +9,5 @@ windowWidth :: Int, windowHeight :: Int }- deriving (Eq, Show, Generic, TmuxCodec)+ deriving stock (Eq, Show, Generic)+ deriving anyclass (TmuxCodec)
@@ -1,58 +1,48 @@-{-# LANGUAGE TypeOperators #-}- module Chiasma.Codec.Decode where import qualified Data.Text as Text (null, unpack) import Data.Text.Read (decimal)-import GHC.Generics (K1(..), M1(..), (:*:)(..))+import GHC.Generics (K1 (..), M1 (..), (:*:) (..)) import Prelude hiding (many)-import Text.Parsec.Char (char, digit)-import Text.ParserCombinators.Parsec (- GenParser,- ParseError,- many,- parse,- )--import Chiasma.Data.TmuxId (PaneId(..), SessionId(..), WindowId(..), panePrefix, sessionPrefix, windowPrefix)+import Text.Parsec.Char (digit, string)+import Text.ParserCombinators.Parsec (GenParser, many, parse) -data TmuxDecodeError =- ParseFailure Text ParseError- |- IntParsingFailure Text- |- BoolParsingFailure Text- |- TooFewFields- |- TooManyFields [Text]- deriving (Eq, Show)+import Chiasma.Data.DecodeError (DecodeFailure (BoolParsingFailure, IntParsingFailure, ParseFailure, TooFewFields))+import Chiasma.Data.TmuxId (+ ClientId (ClientId),+ PaneId (..),+ SessionId (..),+ WindowId (..),+ panePrefix,+ sessionPrefix,+ windowPrefix,+ ) class TmuxPrimDecode a where- primDecode :: Text -> Either TmuxDecodeError a+ primDecode :: Text -> Either DecodeFailure a class TmuxDataDecode f where- decode' :: [Text] -> Either TmuxDecodeError ([Text], f a)+ dataDecode :: [Text] -> Either DecodeFailure ([Text], f a) instance (TmuxDataDecode f, TmuxDataDecode g) => TmuxDataDecode (f :*: g) where- decode' fields = do- (rest, left) <- decode' fields- (rest1, right) <- decode' rest- return (rest1, left :*: right)+ dataDecode fields = do+ (rest, left) <- dataDecode fields+ (rest1, right) <- dataDecode rest+ pure (rest1, left :*: right) instance TmuxDataDecode f => (TmuxDataDecode (M1 i c f)) where- decode' fields =- second M1 <$> decode' fields+ dataDecode fields =+ second M1 <$> dataDecode fields instance TmuxPrimDecode a => (TmuxDataDecode (K1 c a)) where- decode' (a:as) = do+ dataDecode (a:as') = do prim <- primDecode a- return (as, K1 prim)- decode' [] = Left TooFewFields+ pure (as', K1 prim)+ dataDecode [] = Left TooFewFields -readInt :: Text -> Text -> Either TmuxDecodeError Int-readInt input num =- first (const $ IntParsingFailure input) parsed+readInt :: Text -> Text -> Either DecodeFailure Int+readInt text num =+ first (const $ IntParsingFailure text) parsed where parsed = do (num', rest) <- decimal num@@ -72,15 +62,18 @@ convert _ = Left (BoolParsingFailure $ "got non-bool `" <> show field <> "`") -idParser :: Char -> GenParser Char st Text+idParser :: Text -> GenParser Char st Text idParser sym =- char sym *> (toText <$> many digit)+ string (toString sym) *> (toText <$> many digit) -parseId :: (Int -> a) -> Char -> Text -> Either TmuxDecodeError a-parseId cons sym input = do- num <- first (ParseFailure "id") $ parse (idParser sym) "none" (Text.unpack input)- i <- readInt input num- return $ cons i+parseId :: (Int -> a) -> Text -> Text -> Either DecodeFailure a+parseId cons sym text = do+ num <- first (ParseFailure "id") $ parse (idParser sym) "none" (Text.unpack text)+ i <- readInt text num+ pure (cons i)++instance TmuxPrimDecode ClientId where+ primDecode = pure . ClientId instance TmuxPrimDecode SessionId where primDecode = parseId SessionId sessionPrefix
@@ -1,6 +1,3 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE TypeOperators #-}- module Chiasma.Codec.Query where import Data.Char (isUpper, toLower)@@ -8,16 +5,16 @@ import GHC.Generics (C1, D1, S1, Selector, selName, (:*:)) class TmuxDataQuery f where- query' :: [Text]+ dataQuery :: [Text] instance TmuxDataQuery f => (TmuxDataQuery (D1 c f)) where- query' = query' @f+ dataQuery = dataQuery @f instance TmuxDataQuery f => (TmuxDataQuery (C1 c f)) where- query' = query' @f+ dataQuery = dataQuery @f instance (TmuxDataQuery f, TmuxDataQuery g) => TmuxDataQuery (f :*: g) where- query' = query' @f <> query' @g+ dataQuery = dataQuery @f <> dataQuery @g trans :: Char -> Text trans a | isUpper a = toText @String ['_', toLower a]@@ -31,7 +28,7 @@ formatQuery q = "#{" <> snakeCase q <> "}" instance Selector s => (TmuxDataQuery (S1 s f)) where- query' =+ dataQuery = [formatQuery (toText query)] where query = selName (undefined :: t s f p)
@@ -1,173 +1,160 @@ module Chiasma.Command.Pane where -import Data.List (dropWhileEnd)-import qualified Data.Text as Text (intercalate, singleton, splitOn)--import Chiasma.Codec (TmuxCodec)-import qualified Chiasma.Codec.Data as Codec (Pane)-import qualified Chiasma.Codec.Data.PaneCoords as Codec (PaneCoords)-import qualified Chiasma.Codec.Data.PaneMode as Codec (PaneMode(PaneMode))-import qualified Chiasma.Codec.Data.PanePid as Codec (PanePid)-import Chiasma.Data.TmuxId (HasPaneId, PaneId, WindowId, formatId)-import qualified Chiasma.Data.TmuxId as HasPaneId (paneId)-import Chiasma.Data.TmuxThunk (TmuxThunk)-import Chiasma.Data.View (View(View))-import qualified Chiasma.Monad.Tmux as Tmux (read, readRaw, unsafeReadFirst, write)-import Control.Monad.Free.Class (MonadFree)--paneTarget :: PaneId -> [Text]-paneTarget paneId =- ["-t", formatId paneId]--sameId :: HasPaneId a => PaneId -> a -> Bool-sameId target candidate = target == HasPaneId.paneId candidate+import Prelude hiding (output) -panesAs :: (MonadFree TmuxThunk m, TmuxCodec a) => m [a]-panesAs =- Tmux.read "list-panes" ["-a"]+import qualified Chiasma.Codec.Data.Pane as Codec (Pane)+import qualified Chiasma.Codec.Data.PaneMode as Codec (PaneMode (PaneMode))+import Chiasma.Data.Axis (Axis)+import Chiasma.Data.CapturePaneParams (CaptureOutput (Stdout), escapeSequences, output, target)+import Chiasma.Data.CopyModeParams (target)+import Chiasma.Data.KillPaneParams (target)+import qualified Chiasma.Data.PaneSelection as PaneSelection+import qualified Chiasma.Data.Panes as Panes+import Chiasma.Data.Panes (TmuxPanes)+import Chiasma.Data.PipePaneParams (command, target)+import Chiasma.Data.ResizePaneParams (ResizeAbsolute (ResizeAbsolute), absolute, target)+import Chiasma.Data.SelectParams (target)+import qualified Chiasma.Data.SendKeysParams as SendKeysParams+import Chiasma.Data.SendKeysParams (Key, keys, target)+import Chiasma.Data.SplitParams (JoinPaneParams (axis, detach, source, target))+import qualified Chiasma.Data.Target as Target+import Chiasma.Data.TmuxCommand (+ TmuxCommand (CapturePane, CopyMode, KillPane, MovePane, PipePane, ResizePane, SelectPane, SendKeys),+ )+import Chiasma.Data.TmuxId (PaneId, WindowId)+import Chiasma.Data.View (View (View))+import qualified Chiasma.Effect.TmuxApi as Tmux+import Chiasma.Effect.TmuxApi (Tmux) -panes :: MonadFree TmuxThunk m => m [Codec.Pane]+panes ::+ Member (TmuxPanes a) r =>+ Sem r [a] panes =- panesAs+ Tmux.send (Panes.List PaneSelection.All) -pane :: (MonadFree TmuxThunk m, TmuxCodec a, HasPaneId a) => PaneId -> m (Maybe a)+pane ::+ Member (TmuxPanes a) r =>+ PaneId ->+ Sem r (Maybe a) pane paneId =- find (sameId paneId) <$> Tmux.read "list-panes" ["-t", formatId paneId]--windowPanesAs :: (MonadFree TmuxThunk m, TmuxCodec a) => WindowId -> m [a]-windowPanesAs windowId =- Tmux.read "list-panes" ["-t", formatId windowId]+ Tmux.send (Panes.Find paneId) -windowPanes :: MonadFree TmuxThunk m => WindowId -> m [Codec.Pane]-windowPanes =- windowPanesAs+windowPanes ::+ Member (TmuxPanes a) r =>+ WindowId ->+ Sem r [a]+windowPanes windowId =+ Tmux.send (Panes.List (PaneSelection.InWindow (Target.Window windowId))) -firstWindowPane :: MonadFree TmuxThunk m => WindowId -> m Codec.Pane+firstWindowPane ::+ Member (TmuxPanes a) r =>+ WindowId ->+ Sem r a firstWindowPane windowId =- Tmux.unsafeReadFirst "list-panes" ["-t", formatId windowId]+ Tmux.send (Panes.First (PaneSelection.InWindow (Target.Window windowId))) -closePane :: MonadFree TmuxThunk m => PaneId -> m ()+closePane ::+ Member Tmux r =>+ PaneId ->+ Sem r () closePane paneId =- Tmux.write "kill-pane" (paneTarget paneId)+ Tmux.send (KillPane def { target = Target.Pane paneId }) isPaneIdOpen ::- MonadFree TmuxThunk m =>+ Member (TmuxPanes Codec.Pane) r => PaneId ->- m Bool+ Sem r Bool isPaneIdOpen paneId =- any (sameId paneId) <$> panes+ isJust <$> pane paneId isPaneOpen ::- MonadFree TmuxThunk m =>+ Member (TmuxPanes Codec.Pane) r => View PaneId ->- m Bool+ Sem r Bool isPaneOpen (View _ (Just paneId)) = isPaneIdOpen paneId-isPaneOpen _ = return False+isPaneOpen _ =+ pure False movePane ::- MonadFree TmuxThunk m =>+ Member Tmux r => PaneId -> PaneId ->- Bool ->- m ()-movePane paneId refId vertical =- Tmux.write "move-pane" ["-d", "-s", formatId paneId, "-t", formatId refId, direction]- where- direction = if vertical then "-v" else "-h"+ Axis ->+ Sem r ()+movePane paneId refId axis =+ Tmux.send (MovePane def {+ detach = True,+ source = Just (Target.Pane paneId),+ target = Target.Pane refId,+ axis = Just axis+ }) resizePane ::- MonadFree TmuxThunk m =>+ Member Tmux r => PaneId ->- Bool ->+ Axis -> Int ->- m ()-resizePane paneId vertical size =- Tmux.write "resize-pane" ["-t", formatId paneId, direction, show size]- where- direction = if vertical then "-y" else "-x"--formatLine :: Text -> [Text]-formatLine line =- ["\"" <> replace "\"" "\\\"" line <> Text.singleton '"', "enter"]- where- replace from to =- Text.intercalate to . Text.splitOn from+ Sem r ()+resizePane paneId axis size =+ Tmux.send (ResizePane def {+ target = Target.Pane paneId,+ absolute = Just (ResizeAbsolute axis size)+ }) sendKeys ::- MonadFree TmuxThunk m =>+ Member Tmux r => PaneId ->- [Text] ->- m ()+ [Key] ->+ Sem r () sendKeys paneId lines' =- traverse_ send formatted- where- formatted = lines' >>= formatLine- send line = Tmux.write "send-keys" (paneTarget paneId <> [line])+ for_ lines' \ line ->+ Tmux.send (SendKeys def { target = Target.Pane paneId, keys = [line] }) pipePane ::- MonadFree TmuxThunk m =>+ Member Tmux r => PaneId -> Text ->- m ()+ Sem r () pipePane paneId cmd =- Tmux.write "pipe-pane" (paneTarget paneId <> [cmd])+ Tmux.schedule (PipePane def { target = Target.Pane paneId, command = Just cmd }) capturePane ::- MonadFree TmuxThunk m =>- PaneId ->- m [Text]-capturePane paneId = do- lines' <- Tmux.readRaw "capture-pane" (paneTarget paneId <> ["-p", "-e"])- return $ dropWhileEnd ("" ==) lines'--panePid ::- MonadFree TmuxThunk m =>- PaneId ->- m (Maybe Codec.PanePid)-panePid =- pane--panePids ::- MonadFree TmuxThunk m =>- m [Codec.PanePid]-panePids =- panesAs--paneCoords ::- MonadFree TmuxThunk m =>+ Member Tmux r => PaneId ->- m (Maybe Codec.PaneCoords)-paneCoords =- pane+ Sem r [Text]+capturePane paneId =+ Tmux.send (CapturePane def { target = Target.Pane paneId, escapeSequences = True, output = Just Stdout}) selectPane ::- MonadFree TmuxThunk m =>+ Member Tmux r => PaneId ->- m ()+ Sem r () selectPane paneId =- Tmux.write "select-pane" (paneTarget paneId)+ Tmux.send (SelectPane def { target = Target.Pane paneId }) copyMode ::- MonadFree TmuxThunk m =>+ Member Tmux r => PaneId ->- m ()-copyMode =- Tmux.write "copy-mode" . paneTarget+ Sem r ()+copyMode paneId =+ Tmux.send (CopyMode def { target = Target.Pane paneId }) paneMode ::- MonadFree TmuxThunk m =>+ Member (TmuxPanes Codec.PaneMode) r => PaneId ->- m (Maybe Codec.PaneMode)+ Sem r (Maybe Codec.PaneMode) paneMode = pane quitCopyMode ::- MonadFree TmuxThunk m =>+ Member Tmux r =>+ Member (TmuxPanes Codec.PaneMode) r => PaneId ->- m ()+ Sem r () quitCopyMode paneId = traverse_ check =<< pane paneId where check (Codec.PaneMode _ mode) =- when (mode == "copy-mode") (sendKeys paneId ["C-c"])+ when (mode == "copy-mode") do+ Tmux.send (SendKeys def { target = Target.Pane paneId, SendKeysParams.copyMode = True, keys = ["cancel"] })
@@ -1,33 +1,61 @@ module Chiasma.Command.Session where -import Control.Monad.Free.Class (MonadFree)--import Chiasma.Codec.Data (Session(Session))-import Chiasma.Data.Ident (Ident, identText)+import Chiasma.Codec.Data.Client (Client (Client))+import Chiasma.Codec.Data.Session (Session (Session))+import Chiasma.Data.Ident (Ident)+import Chiasma.Data.SessionParams (name)+import qualified Chiasma.Data.Target as Target+import Chiasma.Data.TmuxCommand (TmuxCommand (ListClients, ListSessions, NewSession, SwitchClient))+import Chiasma.Data.TmuxError (TmuxError (NoClients)) import Chiasma.Data.TmuxId (SessionId)-import Chiasma.Data.TmuxThunk (TmuxThunk)-import qualified Chiasma.Monad.Tmux as Tmux (read, unsafeReadOne, write)+import qualified Chiasma.Effect.TmuxApi as Tmux+import Chiasma.Effect.TmuxApi (Tmux) sameId :: SessionId -> Session -> Bool-sameId target (Session i) = target == i+sameId target (Session i _) = target == i -sessions :: MonadFree TmuxThunk m => m [Session]+sessions ::+ Member Tmux r =>+ Sem r [Session] sessions =- Tmux.read "list-sessions" []+ Tmux.send ListSessions -doesSessionExist :: MonadFree TmuxThunk m => SessionId -> m Bool+doesSessionExist ::+ Member Tmux r =>+ SessionId ->+ Sem r Bool doesSessionExist sessionId = any (sameId sessionId) <$> sessions -existingSessionId :: MonadFree TmuxThunk m => SessionId -> m (Maybe SessionId)+existingSessionId ::+ Member Tmux r =>+ SessionId ->+ Sem r (Maybe SessionId) existingSessionId sessionId = do exists <- doesSessionExist sessionId- return $ if exists then Just sessionId else Nothing+ pure $ if exists then Just sessionId else Nothing -newSession :: MonadFree TmuxThunk m => Ident -> m Session+newSession ::+ Member Tmux r =>+ Ident ->+ Sem r Session newSession name =- Tmux.unsafeReadOne "new-session" ["-s", identText name, "-P"]+ Tmux.send (NewSession def { name = Just name }) -activateSession :: MonadFree TmuxThunk m => Int -> m ()-activateSession sessionId =- Tmux.write "send-keys" ["-t", "%1", "'tmux switch-client -t \\$" <> show sessionId <> "'", "enter"]+clientForSession :: SessionId -> [Client] -> Maybe Client+clientForSession session =+ find \case+ Client _ False sid ->+ sid == session+ _ ->+ False++switchClient ::+ Members [Tmux, Stop TmuxError] r =>+ SessionId ->+ SessionId ->+ Sem r ()+switchClient clientSession session = do+ clients <- Tmux.send ListClients+ Client client _ _ <- stopNote NoClients (clientForSession clientSession clients)+ Tmux.send (SwitchClient client (Target.Session session))
@@ -1,53 +1,79 @@ module Chiasma.Command.Window where -import Control.Monad.Free.Class (MonadFree)+import Exon (exon)+import Path (Abs, Dir, Path) -import Chiasma.Codec (TmuxCodec)-import Chiasma.Codec.Data (Pane, Window(Window))-import Chiasma.Data.Ident (Ident, identText)-import Chiasma.Data.TmuxId (SessionId, TmuxId(formatId), WindowId)-import Chiasma.Data.TmuxThunk (TmuxThunk)-import qualified Chiasma.Monad.Tmux as Tmux (read, unsafeReadFirst, unsafeReadOne)+import Chiasma.Codec.Data.Pane (Pane)+import Chiasma.Codec.Data.Window (Window (Window))+import Chiasma.Data.Ident (Ident)+import Chiasma.Data.LayoutError (LayoutError (LayoutError))+import qualified Chiasma.Data.Target as Target+import Chiasma.Data.TmuxCommand (TmuxCommand (ListWindows, NewWindow, SplitWindow))+import Chiasma.Data.TmuxId (SessionId, WindowId)+import Chiasma.Data.WindowParams (cwd, detach, name, target)+import qualified Chiasma.Data.WindowSelection as WindowSelection+import qualified Chiasma.Effect.TmuxApi as Tmux+import Chiasma.Effect.TmuxApi (Tmux) sameId :: WindowId -> Window -> Bool sameId target (Window i _ _) = target == i -windows :: MonadFree TmuxThunk m => m [Window]+windows ::+ Member Tmux r =>+ Sem r [Window] windows =- Tmux.read "list-windows" ["-a"]+ Tmux.send (ListWindows WindowSelection.All) -window :: MonadFree TmuxThunk m => WindowId -> m (Maybe Window)+window ::+ Member Tmux r =>+ WindowId ->+ Sem r (Maybe Window) window windowId = find (sameId windowId) <$> windows -sessionWindows :: MonadFree TmuxThunk m => SessionId -> m [Window]+sessionWindows ::+ Member Tmux r =>+ SessionId ->+ Sem r [Window] sessionWindows sid =- Tmux.read "list-windows" ["-t", formatId sid]+ Tmux.send (ListWindows (WindowSelection.InSession (Target.Session sid))) -newSessionWindow :: MonadFree TmuxThunk m => SessionId -> m Window+-- TODO this could be ListWindow+newSessionWindow ::+ Members [Tmux, Stop LayoutError] r =>+ SessionId ->+ Sem r Window newSessionWindow sid =- Tmux.unsafeReadOne "list-windows" ["-t", formatId sid]+ sessionWindows sid >>= \case+ [w] -> pure w+ ws -> stop (LayoutError [exon|New session contains multiple windows: #{show ws}|]) -doesWindowExist :: MonadFree TmuxThunk m => WindowId -> m Bool+doesWindowExist ::+ Member Tmux r =>+ WindowId ->+ Sem r Bool doesWindowExist windowId = any (sameId windowId) <$> windows -newWindow :: MonadFree TmuxThunk m => SessionId -> Ident -> m Window+newWindow ::+ Member Tmux r =>+ SessionId ->+ Ident ->+ Sem r Window newWindow sid name =- Tmux.unsafeReadOne "new-window" ["-t", formatId sid, "-n", identText name, "-P"]+ Tmux.send (NewWindow def { target = Target.Session sid, name = Just name }) -splitWindowAs ::- (MonadFree TmuxThunk m, TmuxCodec a) =>- FilePath ->+splitWindowInDir ::+ Member Tmux r =>+ Path Abs Dir -> WindowId ->- m a-splitWindowAs dir windowId =- Tmux.unsafeReadFirst "split-window" ["-t", formatId windowId, "-d", "-P", "-c", toText dir]+ Sem r Pane+splitWindowInDir dir windowId =+ Tmux.send (SplitWindow def { target = Target.Window windowId, detach = True, cwd = Just dir } def) splitWindow ::- (MonadFree TmuxThunk m) =>- FilePath ->+ Member Tmux r => WindowId ->- m Pane-splitWindow =- splitWindowAs+ Sem r Pane+splitWindow windowId =+ Tmux.send (SplitWindow def { target = Target.Window windowId, detach = True } def)
@@ -3,4 +3,4 @@ import GHC.IO.Unsafe (unsafePerformIO) unsafeLog :: Show a => a -> b -> b-unsafeLog a b = unsafePerformIO $ print a >> return b+unsafeLog a b = unsafePerformIO $ print a >> pure b
@@ -0,0 +1,31 @@+module Chiasma.Data.Axis where++import Data.Data (Data)+import Prettyprinter (Pretty (pretty))++import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs))++data Axis =+ Horizontal+ |+ Vertical+ deriving stock (Eq, Show, Data)++instance Pretty Axis where+ pretty = \case+ Horizontal -> "h"+ Vertical -> "v"++instance CmdArgs Axis where+ cmdArgs = \case+ Horizontal -> ["-h"]+ Vertical -> ["-v"]++instance Default Axis where+ def =+ Vertical++vertical :: Axis -> Bool+vertical = \case+ Horizontal -> False+ Vertical -> True
@@ -0,0 +1,84 @@+module Chiasma.Data.CapturePaneParams where++import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs), flag1, optionArgs, optionArgsWith)+import qualified Chiasma.Data.Target as Target+import Chiasma.Data.Target (Target)+import Prelude hiding (output)++data CaptureOutput =+ Stdout+ |+ Buffer Text+ deriving stock (Eq, Show)++instance CmdArgs CaptureOutput where+ cmdArgs = \case+ Stdout -> ["-p"]+ Buffer name -> ["-b", name]++data CaptureLine =+ Edge+ |+ CaptureLine Int+ deriving stock (Eq, Show)++instance CmdArgs CaptureLine where+ cmdArgs = \case+ Edge -> ["-"]+ CaptureLine n -> [show n]++data CapturePaneParams =+ CapturePaneParams {+ output :: Maybe CaptureOutput,+ alternate :: Bool,+ quiet :: Bool,+ escapeSequences :: Bool,+ octal :: Bool,+ joinWrapped :: Bool,+ incomplete :: Bool,+ startLine :: Maybe CaptureLine,+ endLine :: Maybe CaptureLine,+ target :: Target,+ stripBlank :: Bool,+ stripTrailingWs :: Bool+ }+ deriving stock (Eq, Show)++instance Default CapturePaneParams where+ def =+ CapturePaneParams {+ output = Nothing,+ alternate = False,+ quiet = False,+ escapeSequences = False,+ octal = False,+ joinWrapped = True,+ incomplete = False,+ startLine = Nothing,+ endLine = Nothing,+ target = Target.Current,+ stripBlank = True,+ stripTrailingWs = True+ }++instance CmdArgs CapturePaneParams where+ cmdArgs CapturePaneParams {..} =+ flag1 "-a" alternate+ <>+ flag1 "-q" quiet+ <>+ flag1 "-e" escapeSequences+ <>+ flag1 "-C" octal+ <>+ flag1 "-J" joinWrapped+ <>+ flag1 "-P" incomplete+ <>+ optionArgs output+ <>+ optionArgsWith "-S" startLine+ <>+ optionArgsWith "-E" endLine+ <>+ cmdArgs target
@@ -8,19 +8,19 @@ newtype CmdName = CmdName Text- deriving (Eq, Show)+ deriving stock (Eq, Show) newtype CmdArgs = CmdArgs [Text]- deriving (Eq, Show)+ deriving stock (Eq, Show) data Cmd = Cmd CmdName CmdArgs- deriving (Eq, Show)+ deriving stock (Eq, Show) newtype Cmds = Cmds [Cmd]- deriving (Eq, Show)+ deriving stock (Eq, Show) cmd :: Text -> [Text] -> Cmd cmd name args = Cmd (CmdName name) (CmdArgs args)
@@ -0,0 +1,8 @@+module Chiasma.Data.CodecError where++import Chiasma.Data.DecodeError (DecodeError)+import Chiasma.Data.TmuxRequest (TmuxRequest)++data CodecError =+ CodecError TmuxRequest DecodeError+ deriving stock (Eq, Show)
@@ -1,10 +0,0 @@-{-# LANGUAGE DataKinds #-}--module Chiasma.Data.Conduit where--import Conduit (ConduitT, Flush)-import Data.Conduit.Binary (sinkHandleFlush)-import Data.Conduit.Process.Typed (StreamSpec, StreamType(STInput), createPipe)--createSinkFlush :: MonadIO m => StreamSpec 'STInput (ConduitT (Flush ByteString) o m ())-createSinkFlush = sinkHandleFlush <$> createPipe
@@ -0,0 +1,32 @@+module Chiasma.Data.CopyModeParams where++import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs), flag1)+import Chiasma.Data.Target (Target)++data CopyModeParams =+ CopyModeParams {+ mouseDrag :: Bool,+ scrollUp :: Bool,+ exitBottom :: Bool,+ target :: Target+ }+ deriving stock (Eq, Show)++instance Default CopyModeParams where+ def =+ CopyModeParams {+ mouseDrag = False,+ scrollUp = False,+ exitBottom = False,+ target = def+ }++instance CmdArgs CopyModeParams where+ cmdArgs CopyModeParams {..} =+ flag1 "-M" mouseDrag+ <>+ flag1 "-u" scrollUp+ <>+ flag1 "-e" exitBottom+ <>+ cmdArgs target
@@ -0,0 +1,26 @@+module Chiasma.Data.DecodeError where++import Text.ParserCombinators.Parsec (ParseError)++data DecodeFailure =+ ParseFailure Text ParseError+ |+ IntParsingFailure Text+ |+ BoolParsingFailure Text+ |+ TooFewFields+ |+ TooManyFields [Text]+ |+ TooManyRecords [Text]+ |+ TargetMissing+ deriving stock (Eq, Show)++data DecodeError =+ DecodeError {+ output :: [Text],+ failure :: DecodeFailure+ }+ deriving stock (Eq, Show)
@@ -0,0 +1,22 @@+module Chiasma.Data.Direction where++import Prelude hiding (Down, Left, Right)++import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs))++data Direction =+ Up+ |+ Down+ |+ Left+ |+ Right+ deriving stock (Eq, Show)++instance CmdArgs Direction where+ cmdArgs = \case+ Up -> ["-U"]+ Down -> ["-D"]+ Left -> ["-L"]+ Right -> ["-R"]
@@ -0,0 +1,7 @@+module Chiasma.Data.Event where++import Chiasma.Data.TmuxId (WindowId)++data Event =+ WindowClosed WindowId+ deriving stock (Eq, Show)
@@ -1,21 +1,18 @@ module Chiasma.Data.Ident where -import Data.Aeson (FromJSON, ToJSON(toEncoding), defaultOptions, genericToEncoding) import Data.Data (Data)-import Data.Text.Prettyprint.Doc (Pretty(..)) import Data.UUID (UUID) import qualified Data.UUID as UUID (fromText, toText)+import Prettyprinter (Pretty (..)) import System.Random (randomIO)-import qualified Text.Show as Show data Ident = Str Text | Uuid UUID- deriving (Eq, Generic, Data, NFData, Ord)+ deriving stock (Eq, Show, Ord, Generic, Data) -instance Show Ident where- show = toString . identText+json ''Ident class Identifiable a where identify :: a -> Ident@@ -24,19 +21,18 @@ identify = id instance Pretty Ident where- pretty (Str s) = pretty s- pretty (Uuid u) = pretty . UUID.toText $ u+ pretty =+ pretty . \case+ Str s -> s+ Uuid u -> UUID.toText u instance Default Ident where- def = Str ""+ def =+ Str "" instance IsString Ident where- fromString = Str . toText--instance FromJSON Ident where--instance ToJSON Ident where- toEncoding = genericToEncoding defaultOptions+ fromString =+ Str . toText sameIdent :: Identifiable a =>
@@ -0,0 +1,24 @@+module Chiasma.Data.KillPaneParams where++import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs), flag1)+import Chiasma.Data.Target (Target)++data KillPaneParams =+ KillPaneParams {+ allbut :: Bool,+ target :: Target+ }+ deriving stock (Eq, Show)++instance Default KillPaneParams where+ def =+ KillPaneParams {+ allbut = False,+ target = def+ }++instance CmdArgs KillPaneParams where+ cmdArgs KillPaneParams {..} =+ flag1 "-a" allbut+ <>+ cmdArgs target
@@ -0,0 +1,6 @@+module Chiasma.Data.LayoutError where++newtype LayoutError =+ LayoutError { unLayoutError :: Text }+ deriving stock (Eq, Show, Generic)+ deriving newtype (IsString)
@@ -1,17 +0,0 @@-module Chiasma.Data.Maybe where--import Control.Monad.Error.Class (MonadError(throwError))--maybeExcept :: MonadError e m => e -> Maybe a -> m a-maybeExcept e Nothing = throwError e-maybeExcept _ (Just a) = return a--findMaybe :: (a -> Maybe b) -> [a] -> Maybe b-findMaybe f as =- case mapMaybe f as of- (a : _) -> Just a- _ -> Nothing--orElse :: Maybe a -> Maybe a -> Maybe a-orElse _ (Just a) = Just a-orElse fallback Nothing = fallback
@@ -0,0 +1,22 @@+module Chiasma.Data.PaneSelection where++import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs))+import Chiasma.Data.Target (Target (Current))++data PaneSelection =+ All+ |+ InSession Target+ |+ InWindow Target+ deriving stock (Eq, Show)++instance CmdArgs PaneSelection where+ cmdArgs = \case+ All -> ["-a"]+ InSession target -> ["-s"] <> cmdArgs target+ InWindow target -> cmdArgs target++instance Default PaneSelection where+ def =+ InWindow Current
@@ -0,0 +1,105 @@+module Chiasma.Data.Panes where++import Chiasma.Class.CmdArgs (cmdArgs)+import qualified Chiasma.Codec as Codec+import Chiasma.Codec (TmuxCodec, multi, single)+import qualified Chiasma.Data.DecodeError as DecodeFailure+import Chiasma.Data.DecodeError (DecodeError (DecodeError))+import qualified Chiasma.Data.PaneSelection as PaneSelection+import Chiasma.Data.PaneSelection (PaneSelection)+import qualified Chiasma.Data.Target as Target+import Chiasma.Data.TmuxId (PaneId)+import Chiasma.Data.TmuxQuery (TmuxQuery)+import Chiasma.Data.TmuxRequest (TmuxRequest (TmuxRequest))+import Chiasma.Data.TmuxResponse (TmuxResponse (TmuxResponse))+import Chiasma.Data.WithPaneId (WithPaneId (WithPaneId))+import Chiasma.Effect.TmuxApi (TmuxApi)++-- |A 'TmuxApi' command for listing panes, with different query criteria.+-- The constructors taking a 'PaneSelection' list all panes that are present in the selected scope, but may constrain+-- the return value.+-- The constructors 'Get' and 'Find' return only the pane with the requested ID.+data Panes (p :: Type) (a :: Type) :: Type where+ -- |Return all panes covered by the selection.+ List :: PaneSelection -> Panes p [p]+ -- |Return one pane covered by the selection, fail if there is none.+ First :: PaneSelection -> Panes p p+ -- |Return one pane covered by the selection, fail if there is none or more than one.+ One :: PaneSelection -> Panes p p+ -- |Return the pane with the specified ID, fail if there is none.+ Get :: PaneId -> Panes p p+ -- |Return the pane with the specified ID if it exists.+ Find :: PaneId -> Panes p (Maybe p)++type TmuxPanes p =+ TmuxApi (Panes p)++query ::+ ∀ p a .+ TmuxCodec p =>+ Panes p a ->+ TmuxQuery+query _ =+ Codec.query @p++selection :: Panes p a -> PaneSelection+selection = \case+ List ps -> ps+ First ps -> ps+ One ps -> ps+ Get i -> PaneSelection.InWindow (Target.Pane i)+ Find _ -> PaneSelection.All++request ::+ Panes p a ->+ TmuxQuery ->+ TmuxRequest+request (selection -> s) =+ TmuxRequest "list-panes" (cmdArgs s) . Just++encode ::+ ∀ p a .+ TmuxCodec p =>+ Panes p a ->+ TmuxRequest+encode (Get i) =+ request (Get i) (Codec.query @(WithPaneId p))+encode (Find i) =+ request (Find i) (Codec.query @(WithPaneId p))+encode cmd =+ request cmd (query cmd)++sameId :: PaneId -> WithPaneId a -> Maybe a+sameId target (WithPaneId paneId p)+ | target == paneId = Just p+ | otherwise = Nothing++paneById ::+ TmuxCodec p =>+ PaneId ->+ [Text] ->+ Either DecodeError (Maybe p)+paneById paneId out = do+ firstJust (sameId paneId) <$> multi out++decode ::+ ∀ p a .+ TmuxCodec p =>+ TmuxResponse ->+ Panes p a ->+ Either DecodeError a+decode (TmuxResponse out) = \case+ List _ ->+ multi out+ First _ ->+ multi out >>= \case+ [] -> Left (DecodeError out DecodeFailure.TooFewFields)+ a : _ -> Right a+ One _ ->+ single out+ Get paneId -> do+ ps <- multi @(WithPaneId p) out+ maybeToRight (DecodeError out DecodeFailure.TargetMissing) (firstJust (sameId paneId) ps)+ Find paneId -> do+ ps <- multi @(WithPaneId p) out+ pure (firstJust (sameId paneId) ps)
@@ -0,0 +1,38 @@+module Chiasma.Data.PipePaneParams where++import Prelude hiding (input, output)++import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs), arg, flag1)+import Chiasma.Data.Target (Target (Current))++data PipePaneParams =+ PipePaneParams {+ input :: Bool,+ output :: Bool,+ onlyNew :: Bool,+ target :: Target,+ command :: Maybe Text+ }+ deriving stock (Eq, Show)++instance Default PipePaneParams where+ def =+ PipePaneParams {+ input = False,+ output = False,+ onlyNew = False,+ target = Current,+ command = Nothing+ }++instance CmdArgs PipePaneParams where+ cmdArgs PipePaneParams {..} =+ flag1 "-I" input+ <>+ flag1 "-O" output+ <>+ flag1 "-o" onlyNew+ <>+ cmdArgs target+ <>+ arg command
@@ -0,0 +1,5 @@+module Chiasma.Data.ProcessError where++data ProcessError =+ Terminated Text+ deriving stock (Eq, Show)
@@ -0,0 +1,6 @@+module Chiasma.Data.ReceiveEvent where++import Chiasma.Data.Event (Event)++data ReceiveEvent :: Type -> Type where+ ReceiveEvent :: ReceiveEvent Event
@@ -1,6 +1,8 @@ module Chiasma.Data.RenderError where +import Chiasma.Data.CodecError (CodecError) import Chiasma.Data.Ident (Ident)+import Chiasma.Data.LayoutError (LayoutError) import Chiasma.Data.TmuxError (TmuxError) import Chiasma.Data.Views (ViewsError) @@ -9,9 +11,11 @@ | Views ViewsError |+ Layout LayoutError+ | Pack Text | Fatal TmuxError- deriving (Eq, Show)--deepPrisms ''RenderError+ |+ Codec CodecError+ deriving stock (Eq, Show)
@@ -0,0 +1,58 @@+module Chiasma.Data.ResizePaneParams where++import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs), arg, flag1, optionArgs)+import Chiasma.Data.Axis (Axis (Horizontal, Vertical))+import Chiasma.Data.Direction (Direction)+import Chiasma.Data.Target (Target (Current))++data ResizeAbsolute =+ ResizeAbsolute Axis Int+ deriving stock (Eq, Show)++instance CmdArgs ResizeAbsolute where+ cmdArgs (ResizeAbsolute axis amount) =+ [axisArg axis, show amount]+ where+ axisArg = \case+ Horizontal -> "-x"+ Vertical -> "-y"++data Adjustment =+ Adjustment (Maybe Direction) (Maybe Int)+ deriving stock (Eq, Show)++instance CmdArgs Adjustment where+ cmdArgs (Adjustment dir amount) =+ optionArgs dir <> arg (show <$> amount)++data ResizePaneParams =+ ResizePaneParams {+ absolute :: Maybe ResizeAbsolute,+ adjustment :: Maybe Adjustment,+ zoom :: Bool,+ mouse :: Bool,+ target :: Target+ }+ deriving stock (Eq, Show)++instance Default ResizePaneParams where+ def =+ ResizePaneParams {+ absolute = Nothing,+ adjustment = Nothing,+ zoom = False,+ mouse = False,+ target = Current+ }++instance CmdArgs ResizePaneParams where+ cmdArgs ResizePaneParams {..} =+ optionArgs absolute+ <>+ flag1 "-Z" zoom+ <>+ flag1 "-M" mouse+ <>+ optionArgs adjustment+ <>+ cmdArgs target
@@ -0,0 +1,65 @@+module Chiasma.Data.SelectParams where++import Prelude hiding (input, last)++import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs), flag1, option, optionArgs)+import Chiasma.Data.Direction (Direction)+import Chiasma.Data.Target (Target)++data SelectInput =+ EnableInput+ |+ DisableInput+ deriving stock (Eq, Show)++instance CmdArgs SelectInput where+ cmdArgs = \case+ EnableInput -> ["-e"]+ DisableInput -> ["-d"]++data SelectMark =+ SelectMark+ |+ SelectUnmark+ deriving stock (Eq, Show)++instance CmdArgs SelectMark where+ cmdArgs = \case+ SelectMark -> ["-m"]+ SelectUnmark -> ["-M"]++data SelectParams =+ SelectParams {+ neighbor :: Maybe Direction,+ last :: Bool,+ input :: Maybe SelectInput,+ mark :: Maybe SelectMark,+ title :: Maybe Text,+ target :: Target+ }+ deriving stock (Eq, Show)++instance Default SelectParams where+ def =+ SelectParams {+ neighbor = Nothing,+ last = False,+ input = Nothing,+ mark = Nothing,+ title = Nothing,+ target = def+ }++instance CmdArgs SelectParams where+ cmdArgs SelectParams {..} =+ optionArgs neighbor+ <>+ flag1 "-l" last+ <>+ optionArgs input+ <>+ optionArgs mark+ <>+ option "-T" title+ <>+ cmdArgs target
@@ -0,0 +1,38 @@+module Chiasma.Data.SelectWindowParams where++import Prelude hiding (last)++import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs), flag1)+import Chiasma.Data.Target (Target)++data SelectWindowParams =+ SelectWindowParams {+ last :: Bool,+ next :: Bool,+ previous :: Bool,+ toggle :: Bool,+ target :: Target+ }+ deriving stock (Eq, Show)++instance Default SelectWindowParams where+ def =+ SelectWindowParams {+ last = False,+ next = False,+ previous = False,+ toggle = False,+ target = def+ }++instance CmdArgs SelectWindowParams where+ cmdArgs SelectWindowParams {..} =+ flag1 "-l" last+ <>+ flag1 "-n" next+ <>+ flag1 "-p" previous+ <>+ flag1 "-T" toggle+ <>+ cmdArgs target
@@ -0,0 +1,78 @@+module Chiasma.Data.SendKeysParams where++import qualified Data.Text as Text+import Exon (exon)+import Prelude hiding (repeat)++import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs), flag1, optionWith)+import Chiasma.Data.Target (Target)++escape :: Text -> Text+escape fragment =+ [exon|"#{escapeQuotes}"|]+ where+ escapeQuotes =+ Text.replace [exon|"|] [exon|\"|] fragment++data Key =+ Key Text+ |+ Lit Text+ deriving stock (Eq, Show)++instance IsString Key where+ fromString =+ Lit . toText++instance CmdArgs Key where+ cmdArgs = \case+ Key k -> [k]+ Lit s -> [escape s]++data SendKeysParams =+ SendKeysParams {+ enter :: Bool,+ literal :: Bool,+ hex :: Bool,+ reset :: Bool,+ mouse :: Bool,+ copyMode :: Bool,+ repeat :: Maybe Int,+ keys :: [Key],+ target :: Target+ }+ deriving stock (Eq, Show)++instance Default SendKeysParams where+ def =+ SendKeysParams {+ enter = True,+ literal = False,+ hex = False,+ reset = False,+ mouse = False,+ copyMode = False,+ repeat = Nothing,+ keys = mempty,+ target = def+ }++instance CmdArgs SendKeysParams where+ cmdArgs SendKeysParams {..} =+ flag1 "-l" literal+ <>+ flag1 "-H" hex+ <>+ flag1 "-R" reset+ <>+ flag1 "-M" mouse+ <>+ flag1 "-X" copyMode+ <>+ optionWith "-N" show repeat+ <>+ cmdArgs target+ <>+ (cmdArgs =<< keys)+ <>+ flag1 "enter" (enter && not copyMode)
@@ -0,0 +1,6 @@+module Chiasma.Data.SessionGroupId where++newtype SessionGroupId =+ SessionGroupId { unSessionGroupId :: Text }+ deriving stock (Eq, Show, Generic)+ deriving newtype (IsString)
@@ -0,0 +1,72 @@+module Chiasma.Data.SessionParams where++import Path (Abs, Dir, Path)++import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs), arg, envVars, flag1, identOption, optionWith)+import Chiasma.Data.Ident (Ident)+import Chiasma.Data.SessionGroupId (SessionGroupId (unSessionGroupId))+import Chiasma.Path (pathText)++data SessionParams =+ SessionParams {+ attach :: Bool,+ defaultSize :: Bool,+ detach :: Bool,+ exitClient :: Bool,+ printInfo :: Bool,+ cwd :: Maybe (Path Abs Dir),+ environment :: Map Text Text,+ name :: Maybe Ident,+ windowName :: Maybe Ident,+ target :: Maybe SessionGroupId,+ width :: Maybe Int,+ height :: Maybe Int,+ command :: Maybe Text+ }+ deriving stock (Eq, Show)++instance Default SessionParams where+ def =+ SessionParams {+ attach = False,+ defaultSize = False,+ detach = False,+ exitClient = False,+ printInfo = True,+ cwd = Nothing,+ environment = mempty,+ name = Nothing,+ windowName = Nothing,+ target = Nothing,+ width = Nothing,+ height = Nothing,+ command = Nothing+ }++instance CmdArgs SessionParams where+ cmdArgs SessionParams {..} =+ flag1 "-A" attach+ <>+ flag1 "-d" defaultSize+ <>+ flag1 "-D" detach+ <>+ envVars environment+ <>+ flag1 "-P" printInfo+ <>+ flag1 "-X" exitClient+ <>+ optionWith "-c" pathText cwd+ <>+ identOption "-n" windowName+ <>+ identOption "-s" name+ <>+ optionWith "-t" unSessionGroupId target+ <>+ optionWith "-x" show width+ <>+ optionWith "-y" show height+ <>+ arg command
@@ -0,0 +1,84 @@+module Chiasma.Data.SplitParams where++import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs), flag1, optionArgs)+import Chiasma.Data.Axis (Axis)+import Chiasma.Data.Target (Target (Current), formatTarget)++data SplitSize =+ Units Int+ |+ Percentage Int+ deriving stock (Eq, Show)++instance CmdArgs SplitSize where+ cmdArgs = \case+ Units n -> ["-l", show n]+ Percentage n -> ["-p", show n]++data SplitWindowParams =+ SplitWindowParams {+ axis :: Maybe Axis,+ size :: Maybe SplitSize,+ before :: Bool,+ fullSize :: Bool,+ readStdin :: Bool+ }+ deriving stock (Eq, Show)++instance Default SplitWindowParams where+ def =+ SplitWindowParams {+ axis = Nothing,+ size = Nothing,+ before = False,+ fullSize = False,+ readStdin = False+ }++instance CmdArgs SplitWindowParams where+ cmdArgs SplitWindowParams {..} =+ optionArgs axis+ <>+ optionArgs size+ <>+ flag1 "-b" before+ <>+ flag1 "-f" fullSize+ <>+ flag1 "-K" readStdin++data JoinPaneParams =+ JoinPaneParams {+ axis :: Maybe Axis,+ size :: Maybe SplitSize,+ before :: Bool,+ detach :: Bool,+ source :: Maybe Target,+ target :: Target+ }+ deriving stock (Eq, Show)++instance Default JoinPaneParams where+ def =+ JoinPaneParams {+ axis = Nothing,+ size = Nothing,+ before = False,+ detach = False,+ source = Nothing,+ target = Current+ }++instance CmdArgs JoinPaneParams where+ cmdArgs JoinPaneParams {..} =+ optionArgs axis+ <>+ optionArgs size+ <>+ flag1 "-b" before+ <>+ flag1 "-d" detach+ <>+ foldMap (formatTarget ["-s"]) source+ <>+ cmdArgs target
@@ -0,0 +1,32 @@+module Chiasma.Data.Target where++import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs))+import Chiasma.Data.TmuxId (PaneId, SessionId, WindowId, formatId)++data Target =+ Pane PaneId+ |+ Window WindowId+ |+ Session SessionId+ |+ Current+ deriving stock (Eq, Show)++formatTarget :: [Text] -> Target -> [Text]+formatTarget pre = \case+ Pane i -> pre <> [formatId i]+ Window i -> pre <> [formatId i]+ Session i -> pre <> [formatId i]+ Current -> []++instance CmdArgs Target where+ cmdArgs = \case+ Pane i -> ["-t", formatId i]+ Window i -> ["-t", formatId i]+ Session i -> ["-t", formatId i]+ Current -> []++instance Default Target where+ def =+ Current
@@ -0,0 +1,213 @@+module Chiasma.Data.TmuxCommand where++import Data.List (dropWhileEnd)+import qualified Data.Text as Text+import Exon (exon)+import Text.Show (Show (showsPrec), showParen, showString, shows)++import Chiasma.Class.CmdArgs (cmdArgs)+import qualified Chiasma.Codec as Codec+import Chiasma.Codec (multi, single)+import Chiasma.Codec.Data.Client (Client)+import Chiasma.Codec.Data.Pane (Pane)+import Chiasma.Codec.Data.Session (Session)+import Chiasma.Codec.Data.Window (Window)+import Chiasma.Data.CapturePaneParams (CapturePaneParams (CapturePaneParams), stripBlank, stripTrailingWs)+import Chiasma.Data.CopyModeParams (CopyModeParams)+import Chiasma.Data.DecodeError (DecodeError)+import Chiasma.Data.KillPaneParams (KillPaneParams)+import Chiasma.Data.PaneSelection (PaneSelection)+import Chiasma.Data.PipePaneParams (PipePaneParams)+import Chiasma.Data.ResizePaneParams (ResizePaneParams)+import Chiasma.Data.SelectParams (SelectParams)+import Chiasma.Data.SelectWindowParams (SelectWindowParams)+import Chiasma.Data.SendKeysParams (SendKeysParams)+import Chiasma.Data.SessionParams (SessionParams)+import Chiasma.Data.SplitParams (JoinPaneParams, SplitWindowParams)+import Chiasma.Data.Target (Target)+import Chiasma.Data.TmuxId (ClientId (ClientId))+import Chiasma.Data.TmuxQuery (TmuxQuery)+import Chiasma.Data.TmuxRequest (TmuxRequest (TmuxRequest))+import Chiasma.Data.TmuxResponse (TmuxResponse (TmuxResponse))+import Chiasma.Data.WindowParams (WindowParams)+import Chiasma.Data.WindowSelection (WindowSelection)+import Chiasma.Function (applyWhen)++data TmuxCommand :: Type -> Type where+ Fmap :: (a -> b) -> TmuxCommand a -> TmuxCommand b+ ListPanes :: PaneSelection -> TmuxCommand [Pane]+ ListWindows :: WindowSelection -> TmuxCommand [Window]+ ListSessions :: TmuxCommand [Session]+ ListClients :: TmuxCommand [Client]+ SwitchClient :: ClientId -> Target -> TmuxCommand ()+ NewWindow :: WindowParams -> TmuxCommand Window+ SplitWindow :: WindowParams -> SplitWindowParams -> TmuxCommand Pane+ SelectWindow :: SelectWindowParams -> TmuxCommand ()+ NewSession :: SessionParams -> TmuxCommand Session+ CopyMode :: CopyModeParams -> TmuxCommand ()+ SendKeys :: SendKeysParams -> TmuxCommand ()+ SelectPane :: SelectParams -> TmuxCommand ()+ KillPane :: KillPaneParams -> TmuxCommand ()+ MovePane :: JoinPaneParams -> TmuxCommand ()+ ResizePane :: ResizePaneParams -> TmuxCommand ()+ PipePane :: PipePaneParams -> TmuxCommand ()+ CapturePane :: CapturePaneParams -> TmuxCommand [Text]+ KillServer :: TmuxCommand ()++instance Functor TmuxCommand where+ fmap = Fmap++instance Show (TmuxCommand a) where+ showsPrec d = \case+ Fmap _ cmd ->+ showParen (d > 10) [exon|Fmap #{showsPrec 11 cmd}|]+ ListPanes sel ->+ showParen (d > 10) [exon|ListPanes #{showsPrec 11 sel}|]+ ListWindows sel ->+ showParen (d > 10) [exon|ListWindows #{showsPrec 11 sel}|]+ ListClients ->+ showString "ListClients"+ ListSessions ->+ showString "ListSessions"+ SwitchClient c t ->+ showParen (d > 10) [exon|SwitchClient #{showsPrec 11 c} #{showsPrec 11 t}|]+ NewWindow params ->+ showParen (d > 10) [exon|NewWindow #{showsPrec 11 params}|]+ SplitWindow wParams sParams ->+ showParen (d > 10) [exon|SplitWindow #{showsPrec 11 wParams} #{showsPrec 11 sParams}|]+ SelectWindow params ->+ showParen (d > 10) [exon|SelectWindow #{showsPrec 11 params}|]+ NewSession params ->+ showParen (d > 10) [exon|NewSession #{showsPrec 11 params}|]+ CopyMode params ->+ showParen (d > 10) [exon|SelectPane #{showsPrec 11 params}|]+ SendKeys params ->+ showParen (d > 10) [exon|SendKeys #{showsPrec 11 params}|]+ SelectPane paneId ->+ showParen (d > 10) [exon|SelectPane #{shows paneId}|]+ KillPane params ->+ showParen (d > 10) [exon|KillPane #{showsPrec 11 params}|]+ MovePane params ->+ showParen (d > 10) [exon|MovePane #{showsPrec 11 params}|]+ ResizePane params ->+ showParen (d > 10) [exon|ResizePane #{showsPrec 11 params}|]+ PipePane params ->+ showParen (d > 10) [exon|PipePane #{showsPrec 11 params}|]+ CapturePane params ->+ showParen (d > 10) [exon|CapturePane #{showsPrec 11 params}|]+ KillServer ->+ showString "KillServer"++query ::+ ∀ a .+ TmuxCommand a ->+ Maybe TmuxQuery+query = \case+ Fmap _ cmd -> query cmd+ ListPanes _ -> Just (Codec.query @Pane)+ ListWindows _ -> Just (Codec.query @Window)+ ListSessions -> Just (Codec.query @Session)+ ListClients -> Just (Codec.query @Client)+ SwitchClient _ _ -> Nothing+ NewWindow _ -> Just (Codec.query @Window)+ SplitWindow _ _ -> Just (Codec.query @Pane)+ SelectWindow _ -> Nothing+ NewSession _ -> Just (Codec.query @Session)+ CopyMode _ -> Nothing+ SendKeys _ -> Nothing+ SelectPane _ -> Nothing+ KillPane _ -> Nothing+ MovePane _ -> Nothing+ ResizePane _ -> Nothing+ PipePane _ -> Nothing+ CapturePane _ -> Nothing+ KillServer -> Nothing++request :: TmuxCommand a -> Maybe TmuxQuery -> TmuxRequest+request = \case+ Fmap _ c ->+ request c+ ListPanes selection ->+ TmuxRequest "list-panes" (cmdArgs selection)+ ListWindows selection ->+ TmuxRequest "list-windows" (cmdArgs selection)+ ListSessions ->+ TmuxRequest "list-sessions" []+ ListClients ->+ TmuxRequest "list-clients" []+ SwitchClient (ClientId client) target ->+ TmuxRequest "switch-client" (["-c", client] <> cmdArgs target)+ NewWindow params ->+ TmuxRequest "new-window" (cmdArgs params)+ SplitWindow wParams sParams ->+ TmuxRequest "split-window" (cmdArgs wParams <> cmdArgs sParams)+ SelectWindow params ->+ TmuxRequest "select-window" (cmdArgs params)+ NewSession params ->+ TmuxRequest "new-session" (cmdArgs params)+ CopyMode params ->+ TmuxRequest "copy-mode" (cmdArgs params)+ SendKeys params ->+ TmuxRequest "send-keys" (cmdArgs params)+ SelectPane params ->+ TmuxRequest "select-pane" (cmdArgs params)+ KillPane params ->+ TmuxRequest "kill-pane" (cmdArgs params)+ MovePane params ->+ TmuxRequest "move-pane" (cmdArgs params)+ ResizePane params ->+ TmuxRequest "resize-pane" (cmdArgs params)+ PipePane params ->+ TmuxRequest "pipe-pane" (cmdArgs params)+ CapturePane params ->+ TmuxRequest "capture-pane" (cmdArgs params)+ KillServer ->+ TmuxRequest "kill-server" []++encode :: TmuxCommand a -> TmuxRequest+encode cmd =+ request cmd (query cmd)++decode :: TmuxResponse -> TmuxCommand a -> Either DecodeError a+decode (TmuxResponse out) = \case+ Fmap f cmd ->+ f <$> decode (TmuxResponse out) cmd+ ListPanes _ ->+ multi out+ ListWindows _ ->+ multi out+ ListSessions ->+ multi out+ ListClients ->+ multi out+ SwitchClient _ _ ->+ unit+ NewWindow _ ->+ single out+ SplitWindow _ _ ->+ single out+ SelectWindow _ ->+ unit+ NewSession _ ->+ single out+ CopyMode _ ->+ unit+ SendKeys _ ->+ unit+ SelectPane _ ->+ unit+ KillPane _ ->+ unit+ MovePane _ ->+ unit+ ResizePane _ ->+ unit+ PipePane _ ->+ unit+ CapturePane CapturePaneParams {stripBlank, stripTrailingWs} ->+ pure $+ applyWhen stripBlank (dropWhileEnd ("" ==)) $+ applyWhen stripTrailingWs (fmap Text.stripEnd) $+ out+ KillServer ->+ unit
@@ -1,39 +1,23 @@ module Chiasma.Data.TmuxError where -import Chiasma.Codec.Decode (TmuxDecodeError)-import Text.ParserCombinators.Parsec (ParseError)+import Polysemy.Process.Data.ProcessError (ProcessError) -import Chiasma.Data.Cmd (Cmds(..))+import Chiasma.Data.CodecError (CodecError (CodecError))+import Chiasma.Data.DecodeError (DecodeError)+import Chiasma.Data.TmuxRequest (TmuxRequest) data TmuxError =- ProcessFailed {- processFailedCmds :: Cmds,- processFailedReason :: Text- }- |- OutputParsingFailed {- parsingFailedCmds :: Cmds,- parsingFailedOutput :: [Text],- parsingFailedError :: ParseError- }+ ProcessFailed ProcessError |- NoOutput Cmds+ RequestFailed TmuxRequest [Text] |- DecodingFailed {- decodingFailedCmds :: Cmds,- decodingFailedOutput :: Text,- decodingFailedError :: TmuxDecodeError- }+ DecodeFailed TmuxRequest DecodeError |- InvalidOutput {- invalidOutputReason :: Text,- invalidOutputCommand :: Text- }+ NoClients |- CommandFailed {- commandFailedCmds :: Cmds,- commandFailedError :: [Text]- }- deriving (Eq, Show)+ NoExe+ deriving stock (Eq, Show) -deepPrisms ''TmuxError+codec :: CodecError -> TmuxError+codec (CodecError req err) =+ DecodeFailed req err
@@ -1,35 +1,43 @@ module Chiasma.Data.TmuxId where -import Data.Text.Prettyprint.Doc (Pretty(..))+import Prettyprinter (Pretty (..)) -sessionPrefix :: Char-sessionPrefix = '$'+newtype ClientId =+ ClientId { unClientId :: Text }+ deriving stock (Eq, Show, Generic)+ deriving newtype (IsString) +instance Pretty ClientId where+ pretty = pretty . unClientId++sessionPrefix :: Text+sessionPrefix = "$"+ newtype SessionId = SessionId Int- deriving (Eq, Show, Generic)+ deriving stock (Eq, Show, Generic) deriving newtype (Num, Ord) instance Pretty SessionId where pretty = pretty . formatId -windowPrefix :: Char-windowPrefix = '@'+windowPrefix :: Text+windowPrefix = "@" newtype WindowId = WindowId Int- deriving (Eq, Show, Generic)+ deriving stock (Eq, Show, Generic) deriving newtype (Num, Ord) instance Pretty WindowId where pretty = pretty . formatId -panePrefix :: Char-panePrefix = '%'+panePrefix :: Text+panePrefix = "%" newtype PaneId = PaneId Int- deriving (Eq, Show, Generic)+ deriving stock (Eq, Show, Generic) deriving newtype (Num, Ord) instance Pretty PaneId where@@ -39,7 +47,9 @@ paneId :: a -> PaneId newtype TmuxIdPrefix a =- TmuxIdPrefix Char+ TmuxIdPrefix { unTmuxIdPrefix :: Text }+ deriving stock (Eq, Show, Generic)+ deriving newtype (IsString) class TmuxId a where prefix :: TmuxIdPrefix a@@ -47,7 +57,7 @@ formatId :: a -> Text formatId a =- toText (p : show (number a))+ p <> show (number a) where (TmuxIdPrefix p) = prefix @a
@@ -0,0 +1,10 @@+module Chiasma.Data.TmuxNative where++import Path (Abs, File, Path)++data TmuxNative =+ TmuxNative {+ executable :: Path Abs File,+ tmuxServerSocket :: Maybe (Path Abs File)+ }+ deriving stock (Eq, Show)
@@ -0,0 +1,13 @@+module Chiasma.Data.TmuxOutputBlock where++data End =+ EndSuccess+ |+ EndError+ deriving stock (Eq, Show)++data TmuxOutputBlock =+ Success [Text]+ |+ Error [Text]+ deriving stock (Eq, Show)
@@ -0,0 +1,6 @@+module Chiasma.Data.TmuxQuery where++newtype TmuxQuery =+ TmuxQuery { unTmuxQuery :: Text }+ deriving stock (Eq, Show, Generic)+ deriving newtype (IsString, Semigroup, Monoid)
@@ -0,0 +1,24 @@+module Chiasma.Data.TmuxRequest where++import qualified Data.Text as Text+import Chiasma.Data.TmuxQuery (TmuxQuery (TmuxQuery))++data TmuxRequest =+ TmuxRequest {+ cmd :: Text,+ args :: [Text],+ query :: Maybe TmuxQuery+ }+ deriving stock (Eq, Show)++queryArgs :: TmuxQuery -> [Text]+queryArgs (TmuxQuery q) =+ ["-F", "'", q, "'"]++cmdline :: TmuxRequest -> [Text]+cmdline TmuxRequest { ..} =+ cmd : args ++ foldMap queryArgs query ++ ["\n"]++encode :: TmuxRequest -> ByteString+encode req =+ encodeUtf8 (Text.unwords (cmdline req))
@@ -0,0 +1,6 @@+module Chiasma.Data.TmuxResponse where++newtype TmuxResponse =+ TmuxResponse { unTmuxResponse :: [Text] }+ deriving stock (Eq, Show)+ deriving newtype (Semigroup, Monoid)
@@ -1,16 +1,16 @@ module Chiasma.Data.TmuxThunk where -import Chiasma.Codec.Decode (TmuxDecodeError)-import Chiasma.Data.Cmd (Cmd(..))-import Chiasma.Data.TmuxError (TmuxError(..))+-- import Chiasma.Codec.Decode (DecodeFailure)+-- import Chiasma.Data.Cmd (Cmd(..))+-- import Chiasma.Data.TmuxError (TmuxError(..)) -data TmuxThunk next =- ∀ a . Read Cmd (Text -> Either TmuxDecodeError a) ([a] -> next)- |- Write Cmd (() -> next)- |- Flush (() -> next)- |- Failed TmuxError+-- data TmuxThunk next =+-- ∀ a . Read Cmd (Text -> Either DecodeFailure a) ([a] -> next)+-- |+-- Write Cmd (() -> next)+-- |+-- Flush (() -> next)+-- |+-- Failed TmuxError -deriving instance Functor TmuxThunk+-- deriving instance Functor TmuxThunk
@@ -9,7 +9,7 @@ viewIdent :: Ident, viewId :: Maybe a }- deriving (Eq, Show)+ deriving stock (Eq, Show) makeClassy_ ''View
@@ -1,9 +1,8 @@-{-# LANGUAGE DeriveAnyClass #-}- module Chiasma.Data.Views where -import Data.Text.Prettyprint.Doc (Doc)-import Data.Text.Prettyprint.Doc.Render.Terminal (AnsiStyle)+import Control.Lens (makeClassy)+import Prettyprinter (Doc)+import Prettyprinter.Render.Terminal (AnsiStyle) import Chiasma.Data.Ident (Ident) import Chiasma.Data.TmuxId (PaneId, SessionId, WindowId)@@ -17,9 +16,7 @@ NoSuchPane Ident | NoPaneId Ident- deriving (Eq, Show)--deepPrisms ''ViewsError+ deriving stock (Eq, Show) data Views = Views {@@ -28,10 +25,11 @@ _panes :: [View PaneId], _log :: [Doc AnsiStyle] }- deriving (Show, Generic, Default)+ deriving stock (Show, Generic)+ deriving anyclass (Default) +makeClassy ''Views+ instance Eq Views where (Views sa wa pa _) == (Views sb wb pb _) = (sa == sb) && (wa == wb) && (pa == pb)--deepLenses ''Views
@@ -0,0 +1,56 @@+module Chiasma.Data.WindowParams where++import Path (Abs, Dir, Path)++import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs), arg, envVars, flag1, identOption, optionWith)+import Chiasma.Data.Ident (Ident)+import Chiasma.Data.Target (Target)+import Chiasma.Path (pathText')++data WindowParams =+ WindowParams {+ after :: Bool,+ detach :: Bool,+ killExisting :: Bool,+ printInfo :: Bool,+ cwd :: Maybe (Path Abs Dir),+ environment :: Map Text Text,+ name :: Maybe Ident,+ target :: Target,+ command :: Maybe Text+ }+ deriving stock (Eq, Show)++instance Default WindowParams where+ def =+ WindowParams {+ after = False,+ detach = False,+ killExisting = False,+ printInfo = True,+ cwd = Nothing,+ environment = mempty,+ name = Nothing,+ target = def,+ command = Nothing+ }++instance CmdArgs WindowParams where+ cmdArgs WindowParams {..} =+ flag1 "-a" after+ <>+ flag1 "-d" detach+ <>+ flag1 "-k" killExisting+ <>+ flag1 "-P" printInfo+ <>+ optionWith "-c" pathText' cwd+ <>+ envVars environment+ <>+ identOption "-n" name+ <>+ cmdArgs target+ <>+ arg command
@@ -0,0 +1,19 @@+module Chiasma.Data.WindowSelection where++import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs))+import Chiasma.Data.Target (Target (Current))++data WindowSelection =+ All+ |+ InSession Target+ deriving stock (Eq, Show)++instance CmdArgs WindowSelection where+ cmdArgs = \case+ All -> ["-a"]+ InSession target -> cmdArgs target++instance Default WindowSelection where+ def =+ InSession Current
@@ -15,4 +15,4 @@ wsLayout :: RenderableTree, wsReferencePane :: PaneId }- deriving (Eq, Show)+ deriving stock (Eq, Show)
@@ -0,0 +1,39 @@+module Chiasma.Data.WithPaneId where++import qualified Data.Text as Text++import Chiasma.Codec (TmuxCodec (decode, query), trim)+import Chiasma.Codec.Decode (primDecode)+import Chiasma.Data.DecodeError (DecodeError (DecodeError), DecodeFailure (TooFewFields))+import qualified Chiasma.Data.TmuxId as TmuxId+import Chiasma.Data.TmuxId (HasPaneId, PaneId)+import Chiasma.Data.TmuxQuery (TmuxQuery (TmuxQuery))++data WithPaneId a =+ WithPaneId {+ paneId :: PaneId,+ pane :: a+ }+ deriving stock (Eq, Show, Generic)++instance HasPaneId (WithPaneId a) where+ paneId = paneId++safeBreakOn :: Text -> Text -> Maybe (Text, Text)+safeBreakOn n = \case+ "" -> Nothing+ t -> Just (second (Text.drop 1) (Text.breakOn n t))++instance TmuxCodec a => TmuxCodec (WithPaneId a) where+ decode payload =+ case safeBreakOn " " (trim payload) of+ Just (idField, rest) -> do+ pane <- decode rest+ paneId <- first (DecodeError [payload]) (primDecode idField)+ pure WithPaneId {..}+ Nothing ->+ Left (DecodeError [payload] TooFewFields)++ query =+ let TmuxQuery paneQuery = query @a+ in TmuxQuery ("#{pane_id} " <> paneQuery)
@@ -0,0 +1,32 @@+module Chiasma.Effect.Codec where++import Chiasma.Data.CodecError (CodecError)+import Chiasma.Data.TmuxCommand (TmuxCommand)+import Chiasma.Data.TmuxRequest (TmuxRequest)+import Chiasma.Data.TmuxResponse (TmuxResponse)++data Codec (command :: Type -> Type) (i :: Type) (o :: Type) :: Effect where+ WithCodec :: command a -> (i -> m o) -> Codec command i o m a+ Encode :: command a -> Codec command i o m i++makeSem ''Codec++type NativeCodec command =+ Codec command TmuxRequest TmuxResponse++type NativeCodecE command =+ NativeCodec command !! CodecError++type NativeCommandCodec =+ NativeCodec TmuxCommand++type NativeCommandCodecE =+ NativeCodecE TmuxCommand++type family NativeCodecs (cs :: [Type -> Type]) :: [Effect] where+ NativeCodecs '[] = '[]+ NativeCodecs (c : cs) = NativeCodec c : NativeCodecs cs++type family NativeCodecsE (cs :: [Type -> Type]) :: [Effect] where+ NativeCodecsE '[] = '[]+ NativeCodecsE (c : cs) = NativeCodecE c : NativeCodecsE cs
@@ -0,0 +1,12 @@+module Chiasma.Effect.TmuxApi where++import Chiasma.Data.TmuxCommand (TmuxCommand)++data TmuxApi (command :: Type -> Type) :: Effect where+ Send :: command a -> TmuxApi command m a+ Schedule :: command a -> TmuxApi command m ()++makeSem ''TmuxApi++type Tmux =+ TmuxApi TmuxCommand
@@ -0,0 +1,28 @@+module Chiasma.Effect.TmuxClient where++import Prelude hiding (send)++import Chiasma.Data.TmuxRequest (TmuxRequest)+import Chiasma.Data.TmuxResponse (TmuxResponse)++data TmuxClient (i :: Type) (o :: Type) :: Effect where+ Send :: i -> TmuxClient i o m o+ Schedule :: i -> TmuxClient i o m ()++makeSem ''TmuxClient++type ScopedTmux i o =+ Scoped_ (TmuxClient i o)++type NativeTmux =+ ScopedTmux TmuxRequest TmuxResponse++flush ::+ Member (TmuxClient e d) r =>+ InterpreterFor (TmuxClient e d) r+flush =+ interpret \case+ Send cmd ->+ send cmd+ Schedule cmd ->+ void (send cmd)
@@ -0,0 +1,58 @@+module Chiasma.Event where++import Exon (exon)+import qualified Polysemy.Conc as Conc+import Polysemy.Conc (withAsync_)+import qualified Polysemy.Log as Log+import qualified Polysemy.Time as Time+import Polysemy.Time (Seconds (Seconds))+import Prelude hiding (listen)++import Chiasma.Data.Event (Event)+import Chiasma.Data.ReceiveEvent (ReceiveEvent (ReceiveEvent))+import Chiasma.Data.TmuxError (TmuxError)+import Chiasma.Effect.Codec (Codec)+import qualified Chiasma.Effect.TmuxApi as TmuxApi+import Chiasma.Effect.TmuxApi (TmuxApi)+import Chiasma.Effect.TmuxClient (TmuxClient)+import Chiasma.Tmux (withTmux)++receiveEvent ::+ ∀ r .+ Members [TmuxApi ReceiveEvent, Events Event] r =>+ Sem r ()+receiveEvent =+ Conc.publish =<< TmuxApi.send ReceiveEvent++listenLoop ::+ ∀ err t d r .+ Show err =>+ Members [TmuxApi ReceiveEvent !! err, Events Event, Time t d, Log] r =>+ Sem r ()+listenLoop = do+ resume @_ @(TmuxApi _) receiveEvent \ err ->+ Log.error [exon|Receiving tmux event: #{show err}|]+ listenLoop++listen ::+ ∀ enc dec err t d r .+ Show err =>+ Member (Codec ReceiveEvent enc dec !! err) r =>+ Members [Scoped_ (TmuxClient enc dec) !! TmuxError, Events Event, Time t d, Log] r =>+ Sem r ()+listen = do+ resume @_ @(Scoped_ _) (withTmux listenLoop) \ err -> do+ Log.error [exon|Lost connection to tmux: #{show err}+Reconnecting...|]+ Time.sleep (Seconds 1)++withTmuxEvents ::+ Show err =>+ Member (Codec ReceiveEvent enc dec !! err) r =>+ Member (Scoped_ (TmuxClient enc dec) !! TmuxError) r =>+ Members [Events Event, Time t d, Log, Race, Async, Resource] r =>+ Sem r a ->+ Sem r a+withTmuxEvents =+ withAsync_ do+ forever listen
@@ -0,0 +1,7 @@+module Chiasma.Function where++applyWhen :: Bool -> (a -> a) -> a -> a+applyWhen True f x =+ f x+applyWhen False _ x =+ x
@@ -0,0 +1,50 @@+module Chiasma.Interpreter.Codec where++import Chiasma.Codec (TmuxCodec)+import Chiasma.Data.CodecError (CodecError (CodecError))+import Chiasma.Data.DecodeError (DecodeError)+import qualified Chiasma.Data.Panes as Panes+import Chiasma.Data.Panes (Panes)+import qualified Chiasma.Data.TmuxCommand as TmuxCommand+import Chiasma.Data.TmuxCommand (TmuxCommand)+import Chiasma.Data.TmuxRequest (TmuxRequest)+import Chiasma.Data.TmuxResponse (TmuxResponse)+import qualified Chiasma.Effect.Codec as Codec+import Chiasma.Effect.Codec (Codec)++interpretCodecNative ::+ (∀ x . cmd x -> TmuxRequest) ->+ (∀ x . TmuxResponse -> cmd x -> Either DecodeError x) ->+ InterpreterFor (Codec cmd TmuxRequest TmuxResponse !! CodecError) r+interpretCodecNative enc dec =+ interpretResumableH \case+ Codec.Encode cmd ->+ pureT (enc cmd)+ Codec.WithCodec cmd use -> do+ let req = enc cmd+ out <- runTSimple (use req)+ Inspector ins <- getInspectorT+ pureT =<< stopEitherWith (CodecError req) (dec (fold (ins out)) cmd)++interpretCodecTmuxCommand ::+ InterpreterFor (Codec TmuxCommand TmuxRequest TmuxResponse !! CodecError) r+interpretCodecTmuxCommand =+ interpretCodecNative TmuxCommand.encode TmuxCommand.decode+{-# inline interpretCodecTmuxCommand #-}++interpretCodecPanes ::+ TmuxCodec p =>+ InterpreterFor (Codec (Panes p) TmuxRequest TmuxResponse !! CodecError) r+interpretCodecPanes =+ interpretCodecNative Panes.encode Panes.decode+{-# inline interpretCodecPanes #-}++interpretCodecPure ::+ (∀ a . command a -> Sem r (Either Text a)) ->+ InterpreterFor (Codec command () decode !! Text) r+interpretCodecPure run =+ interpretResumableH \case+ Codec.Encode _ ->+ pureT ()+ Codec.WithCodec cmd _ ->+ either stop pureT =<< raise (raise (run cmd))
@@ -0,0 +1,39 @@+module Chiasma.Interpreter.ProcessOutput where++import Data.Attoparsec.ByteString (parse)+import Data.Attoparsec.Types (IResult (Done, Fail, Partial))+import Polysemy.Conc (interpretAtomic)+import Polysemy.Process.Effect.ProcessOutput (ProcessOutput (Chunk))++import Chiasma.Data.TmuxOutputBlock (TmuxOutputBlock)+import Chiasma.Native.TmuxOutputBlock (parser)++type ParseResult =+ IResult ByteString TmuxOutputBlock++type ParseCont =+ ByteString -> IResult ByteString TmuxOutputBlock++parseResult ::+ Member (AtomicState (Maybe ParseCont)) r =>+ ParseResult ->+ Sem r ([Either Text TmuxOutputBlock], ByteString)+parseResult = \case+ Fail _ _ err -> pure ([Left (toText err)], "")+ Partial c -> ([], "") <$ atomicPut (Just c)+ Done rest block -> pure ([Right block], rest)++interpretProcessOutputTmuxBlock ::+ ∀ p r .+ Member (Embed IO) r =>+ InterpreterFor (ProcessOutput p (Either Text TmuxOutputBlock)) r+interpretProcessOutputTmuxBlock =+ interpretAtomic (Nothing :: Maybe ParseCont) .+ interpret \case+ Chunk _ new ->+ atomicState' (Nothing,) >>= \case+ Just cont ->+ parseResult (cont new)+ Nothing ->+ parseResult (parse parser new)+ . raiseUnder
@@ -0,0 +1,13 @@+module Chiasma.Interpreter.Pure where++import Chiasma.Data.TmuxError (TmuxError)+import Chiasma.Effect.Codec (Codec)+import Chiasma.Effect.TmuxClient (TmuxClient)+import Chiasma.Interpreter.Codec (interpretCodecPure)+import Chiasma.Interpreter.TmuxClient (interpretTmuxClientNull)++interpretTmuxPure ::+ (∀ a . command a -> Sem r (Either Text a)) ->+ InterpretersFor [Scoped_ (TmuxClient () ()) !! TmuxError, Codec command () () !! Text] r+interpretTmuxPure run =+ interpretCodecPure run . interpretTmuxClientNull
@@ -0,0 +1,90 @@+module Chiasma.Interpreter.TmuxApi where++import Fcf (Eval, Exp, Pure1, type (@@))+import Fcf.Class.Functor (FMap)+import Prelude hiding (send, type (@@))++import Chiasma.Data.CodecError (CodecError)+import Chiasma.Data.TmuxRequest (TmuxRequest)+import Chiasma.Data.TmuxResponse (TmuxResponse)+import Chiasma.Effect.Codec (Codec, encode, withCodec)+import Chiasma.Effect.TmuxApi (TmuxApi (Schedule, Send), send)+import qualified Chiasma.Effect.TmuxClient as TmuxClient+import Chiasma.Effect.TmuxClient (TmuxClient)++type family (f :: l -> k) <$> (fa :: [l]) :: [k] where+ f <$> fa =+ FMap (Pure1 f) @@ fa++flush ::+ Member (TmuxApi c) r =>+ InterpreterFor (TmuxApi c) r+flush =+ interpret \case+ Send cmd ->+ send cmd+ Schedule cmd ->+ void (send cmd)++interpretTmuxApi ::+ ∀ command i o err r .+ Members [TmuxClient i o, Codec command i o !! err] r =>+ InterpreterFor (TmuxApi command !! err) r+interpretTmuxApi =+ interpretResumable \case+ Send cmd -> do+ restop @_ @(Codec _ _ _) $ withCodec cmd \ encoded -> do+ TmuxClient.send encoded+ Schedule cmd -> do+ encoded <- restop (encode cmd)+ TmuxClient.schedule encoded++data TmuxApiEffect :: Type -> (Type -> Type) -> Exp Effect++type instance Eval (TmuxApiEffect err command) =+ TmuxApi command !! err++type family TmuxApis (commands :: [Type -> Type]) (err :: Type) :: EffectRow where+ TmuxApis commands err =+ FMap (TmuxApiEffect err) @@ commands++class InterpretApis (commands :: [Type -> Type]) err i o r where+ interpretApis :: InterpretersFor (TmuxApis commands err) (TmuxClient i o : r)++instance InterpretApis '[] err i o r where+ interpretApis =+ id++instance (+ r1 ~ (TmuxApis commands err ++ TmuxClient i o : r),+ Member (TmuxClient i o) r1,+ Member (Codec command i o !! err) r1,+ InterpretApis commands err i o r+ ) => InterpretApis (command : commands) err i o r where+ interpretApis =+ interpretApis @commands @err . interpretTmuxApi++type InterpretApisNative commands r =+ InterpretApis commands CodecError TmuxRequest TmuxResponse r++class RestopApis (commands :: [Type -> Type]) err i o r where+ restopApis :: InterpretersFor (TmuxApi <$> commands) (TmuxClient i o : r)++instance RestopApis '[] err i o r where+ restopApis =+ id++instance (+ r1 ~ (TmuxApi <$> commands ++ TmuxClient i o : r),+ Members [TmuxClient i o, Stop err] r1,+ Member (Codec command i o !! err) r1,+ RestopApis commands err i o r+ ) => RestopApis (command : commands) err i o r where+ restopApis =+ restopApis @commands @err @i @o .+ interpretTmuxApi @command @i @o .+ restop @err @(TmuxApi command) .+ raiseUnder++type RestopApisNative commands r =+ RestopApis commands CodecError TmuxRequest TmuxResponse r
@@ -0,0 +1,201 @@+module Chiasma.Interpreter.TmuxClient where++import Conc (interpretAtomic, interpretScopedResumableWith_, interpretScopedResumable_)+import Data.Sequence ((|>))+import qualified Data.Text as Text+import Exon (exon)+import qualified Log as Log+import Path (Abs, File, Path, relfile, toFilePath)+import Polysemy.Process.Interpreter.Process (ProcessQueues)+import qualified Process as Process+import Process (+ OutputPipe (Stderr, Stdout),+ Process,+ ProcessError,+ SystemProcess,+ SystemProcessError,+ SystemProcessScopeError,+ interpretProcessInputId,+ interpretProcessOutputLeft,+ interpretProcessOutputTextLines,+ interpretProcess_,+ interpretSystemProcessNative_,+ resolveExecutable,+ withProcess_,+ )+import System.Process.Typed (ProcessConfig, proc)++import qualified Chiasma.Data.TmuxError as TmuxError+import Chiasma.Data.TmuxError (TmuxError (NoExe))+import Chiasma.Data.TmuxNative (TmuxNative (TmuxNative))+import qualified Chiasma.Data.TmuxOutputBlock as TmuxOutputBlock+import Chiasma.Data.TmuxOutputBlock (TmuxOutputBlock)+import qualified Chiasma.Data.TmuxRequest as TmuxRequest+import Chiasma.Data.TmuxRequest (TmuxRequest (TmuxRequest))+import Chiasma.Data.TmuxResponse (TmuxResponse (TmuxResponse))+import qualified Chiasma.Effect.TmuxClient as TmuxClient+import Chiasma.Effect.TmuxClient (TmuxClient)+import Chiasma.Interpreter.ProcessOutput (interpretProcessOutputTmuxBlock)++type TmuxQueues =+ ProcessQueues (Either Text TmuxOutputBlock) Text++type TmuxProc =+ Process ByteString (Either Text TmuxOutputBlock)++validate :: TmuxRequest -> TmuxOutputBlock -> Either TmuxError TmuxResponse+validate request = \case+ TmuxOutputBlock.Success a ->+ Right (TmuxResponse a)+ TmuxOutputBlock.Error a ->+ Left (TmuxError.RequestFailed request a)++tmuxRequest ::+ Members [Process ByteString (Either Text TmuxOutputBlock), Log, Stop TmuxError] r =>+ TmuxRequest ->+ Sem r TmuxResponse+tmuxRequest request = do+ Log.trace [exon|tmux request: #{Text.stripEnd (decodeUtf8 cmdline)}|]+ Process.send cmdline+ Process.recv >>= \case+ Left err -> stop (TmuxError.RequestFailed request [err])+ Right block -> do+ Log.trace [exon|tmux response: #{show block}|]+ stopEither (validate request block)+ where+ cmdline =+ TmuxRequest.encode request++socketArg :: Path Abs File -> [String]+socketArg socket =+ ["-S", toFilePath socket]++tmuxProc ::+ TmuxNative ->+ ProcessConfig () () ()+tmuxProc (TmuxNative exe socket) =+ proc (toFilePath exe) (foldMap socketArg socket <> ["-C", "-u", "attach-session", "-f", "ignore-size"])++interpretSystemProcessTmux ::+ Members [Reader TmuxNative, Resource, Race, Async, Embed IO] r =>+ InterpreterFor (Scoped_ (SystemProcess !! SystemProcessError) !! SystemProcessScopeError) r+interpretSystemProcessTmux sem = do+ conf <- tmuxProc <$> ask+ interpretSystemProcessNative_ conf sem++interpretProcessTmux ::+ Member (Scoped_ (SystemProcess !! SystemProcessError) !! SystemProcessScopeError) r =>+ Members [Resource, Race, Async, Embed IO] r =>+ InterpreterFor (Scoped_ TmuxProc !! ProcessError) r+interpretProcessTmux sem = do+ interpretProcessOutputTmuxBlock @'Stdout $+ interpretProcessOutputTextLines @'Stderr $+ interpretProcessOutputLeft @'Stderr $+ interpretProcessInputId $+ interpretProcess_ def $+ insertAt @1 sem+{-# inline interpretProcessTmux #-}++flush ::+ Members [TmuxProc, AtomicState (Seq TmuxRequest), Log, Stop TmuxError] r =>+ Sem r ()+flush =+ traverse_ tmuxRequest =<< atomicState' (mempty,)++tmuxSession ::+ ∀ r a .+ Members [Scoped_ TmuxProc !! ProcessError, AtomicState (Seq TmuxRequest), Log, Stop TmuxError] r =>+ Sem (TmuxProc : r) a ->+ Sem r a+tmuxSession action =+ resumeHoist @ProcessError @(Scoped_ TmuxProc) TmuxError.ProcessFailed $ withProcess_ do+ void Process.recv+ tmuxRequest (TmuxRequest "refresh-client" ["-C", "10000x10000"] Nothing)+ raiseUnder action <* flush++interpretTmuxProcessBuffered ::+ Members [AtomicState (Seq TmuxRequest), Scoped_ TmuxProc !! ProcessError, Log, Embed IO] r =>+ InterpreterFor (Scoped_ (TmuxClient TmuxRequest TmuxResponse) !! TmuxError) r+interpretTmuxProcessBuffered =+ interpretScopedResumableWith_ @'[TmuxProc] (const tmuxSession) \case+ TmuxClient.Schedule request ->+ atomicModify' (|> request)+ TmuxClient.Send cmd -> do+ flush+ tmuxRequest cmd+{-# inline interpretTmuxProcessBuffered #-}++interpretTmuxWithProcess ::+ Members [Scoped_ TmuxProc !! ProcessError, Log, Embed IO] r =>+ InterpreterFor (Scoped_ (TmuxClient TmuxRequest TmuxResponse) !! TmuxError) r+interpretTmuxWithProcess =+ interpretAtomic mempty .+ interpretTmuxProcessBuffered .+ raiseUnder+{-# inline interpretTmuxWithProcess #-}++interpretTmuxNative ::+ ∀ r .+ Members [Reader TmuxNative, Log, Resource, Race, Async, Embed IO] r =>+ InterpreterFor (Scoped_ (TmuxClient TmuxRequest TmuxResponse) !! TmuxError) r+interpretTmuxNative =+ interpretSystemProcessTmux .+ interpretProcessTmux .+ interpretTmuxWithProcess .+ raiseUnder2+{-# inline interpretTmuxNative #-}++interpretTmuxFailing ::+ TmuxError ->+ InterpreterFor (Scoped_ (TmuxClient TmuxRequest TmuxResponse) !! TmuxError) r+interpretTmuxFailing err =+ interpretScopedResumable_ mempty \ () -> \case+ TmuxClient.Schedule _ ->+ stop err+ TmuxClient.Send _ ->+ stop err++withTmuxNativeEnv ::+ Member (Embed IO) r =>+ Maybe (Path Abs File) ->+ (Maybe TmuxNative -> Sem r a) ->+ Sem r a+withTmuxNativeEnv socket use =+ use . fmap (flip TmuxNative socket) . rightToMaybe =<< resolveExecutable [relfile|tmux|] Nothing++runReaderTmuxNativeEnv ::+ Members [Error TmuxError, Embed IO] r =>+ Maybe (Path Abs File) ->+ InterpreterFor (Reader TmuxNative) r+runReaderTmuxNativeEnv socket sem = do+ tn <- withTmuxNativeEnv socket (note NoExe)+ runReader tn sem+{-# inline runReaderTmuxNativeEnv #-}++interpretTmuxNativeEnv ::+ Members [Error TmuxError, Log, Resource, Race, Async, Embed IO] r =>+ Maybe (Path Abs File) ->+ InterpreterFor (Scoped_ (TmuxClient TmuxRequest TmuxResponse) !! TmuxError) r+interpretTmuxNativeEnv socket =+ runReaderTmuxNativeEnv socket . interpretTmuxNative . raiseUnder+{-# inline interpretTmuxNativeEnv #-}++interpretTmuxNativeEnvGraceful ::+ Members [Log, Resource, Race, Async, Embed IO] r =>+ Maybe (Path Abs File) ->+ InterpreterFor (Scoped_ (TmuxClient TmuxRequest TmuxResponse) !! TmuxError) r+interpretTmuxNativeEnvGraceful socket sem =+ withTmuxNativeEnv socket \case+ Just tn -> runReader tn (interpretTmuxNative (raiseUnder sem))+ Nothing -> interpretTmuxFailing NoExe sem+{-# inline interpretTmuxNativeEnvGraceful #-}++interpretTmuxClientNull ::+ InterpreterFor (Scoped_ (TmuxClient i ()) !! TmuxError) r+interpretTmuxClientNull =+ interpretScopedResumable_ mempty \ () -> \case+ TmuxClient.Schedule _ ->+ unit+ TmuxClient.Send _ ->+ unit+{-# inline interpretTmuxClientNull #-}
@@ -1,37 +1,36 @@-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE FunctionalDependencies #-}-{-# LANGUAGE DeriveDataTypeable #-}- module Chiasma.Lens.Tree where -import Chiasma.Data.Ident (Ident, Identifiable(..))-import Chiasma.Ui.Data.View-import Chiasma.Ui.Lens.Ident (matchIdentP) import Control.Lens ( Fold, Index, IxValue,- Ixed(ix),- Plated(..),- Traversal',+ Ixed (ix),+ Plated (..), cosmos,- each, makeClassy_,- over, preview,- toListOf, transform, ) import Data.Data (Data) import Data.Foldable (foldrM)+import Prelude hiding (ix, transform) +import Chiasma.Data.Ident (Ident, Identifiable (..))+import Chiasma.Ui.Data.View (+ HasTree (_treeSubs),+ HasTreeSub (leafData),+ LayoutView,+ PaneView,+ Tree (Tree),+ TreeSub (TreeNode),+ )+import Chiasma.Ui.Lens.Ident (matchIdentP)+ newtype NodeIndexTree l p = NodeIndexTree { nitTree :: Tree l p }- deriving (Eq, Show)+ deriving stock (Eq, Show) makeClassy_ ''NodeIndexTree @@ -39,7 +38,7 @@ LeafIndexTree { litTree :: Tree l p }- deriving (Eq, Show)+ deriving stock (Eq, Show) makeClassy_ ''LeafIndexTree @@ -90,7 +89,7 @@ subtreesWithLayout :: ∀ l p m. Monad m => ((l, TreeSub l p) -> m (l, TreeSub l p)) -> Tree l p -> m (Tree l p) subtreesWithLayout f (Tree l0 sub) = do (newL, newSub) <- foldrM applySub (l0, []) sub- return (Tree newL newSub)+ pure (Tree newL newSub) where prependSub s (newL, newN) = (newL, newN : s) applySub :: TreeSub l p -> (l, [TreeSub l p]) -> m (l, [TreeSub l p])@@ -103,7 +102,7 @@ subtrees :: ∀ l p m. Monad m => (TreeSub l p -> m (TreeSub l p)) -> Tree l p -> m (Tree l p) subtrees f (Tree l sub) = do newSub <- mapM applySub sub- return (Tree l newSub)+ pure (Tree l newSub) where applySub :: TreeSub l p -> m (TreeSub l p) applySub (TreeNode t) = do
@@ -1,10 +1,6 @@-{-# LANGUAGE RankNTypes #-}--module Chiasma.Lens.Where(- where1,-) where+module Chiasma.Lens.Where where -import Control.Lens (Over, filtered, taking)+import Control.Lens (Over, taking) where1 :: (Applicative f, Traversable t) => (a -> Bool) -> Over (->) f (t a) (t a) a a where1 predicate = taking 1 $ traverse . filtered predicate
@@ -1,50 +1,50 @@ module Chiasma.Monad.EvalFreeT where -import Control.Monad.Trans.Free (FreeF(..), FreeT(..))+-- import Control.Monad.Trans.Free (FreeF(..), FreeT(..)) -import Chiasma.Codec.Decode (TmuxDecodeError)-import Chiasma.Data.Cmd (Cmd(..), Cmds(..))-import Chiasma.Data.TmuxError (TmuxError)-import Chiasma.Data.TmuxThunk (TmuxThunk(..))+-- import Chiasma.Codec.Decode (DecodeFailure)+-- import Chiasma.Data.Cmd (Cmd(..), Cmds(..))+-- import Chiasma.Data.TmuxError (TmuxError)+-- import Chiasma.Data.TmuxThunk (TmuxThunk(..)) -newtype CmdBuffer = CmdBuffer [Cmd]+-- newtype CmdBuffer = CmdBuffer [Cmd] -instance Default CmdBuffer where- def = CmdBuffer def+-- instance Default CmdBuffer where+-- def = CmdBuffer def -type CommandExec m =- (∀ b. (Text -> Either TmuxDecodeError b) -> Cmds -> m (Either TmuxError [b]))+-- type CommandExec m =+-- (∀ b. (Text -> Either DecodeFailure b) -> Cmds -> m (Either TmuxError [b])) -evalFreeF ::- Monad m =>- CommandExec m ->- CmdBuffer ->- FreeF TmuxThunk a (FreeT TmuxThunk m a) ->- m (Either TmuxError a)-evalFreeF _ (CmdBuffer []) (Pure a) =- return (Right a)-evalFreeF exec (CmdBuffer cmds) (Pure a) =- Right a <$ exec (const $ Right ()) (Cmds cmds)-evalFreeF exec (CmdBuffer cmds) (Free (Read cmd decode next)) = do- a <- exec decode $ Cmds (cmd : cmds)- case a of- Right a' -> evalFreeT exec def (next a')- Left err -> return (Left err)-evalFreeF exec (CmdBuffer cmds) (Free (Write cmd next)) =- evalFreeT exec (CmdBuffer (cmd : cmds)) (next ())-evalFreeF exec (CmdBuffer cmds) (Free (Flush next)) =- exec (const $ Right ()) (Cmds cmds) >>= \case- Right _ -> evalFreeT exec def (next ())- Left err -> return (Left err)-evalFreeF _ _ (Free (Failed err)) =- return (Left err)+-- evalFreeF ::+-- Monad m =>+-- CommandExec m ->+-- CmdBuffer ->+-- FreeF TmuxThunk a (FreeT TmuxThunk m a) ->+-- m (Either TmuxError a)+-- evalFreeF _ (CmdBuffer []) (Pure a) =+-- pure (Right a)+-- evalFreeF exec (CmdBuffer cmds) (Pure a) =+-- Right a <$ exec (const $ Right ()) (Cmds cmds)+-- evalFreeF exec (CmdBuffer cmds) (Free (Read cmd decode next)) = do+-- a <- exec decode $ Cmds (cmd : cmds)+-- case a of+-- Right a' -> evalFreeT exec def (next a')+-- Left err -> pure (Left err)+-- evalFreeF exec (CmdBuffer cmds) (Free (Write cmd next)) =+-- evalFreeT exec (CmdBuffer (cmd : cmds)) (next ())+-- evalFreeF exec (CmdBuffer cmds) (Free (Flush next)) =+-- exec (const $ Right ()) (Cmds cmds) >>= \case+-- Right _ -> evalFreeT exec def (next ())+-- Left err -> pure (Left err)+-- evalFreeF _ _ (Free (Failed err)) =+-- pure (Left err) -evalFreeT ::- Monad m =>- CommandExec m ->- CmdBuffer ->- FreeT TmuxThunk m a ->- m (Either TmuxError a)-evalFreeT exec s (FreeT ma) = do- inner <- ma- evalFreeF exec s inner+-- evalFreeT ::+-- Monad m =>+-- CommandExec m ->+-- CmdBuffer ->+-- FreeT TmuxThunk m a ->+-- m (Either TmuxError a)+-- evalFreeT exec s (FreeT ma) = do+-- inner <- ma+-- evalFreeF exec s inner
@@ -1,40 +0,0 @@-module Chiasma.Monad.IndividualProcess where--import Control.Monad.Free (Free(..))--import Chiasma.Api.Class (TmuxApi(..))-import Chiasma.Data.Cmd (Cmd(..), Cmds(..))-import Chiasma.Data.TmuxError (TmuxError)-import Chiasma.Data.TmuxThunk (TmuxThunk(..))--type TmuxProg = Free TmuxThunk--newtype TmuxState = TmuxState [Cmd]--instance Default TmuxState where- def = TmuxState def--interpret ::- (TmuxApi m api, MonadDeepError e TmuxError m) =>- TmuxState ->- api ->- TmuxProg a ->- m a-interpret (TmuxState cmds) api (Pure a) = a <$ runCommands api (const $ Right ()) (Cmds cmds)-interpret (TmuxState cmds) api (Free (Read cmd decode next)) = do- a <- runCommands api decode $ Cmds (cmd : cmds)- interpret def api (next a)-interpret (TmuxState cmds) api (Free (Write cmd next)) =- interpret (TmuxState (cmd : cmds)) api (next ())-interpret (TmuxState cmds) api (Free (Flush next)) = do- _ <- runCommands api (const $ Right ()) (Cmds cmds)- interpret def api (next ())-interpret _ _ (Free (Failed err)) =- throwHoist err--runTmux ::- (TmuxApi m api, MonadDeepError e TmuxError m) =>- api ->- TmuxProg a ->- m a-runTmux = interpret def
@@ -1,80 +0,0 @@-module Chiasma.Monad.Stream where--import Conduit (ConduitT, Flush(..), runConduit, sinkList, yield, yieldMany, (.|))-import Control.Monad.Trans.Free (FreeT(..))-import qualified Data.Conduit.Combinators as Conduit (drop, take)--import Chiasma.Api.Class (TmuxApi(..))-import Chiasma.Codec.Decode (TmuxDecodeError)-import Chiasma.Data.Cmd (Cmd(..), Cmds(..))-import Chiasma.Data.TmuxError (TmuxError)-import qualified Chiasma.Data.TmuxError as TmuxError (TmuxError(ProcessFailed, DecodingFailed, CommandFailed))-import Chiasma.Data.TmuxThunk (TmuxThunk)-import Chiasma.Monad.EvalFreeT (evalFreeT)-import Chiasma.Native.StreamParse (TmuxOutputBlock)-import qualified Chiasma.Native.StreamParse as TmuxOutputBlock (TmuxOutputBlock(..))--type TmuxProg = FreeT TmuxThunk--type WriteCmd m =- ConduitT (Flush Cmd) Void m ()--type ReadOutput m =- ConduitT () TmuxOutputBlock m ()--handleProcessOutput ::- Cmds ->- (Text -> Either TmuxDecodeError a) ->- [TmuxOutputBlock] ->- Either TmuxError [a]-handleProcessOutput cs@(Cmds cmds) _ output | length output < length cmds =- Left $ TmuxError.ProcessFailed cs "tmux terminated before all commands were processed"-handleProcessOutput cmds decode output = do- readOutput <- foldl validate (Right []) output- traverse decode' readOutput- where- validate (Left err) _ = Left err- validate _ (TmuxOutputBlock.Success a) = Right a- validate _ (TmuxOutputBlock.Error a) = Left $ TmuxError.CommandFailed cmds a- decode' outputLine =- mapLeft (TmuxError.DecodingFailed cmds outputLine) . decode $ outputLine--executeCommands ::- MonadIO m =>- WriteCmd m ->- ReadOutput m ->- (Text -> Either TmuxDecodeError a) ->- Cmds ->- m (Either TmuxError [a])-executeCommands writeCmd readOutput decode cs@(Cmds cmds) = do- output <- runConduit $ do- yieldMany (Chunk <$> reverse cmds) .| writeCmd- yield Flush .| writeCmd- readOutput .| Conduit.take (length cmds) .| sinkList- return $ handleProcessOutput cs decode output--runTmuxProg ::- MonadIO m =>- TmuxProg m a ->- WriteCmd m ->- ReadOutput m ->- m (Either TmuxError a)-runTmuxProg prog writeCmd readOutput = do- runConduit $ readOutput .| Conduit.drop 1- evalFreeT (executeCommands writeCmd readOutput) def prog--runTmuxE ::- (MonadIO m, TmuxApi m api) =>- api ->- TmuxProg m a ->- m (Either TmuxError a)-runTmuxE api prog =- withTmux api (runTmuxProg prog)--runTmux ::- (MonadIO m, MonadDeepError e TmuxError m, TmuxApi m api) =>- api ->- TmuxProg m a ->- m a-runTmux api =- hoistEither <=< runTmuxE api
@@ -1,49 +1,49 @@ module Chiasma.Monad.Tmux where -import Control.Monad.Free (liftF)-import Control.Monad.Free.Class (MonadFree)+-- import Control.Monad.Free (liftF)+-- import Control.Monad.Free.Class (MonadFree) -import Chiasma.Codec (TmuxCodec, TmuxQuery(unQ))-import qualified Chiasma.Codec as TmuxCodec (TmuxCodec(decode, query))-import Chiasma.Data.Cmd (cmd)-import Chiasma.Data.TmuxError (TmuxError(InvalidOutput))-import Chiasma.Data.TmuxThunk (TmuxThunk(..))+-- import Chiasma.Codec (TmuxCodec, TmuxQuery(unQ))+-- import qualified Chiasma.Codec as TmuxCodec (TmuxCodec(decode, query))+-- import Chiasma.Data.Cmd (cmd)+-- import Chiasma.Data.TmuxError (TmuxError(InvalidOutput))+-- import Chiasma.Data.TmuxThunk (TmuxThunk(..)) -read :: ∀ a m . (TmuxCodec a, MonadFree TmuxThunk m) => Text -> [Text] -> m [a]-read name args =- liftF $ Read (cmd name (args <> formatArgs)) TmuxCodec.decode id- where- formatArgs = ["-F", "'", unQ (TmuxCodec.query @a), "'"]+-- read :: ∀ a m . (TmuxCodec a, MonadFree TmuxThunk m) => Text -> [Text] -> m [a]+-- read name args =+-- liftF $ Read (cmd name (args <> formatArgs)) TmuxCodec.decode id+-- where+-- formatArgs = ["-F", "'", unQ (TmuxCodec.query @a), "'"] -unsafeReadOne :: ∀ a m . (TmuxCodec a, MonadFree TmuxThunk m) => Text -> [Text] -> m a-unsafeReadOne name args = do- outputs <- read name args- case outputs of- [a] -> return a- [] -> liftF $ Failed $ InvalidOutput "no data" (name <> unwords args)- _ -> liftF $ Failed $ InvalidOutput "multiple outputs for `unsafeReadOne`" (name <> unwords args)+-- unsafeReadOne :: ∀ a m . (TmuxCodec a, MonadFree TmuxThunk m) => Text -> [Text] -> m a+-- unsafeReadOne name args = do+-- outputs <- read name args+-- case outputs of+-- [a] -> pure a+-- [] -> liftF $ Failed $ InvalidOutput "no data" (name <> unwords args)+-- _ -> liftF $ Failed $ InvalidOutput "multiple outputs for `unsafeReadOne`" (name <> unwords args) -readFirst :: ∀ a m . (TmuxCodec a, MonadFree TmuxThunk m) => Text -> [Text] -> m (Maybe a)-readFirst name args = do- outputs <- read name args- return $ case outputs of- (a : _) -> Just a- [] -> Nothing+-- readFirst :: ∀ a m . (TmuxCodec a, MonadFree TmuxThunk m) => Text -> [Text] -> m (Maybe a)+-- readFirst name args = do+-- outputs <- read name args+-- pure $ case outputs of+-- (a : _) -> Just a+-- [] -> Nothing -unsafeReadFirst :: ∀ a m . (TmuxCodec a, MonadFree TmuxThunk m) => Text -> [Text] -> m a-unsafeReadFirst name args = do- mayFirst <- readFirst name args- case mayFirst of- (Just a) -> return a- Nothing -> liftF $ Failed $ InvalidOutput "no data" (name <> unwords args)+-- unsafeReadFirst :: ∀ a m . (TmuxCodec a, MonadFree TmuxThunk m) => Text -> [Text] -> m a+-- unsafeReadFirst name args = do+-- mayFirst <- readFirst name args+-- case mayFirst of+-- (Just a) -> pure a+-- Nothing -> liftF $ Failed $ InvalidOutput "no data" (name <> unwords args) -readRaw :: ∀ m . (MonadFree TmuxThunk m) => Text -> [Text] -> m [Text]-readRaw name args =- liftF $ Read (cmd name args) Right id+-- readRaw :: ∀ m . (MonadFree TmuxThunk m) => Text -> [Text] -> m [Text]+-- readRaw name args =+-- liftF $ Read (cmd name args) Right id -write :: MonadFree TmuxThunk m => Text -> [Text] -> m ()-write name args = liftF $ Write (cmd name args) id+-- write :: MonadFree TmuxThunk m => Text -> [Text] -> m ()+-- write name args = liftF $ Write (cmd name args) id -flush :: MonadFree TmuxThunk m => m ()-flush =- liftF $ Flush id+-- flush :: MonadFree TmuxThunk m => m ()+-- flush =+-- liftF $ Flush id
@@ -1,67 +0,0 @@-{-# LANGUAGE UndecidableInstances #-}--module Chiasma.Native.Api where--import Chiasma.Data.TmuxError (TmuxError)-import Conduit (ConduitT, Flush, mapC, (.|))-import Control.Monad.Catch (MonadMask)-import qualified Control.Monad.Catch as Catch (bracket)-import Data.Conduit.Process.Typed (createSource)-import qualified Data.Text as Text (unwords)-import System.Process.Typed (- Process,- ProcessConfig,- getStdin,- getStdout,- proc,- setStdin,- setStdout,- startProcess,- stopProcess,- )-import Text.ParserCombinators.Parsec ()--import Chiasma.Api.Class (TmuxApi(..))-import Chiasma.Data.Cmd (Cmd(..), CmdArgs(..), CmdName(..))-import Chiasma.Data.Conduit (createSinkFlush)-import Chiasma.Native.Process (nativeTmuxProcess, socketArg)-import Chiasma.Native.StreamParse (parseConduit)--newtype TmuxNative =- TmuxNative { tmuxServerSocket :: Maybe FilePath }- deriving Show--formatCmd :: Cmd -> ByteString-formatCmd (Cmd (CmdName name) (CmdArgs args)) =- encodeUtf8 . Text.unwords $ name : args ++ ["\n"]--tmuxProcessConfig ::- MonadIO m =>- Maybe FilePath ->- ProcessConfig (ConduitT (Flush ByteString) Void m ()) (ConduitT () ByteString m ()) ()-tmuxProcessConfig sock =- cons args- where- cons =- setStdin createSinkFlush . setStdout createSource . proc "tmux"- args =- toString <$> (socketArg sock ++ ["-C", "attach"])--withProcess :: (MonadIO m, MonadMask m)- => ProcessConfig stdin stdout stderr- -> (Process stdin stdout stderr -> m a)- -> m a-withProcess config = Catch.bracket (startProcess config) stopProcess--instance (MonadIO m, MonadDeepError e TmuxError m, MonadMask m) => TmuxApi m TmuxNative where- runCommands (TmuxNative socket) =- nativeTmuxProcess socket-- withTmux (TmuxNative socket) f =- withProcess (tmuxProcessConfig socket) handler- where- handler prc =- let- stdin' = mapC (fmap formatCmd) .| getStdin prc- stdout' = getStdout prc .| parseConduit- in f stdin' stdout'
@@ -1,7 +1,7 @@ module Chiasma.Native.Parse where import qualified Data.Text as T (pack)-import Prelude hiding (many)+import Prelude hiding (many, try) import Text.Parsec ( ParseError, many,
@@ -1,57 +0,0 @@-module Chiasma.Native.Process where--import qualified Data.ByteString.Lazy as Lazy (ByteString)-import Data.ByteString.Lazy.Internal (unpackChars)-import qualified Data.Text as Text (intercalate, lines, pack, unwords)-import System.Exit (ExitCode(ExitSuccess))-import System.Process.Typed (ProcessConfig, byteStringInput, proc, readProcessStdout, setStdin)--import Chiasma.Codec.Decode (TmuxDecodeError)-import Chiasma.Data.Cmd (Cmd(..), CmdArgs(..), CmdName(..), Cmds(..))-import Chiasma.Data.TmuxError (TmuxError)-import qualified Chiasma.Data.TmuxError as TmuxError (- TmuxError(OutputParsingFailed, NoOutput, ProcessFailed, DecodingFailed),- )-import Chiasma.Native.Parse (resultLines)--cmdBytes :: [Text] -> Lazy.ByteString-cmdBytes cmds = encodeUtf8 $ Text.intercalate "\n" $ reverse $ "" : cmds--socketArg :: Maybe FilePath -> [Text]-socketArg (Just socket) = ["-S", toText socket]-socketArg Nothing = []--tmuxProcessConfig :: Maybe FilePath -> [Text] -> ProcessConfig () () ()-tmuxProcessConfig socket cmds =- cons args- where- cons =- setStdin (byteStringInput $ cmdBytes cmds) . proc "tmux"- args =- toString <$> (socketArg socket <> ["-C", "attach"])--handleProcessOutput :: Cmds -> ExitCode -> (Text -> Either TmuxDecodeError a) -> Text -> Either TmuxError [a]-handleProcessOutput cmds ExitSuccess decode out = do- outputs <- mapLeft (TmuxError.OutputParsingFailed cmds (Text.lines out)) $ resultLines out- case reverse outputs of- output : _ -> traverse decode' output- _ -> Left $ TmuxError.NoOutput cmds- where- decode' = mapLeft (TmuxError.DecodingFailed cmds out) . decode-handleProcessOutput cmds _ _ out =- Left $ TmuxError.ProcessFailed cmds out--formatCmd :: Cmd -> Text-formatCmd (Cmd (CmdName name) (CmdArgs args)) =- Text.unwords $ name : args--nativeTmuxProcess ::- (MonadIO m, MonadDeepError e TmuxError m) =>- Maybe FilePath ->- (Text -> Either TmuxDecodeError a) ->- Cmds ->- m [a]-nativeTmuxProcess socket decode cmds@(Cmds cmds') = do- let cmdLines = fmap formatCmd cmds'- (code, out) <- readProcessStdout $ tmuxProcessConfig socket cmdLines- hoistEither $ handleProcessOutput cmds code decode $ Text.pack $ unpackChars out
@@ -1,65 +0,0 @@-module Chiasma.Native.StreamParse(- parseConduit,- parseBlocks,- TmuxOutputBlock(..),-) where--import Conduit (ConduitT, mapC, (.|))-import Control.Monad.Catch (MonadThrow)-import Data.Attoparsec.ByteString (Parser)-import Data.Conduit.Attoparsec (conduitParser)-import qualified Data.Text as T (pack)-import Text.Parser.Char (CharParsing, anyChar, newline, string)-import Text.Parser.Combinators (choice, manyTill, notFollowedBy, skipMany, try)-import Text.Parser.LookAhead (LookAheadParsing, lookAhead)--data End =- EndSuccess- |- EndError- deriving (Eq, Show)--data TmuxOutputBlock =- Success [Text]- |- Error [Text]- deriving (Eq, Show)--tillEol :: (Alternative m, CharParsing m) => m Text-tillEol = T.pack <$> manyTill anyChar newline--beginLine :: (Alternative m, CharParsing m, Monad m) => m ()-beginLine = void $ string "%begin" >> tillEol--endLine :: (Alternative m, CharParsing m) => m End-endLine = do- end <- choice [EndSuccess <$ string "%end", EndError <$ string "%error"]- _ <- tillEol- return end--notBeginLine :: (Alternative m, CharParsing m, Monad m) => m ()-notBeginLine = void $ notFollowedBy (string "%begin") >> tillEol---- |Parse a sequence of lines between a %start and a %end line.--- Tmux pads output lines with a single space on both sides, so strip those if the leading one is present.-parseBlock :: (Alternative m, CharParsing m, Monad m, LookAheadParsing m) => m TmuxOutputBlock-parseBlock = do- _ <- skipMany notBeginLine- _ <- beginLine- dataLines <- manyTill tillEol $ try $ lookAhead endLine- end <- endLine- return $ case end of- EndSuccess -> Success dataLines- EndError -> Error dataLines--parseBlocks :: (Alternative m, CharParsing m, Monad m, LookAheadParsing m) => m [TmuxOutputBlock]-parseBlocks = do- result <- many (try parseBlock)- skipMany tillEol- return result--parser :: Parser TmuxOutputBlock-parser = parseBlock--parseConduit :: MonadThrow m => ConduitT ByteString TmuxOutputBlock m ()-parseConduit = conduitParser parser .| mapC snd
@@ -0,0 +1,46 @@+module Chiasma.Native.TmuxOutputBlock where++import Data.Attoparsec.ByteString (Parser)+import Data.ByteString.Internal (packChars)+import Prelude hiding (try)+import Text.Parser.Char (CharParsing, anyChar, newline, string)+import Text.Parser.Combinators (choice, manyTill, notFollowedBy, skipMany, try)+import Text.Parser.LookAhead (LookAheadParsing, lookAhead)++import Chiasma.Data.TmuxOutputBlock (End (EndError, EndSuccess), TmuxOutputBlock (Error, Success))++tillEol :: (Alternative m, CharParsing m) => m Text+tillEol = decodeUtf8 . packChars <$> manyTill anyChar newline++beginLine :: (Alternative m, CharParsing m, Monad m) => m ()+beginLine = void $ string "%begin" >> tillEol++endLine :: (Alternative m, CharParsing m) => m End+endLine = do+ end <- choice [EndSuccess <$ string "%end", EndError <$ string "%error"]+ _ <- tillEol+ pure end++notBeginLine :: (Alternative m, CharParsing m, Monad m) => m ()+notBeginLine = void $ notFollowedBy (string "%begin") >> tillEol++-- |Parse a sequence of lines between a %start and a %end line.+-- Tmux pads output lines with a single space on both sides, so strip those if the leading one is present.+parseBlock :: (Alternative m, CharParsing m, Monad m, LookAheadParsing m) => m TmuxOutputBlock+parseBlock = do+ _ <- skipMany notBeginLine+ _ <- beginLine+ dataLines <- manyTill tillEol $ try (lookAhead endLine)+ end <- endLine+ pure $ case end of+ EndSuccess -> Success dataLines+ EndError -> Error dataLines++parseBlocks :: (Alternative m, CharParsing m, Monad m, LookAheadParsing m) => m [TmuxOutputBlock]+parseBlocks = do+ result <- many (try parseBlock)+ skipMany tillEol+ pure result++parser :: Parser TmuxOutputBlock+parser = parseBlock
@@ -1,61 +1,64 @@ module Chiasma.Pack where -import qualified Chiasma.Codec.Data as Codec (Window(Window))+import Control.Lens (mapMOf_)+import qualified Data.List.NonEmpty as NonEmpty (reverse, toList)+import qualified Data.Set as Set (fromList, size)+import Prettyprinter (Doc, line, pretty, (<+>))++import qualified Chiasma.Codec.Data.Window as Codec (Window (Window)) import Chiasma.Command.Pane (movePane, resizePane)+import Chiasma.Data.Axis (Axis (Horizontal, Vertical)) import Chiasma.Data.TmuxId (PaneId)-import Chiasma.Data.TmuxThunk (TmuxThunk) import Chiasma.Data.Views (Views)-import Chiasma.Data.WindowState (WindowState(..))-import Chiasma.Ui.Data.Measure (MLayout(..), MPane(..), MeasureTree, MeasureTreeSub, Measured(Measured))-import Chiasma.Ui.Data.Tree (Node(Sub, Leaf), Tree(Tree))+import Chiasma.Data.WindowState (WindowState (..))+import Chiasma.Effect.TmuxApi (Tmux)+import Chiasma.Ui.Data.Measure (MLayout (..), MPane (..), MeasureTree, MeasureTreeSub, Measured (Measured))+import Chiasma.Ui.Data.Tree (Node (Leaf, Sub), Tree (Tree)) import qualified Chiasma.Ui.Data.Tree as Tree (subTree) import Chiasma.Ui.Measure (measureTree) import Chiasma.View (viewsLog)-import Control.Lens (each, mapMOf_)-import Control.Monad.Free.Class (MonadFree)-import qualified Data.List.NonEmpty as NonEmpty (reverse, toList)-import qualified Data.Set as Set (fromList, size)-import Data.Text.Prettyprint.Doc (Doc, line, pretty, (<+>)) packPane ::- (MonadDeepState s Views m, MonadFree TmuxThunk m) =>+ Members [AtomicState Views, Tmux] r => PaneId ->- Bool ->+ Axis -> PaneId ->- m ()-packPane refId vertical paneId =- when (paneId /= refId) $ movePane paneId refId vertical+ Sem r ()+packPane refId axis paneId =+ when (paneId /= refId) do+ movePane paneId refId axis positionView ::- (MonadDeepState s Views m, MonadFree TmuxThunk m) =>- Bool ->+ Members [AtomicState Views, Tmux] r =>+ Axis -> PaneId -> MeasureTreeSub ->- m ()-positionView vertical refId =+ Sem r ()+positionView axis refId = position where position (Sub (Tree (Measured _ (MLayout layoutRef _ _ _)) _)) =- packPane refId vertical layoutRef+ packPane refId axis layoutRef position (Leaf (Measured _ (MPane paneId _ _))) =- packPane refId vertical paneId+ packPane refId axis paneId -describeVertical :: Bool -> Doc a-describeVertical True = "vertically"-describeVertical False = "horizontally"+describeAxis :: Axis -> Doc a+describeAxis = \case+ Vertical -> "vertically"+ Horizontal -> "horizontally" resizeView ::- MonadDeepState s Views m =>- MonadFree TmuxThunk m =>- Bool ->+ Members [AtomicState Views, Tmux] r =>+ Axis -> MeasureTreeSub ->- m ()-resizeView vertical (Sub (Tree (Measured size (MLayout refId _ _ _)) _)) = do- viewsLog $ "resizing layout with ref" <+> pretty refId <+> "to" <+> pretty size <+> describeVertical vertical- resizePane refId vertical size-resizeView vertical (Leaf (Measured size (MPane paneId _ _))) = do- viewsLog $ "resizing pane" <+> pretty paneId <+> "to" <+> pretty size <+> describeVertical vertical- resizePane paneId vertical size+ Sem r ()+resizeView axis = \case+ Sub (Tree (Measured size (MLayout refId _ _ _)) _) -> do+ viewsLog ("resizing layout with ref" <+> pretty refId <+> "to" <+> pretty size <+> describeAxis axis)+ resizePane refId axis size+ Leaf (Measured size (MPane paneId _ _)) -> do+ viewsLog $ "resizing pane" <+> pretty paneId <+> "to" <+> pretty size <+> describeAxis axis+ resizePane paneId axis size needPositioning :: NonEmpty MeasureTreeSub ->@@ -83,26 +86,26 @@ offPos packTree ::- (MonadDeepState s Views m, MonadFree TmuxThunk m) =>+ Members [AtomicState Views, Tmux] r => MeasureTree ->- m ()+ Sem r () packTree = pack where- pack (Tree (Measured _ (MLayout ref _ _ vertical)) sub) = do+ pack (Tree (Measured _ (MLayout ref _ _ axis)) sub) = do when needPos runPos mapMOf_ (each . Tree.subTree) pack sub- traverse_ (resizeView vertical) sub+ traverse_ (resizeView axis) sub where needPos = needPositioning sub runPos = do viewsLog $ "repositioning views" <+> pretty sub- traverse_ (positionView vertical ref) (NonEmpty.reverse sub)+ traverse_ (positionView axis ref) (NonEmpty.reverse sub) packWindow ::- (MonadDeepState s Views m, MonadFree TmuxThunk m) =>+ Members [AtomicState Views, Tmux] r => WindowState ->- m ()+ Sem r () packWindow (WindowState (Codec.Window _ width height) _ _ tree _) = do let measures = measureTree tree width height viewsLog $ "measured tree:" <> line <> pretty measures
@@ -2,13 +2,16 @@ import Chiasma.Data.Ident (Ident) import Chiasma.Data.TmuxId (PaneId)-import qualified Chiasma.Data.View as Tmux (View(View))+import qualified Chiasma.Data.View as Tmux (View (View)) import Chiasma.Data.Views (Views) import qualified Chiasma.View as Views (insertPane) -addPane :: MonadDeepState s Views m => Ident -> m (Tmux.View PaneId)-addPane ident = do- modify $ Views.insertPane pane- return pane+addPane ::+ Member (AtomicState Views) r =>+ Ident ->+ Sem r (Tmux.View PaneId)+addPane ident =+ pane <$ atomicModify' (Views.insertPane pane) where- pane = Tmux.View ident Nothing+ pane =+ Tmux.View ident Nothing
@@ -0,0 +1,16 @@+module Chiasma.Path where++import qualified Data.Text as Text+import Path (Path, toFilePath)++pathText :: Path b t -> Text+pathText =+ toText . toFilePath+{-# inline pathText #-}++pathText' :: Path b t -> Text+pathText' p =+ case pathText p of+ "/" -> "/"+ t -> Text.dropWhileEnd (== '/') t+{-# inline pathText' #-}
@@ -1,71 +0,0 @@-{-# LANGUAGE NoImplicitPrelude #-}--module Chiasma.Prelude (- module Cornea,- module Data.Default,- module Data.Foldable,- module Relude,- dbg,- dbgs,- dbgm,- makeClassy,- mapLeft,- tuple,- undefined,- unit,- unsafeLogAnd,- unsafeLogS,- unsafeLogSAnd,- (<$$>),-) where--import Control.Lens (makeClassy)-import Cornea-import Data.Default (Default(def))-import Data.Either.Combinators (mapLeft)-import Data.Foldable (foldl, traverse_)-import Data.Functor.Syntax ((<$$>))-import GHC.Err (undefined)-import GHC.IO.Unsafe (unsafePerformIO)-import Relude hiding (Type, ask, asks, get, gets, hoistEither, hoistMaybe, local, modify, put, state, undefined)--dbg :: Monad m => Text -> m ()-dbg msg = do- () <- return $ unsafePerformIO (putStrLn (toString msg))- return ()--dbgs :: Monad m => Show a => a -> m ()-dbgs =- dbg . show--dbgm :: Monad m => Show a => m a -> m a-dbgm ma = do- a <- ma- dbgs a- return a--unit ::- Applicative f =>- f ()-unit =- pure ()--tuple ::- Applicative f =>- f a ->- f b ->- f (a, b)-tuple fa fb =- (,) <$> fa <*> fb--unsafeLogSAnd :: Show a => a -> b -> b-unsafeLogSAnd a b =- unsafePerformIO $ print a >> return b--unsafeLogAnd :: Text -> b -> b-unsafeLogAnd a b =- unsafePerformIO $ putStrLn (toString a) >> return b--unsafeLogS :: Show a => a -> a-unsafeLogS a =- unsafePerformIO $ print a >> return a
@@ -1,41 +1,47 @@ module Chiasma.Render where -import qualified Chiasma.Codec.Data as Codec (Window(Window))+import Path (Abs, Dir, Path)+import Prettyprinter (line, pretty)++import Chiasma.Codec.Data.Pane (Pane)+import qualified Chiasma.Codec.Data.Window as Codec (Window (Window))+import Chiasma.Data.CodecError (CodecError) import Chiasma.Data.Ident (Ident, identText)+import Chiasma.Data.Panes (TmuxPanes)+import qualified Chiasma.Data.RenderError as RenderError import Chiasma.Data.RenderError (RenderError)-import Chiasma.Data.TmuxThunk (TmuxThunk) import Chiasma.Data.Views (Views)+import Chiasma.Effect.TmuxApi (Tmux) import Chiasma.Pack (packWindow) import Chiasma.Session (ensureSession, findOrCreateSession) import Chiasma.Ui.Data.RenderableTree (RenderableTree) import Chiasma.Ui.Data.View (ViewTree) import Chiasma.View (viewsLog) import Chiasma.Window (ensureView, ensureWindow, findOrCreateWindow, windowState)-import Control.Monad.Error.Class (MonadError)-import Control.Monad.Free.Class (MonadFree)-import Data.Text.Prettyprint.Doc (line, pretty) renderTree ::- (MonadDeepState s Views m, MonadFree TmuxThunk m) =>+ Members [TmuxPanes Pane, AtomicState Views, Tmux] r => Ident -> Codec.Window -> RenderableTree ->- m ()+ Sem r () renderTree windowIdent window tree = do viewsLog $ pretty ("rendering tree in window " <> identText windowIdent <> ":") <> line <> pretty tree wState <- windowState windowIdent window tree packWindow wState render ::- (MonadDeepState s Views m, MonadFree TmuxThunk m, MonadError RenderError m) =>- FilePath ->+ Members [TmuxPanes Pane !! CodecError, AtomicState Views, Tmux !! CodecError, Stop RenderError] r =>+ Path Abs Dir -> Ident -> Ident -> ViewTree ->- m ()+ Sem r () render cwd sessionIdent windowIdent tree = do- initialSession <- findOrCreateSession sessionIdent- initialWindow <- findOrCreateWindow windowIdent- (sid, newSessionWid) <- ensureSession initialSession initialWindow- window@(Codec.Window windowId _ _) <- ensureWindow sid initialWindow newSessionWid tree- traverse_ (renderTree windowIdent window) =<< ensureView cwd windowId tree+ resumeHoist @_ @(TmuxPanes _) RenderError.Codec do+ resumeHoist @_ @Tmux RenderError.Codec do+ initialSession <- findOrCreateSession sessionIdent+ initialWindow <- findOrCreateWindow windowIdent+ (sid, newSessionWid) <- mapStop RenderError.Layout (ensureSession initialSession initialWindow)+ window@(Codec.Window windowId _ _) <- ensureWindow sid initialWindow newSessionWid tree+ traverse_ (renderTree windowIdent window) =<< ensureView cwd windowId tree
@@ -1,49 +1,46 @@-{-# LANGUAGE FlexibleContexts #-}- module Chiasma.Session where -import Control.Monad.Free.Class (MonadFree)--import qualified Chiasma.Codec.Data as Codec (Session(Session), Window(Window))+import Exon (exon)+import qualified Chiasma.Codec.Data.Session as Codec (Session (Session))+import qualified Chiasma.Codec.Data.Window as Codec (Window (Window)) import Chiasma.Command.Session (existingSessionId, newSession) import Chiasma.Command.Window (newSessionWindow) import Chiasma.Data.Ident (Ident)+import Chiasma.Data.LayoutError (LayoutError) import Chiasma.Data.TmuxId (SessionId, WindowId)-import Chiasma.Data.TmuxThunk (TmuxThunk)-import qualified Chiasma.Data.View as Tmux (View(viewId, viewIdent), setViewId)+import qualified Chiasma.Data.View as Tmux (View (viewId, viewIdent), setViewId) import Chiasma.Data.Views (Views)+import Chiasma.Effect.TmuxApi (Tmux) import Chiasma.View (findOrCreateView, viewsLogS) import qualified Chiasma.View as Views (insertSession, session, updateSession, updateWindow) findOrCreateSession ::- MonadDeepState s Views m =>+ Member (AtomicState Views) r => Ident ->- m (Tmux.View SessionId)+ Sem r (Tmux.View SessionId) findOrCreateSession = findOrCreateView Views.session Views.insertSession spawnSession ::- MonadDeepState s Views m =>- MonadFree TmuxThunk m =>+ Members [AtomicState Views, Tmux, Stop LayoutError] r => Tmux.View SessionId -> Tmux.View WindowId ->- m (SessionId, WindowId)+ Sem r (SessionId, WindowId) spawnSession session' window = do- Codec.Session sid <- newSession (Tmux.viewIdent session')- modify $ Views.updateSession $ Tmux.setViewId sid session'+ Codec.Session sid _ <- newSession (Tmux.viewIdent session')+ atomicModify' (Views.updateSession (Tmux.setViewId sid session')) Codec.Window wid _ _ <- newSessionWindow sid- modify $ Views.updateWindow $ Tmux.setViewId wid window- viewsLogS $ "spawned session " <> show session' <> " with id " <> show sid <> " and window id " <> show wid- return (sid, wid)+ atomicModify' (Views.updateWindow (Tmux.setViewId wid window))+ viewsLogS [exon|spawned session #{show session'} with id #{show sid} and window id #{show wid}|]+ pure (sid, wid) ensureSession ::- MonadDeepState s Views m =>- MonadFree TmuxThunk m =>+ Members [AtomicState Views, Tmux, Stop LayoutError] r => Tmux.View SessionId -> Tmux.View WindowId ->- m (SessionId, Maybe WindowId)+ Sem r (SessionId, Maybe WindowId) ensureSession session' window = do existing <- join <$> traverse existingSessionId (Tmux.viewId session') case existing of- Just sid -> return (sid, Nothing)+ Just sid -> pure (sid, Nothing) Nothing -> second Just <$> spawnSession session' window
@@ -1,31 +0,0 @@-module Chiasma.Test.File(- tempDirIO,- tempDir,- fixture,-) where--import System.Directory (canonicalizePath, createDirectoryIfMissing, removePathForcibly)-import System.FilePath ((</>))--testDir :: Text -> IO FilePath-testDir prefix = canonicalizePath $ "test" </> toString prefix---- raises exception if cwd is not the package root so we don't damage anything-tempDirIO :: Text -> FilePath -> IO FilePath-tempDirIO prefix path = do- base <- testDir prefix- let dir = base </> "temp"- removePathForcibly dir- createDirectoryIfMissing False dir- let absPath = dir </> path- createDirectoryIfMissing True absPath- return absPath--tempDir :: MonadIO m => Text -> FilePath -> m FilePath-tempDir prefix path =- liftIO $ tempDirIO prefix path--fixture :: MonadIO m => Text -> FilePath -> m FilePath-fixture prefix path = do- base <- liftIO $ testDir prefix- return $ base </> "fixtures" </> path
@@ -1,67 +0,0 @@-module Chiasma.Test.Screenshot where--import Control.Monad.Free.Class (MonadFree)-import qualified Data.ByteString as ByteString (writeFile)-import qualified Data.Text as Text (lines, unlines)-import qualified Data.Text.Encoding as Text (encodeUtf8)-import System.FilePath (takeDirectory, (</>))-import UnliftIO.Directory (createDirectoryIfMissing, doesFileExist)--import Chiasma.Command.Pane (capturePane)-import Chiasma.Data.TmuxId (PaneId(PaneId))-import Chiasma.Data.TmuxThunk (TmuxThunk)--loadScreenshot :: MonadIO m => FilePath -> m (Maybe Text)-loadScreenshot path =- ifM (doesFileExist path) (Just . toText <$> liftIO (readFile path)) (pure Nothing)--storeScreenshot :: MonadIO m => FilePath -> [Text] -> m ()-storeScreenshot path text = do- createDirectoryIfMissing True (takeDirectory path)- liftIO $ ByteString.writeFile path (Text.encodeUtf8 . Text.unlines $ text)--takeScreenshot ::- MonadFree TmuxThunk m =>- MonadIO m =>- Int ->- m [Text]-takeScreenshot =- capturePane . PaneId--recordScreenshot ::- MonadFree TmuxThunk m =>- MonadIO m =>- FilePath ->- Int ->- m ()-recordScreenshot path paneId = do- current <- takeScreenshot paneId- storeScreenshot path current--testScreenshot ::- MonadFree TmuxThunk m =>- MonadIO m =>- FilePath ->- Int ->- m (Maybe ([Text], [Text]))-testScreenshot path pane = do- current <- takeScreenshot pane- loadScreenshot path >>= check current- where- check current (Just existing) =- return $ Just (current, Text.lines existing)- check current Nothing =- Nothing <$ storeScreenshot path current--screenshot ::- MonadFree TmuxThunk m =>- MonadIO m =>- Bool ->- FilePath ->- Text ->- Int ->- m (Maybe ([Text], [Text]))-screenshot record storage name paneId =- if record then Nothing <$ recordScreenshot path paneId else testScreenshot path paneId- where- path = storage </> toString name
@@ -1,121 +0,0 @@-module Chiasma.Test.Tmux where--import Chiasma.Data.TmuxError (TmuxError)-import Chiasma.Monad.Stream (runTmux)-import qualified Chiasma.Monad.Tmux as Tmux (write)-import Chiasma.Native.Api (TmuxNative(..))-import Control.Concurrent (threadDelay)-import System.Directory (doesFileExist)-import System.FilePath ((</>))-import System.Posix.IO (fdToHandle)-import System.Posix.Pty (Pty, createPty, resizePty)-import qualified System.Posix.Signals as Signal (killProcess, signalProcess)-import System.Posix.Terminal (openPseudoTerminal)-import System.Process (getPid)-import System.Process.Typed (- Process,- ProcessConfig,- StreamSpec,- proc,- setStderr,- setStdin,- setStdout,- unsafeProcessHandle,- useHandleClose,- withProcessWait,- )-import UnliftIO (finally, throwString)-import UnliftIO.Exception (tryAny)-import UnliftIO.Temporary (withSystemTempDirectory)--import Chiasma.Test.File (fixture)--data Terminal = Terminal Handle Pty--data TmuxTestConf =- TmuxTestConf {- ttcWidth :: Int,- ttcHeight :: Int,- ttcFontSize :: Int,- ttcGui :: Bool- }- deriving (Eq, Show)--instance Default TmuxTestConf where- def = TmuxTestConf 240 61 18 True--usleep :: MonadIO f => Double -> f ()-usleep =- liftIO . threadDelay . round--sleep :: MonadIO f => Double -> f ()-sleep seconds =- usleep $ seconds * 1e6--unsafeTerminal :: IO Terminal-unsafeTerminal = do- (_, slave) <- openPseudoTerminal- mayPty <- createPty slave- handle <- fdToHandle slave- pty <- maybe (throwString "couldn't spawn pty") return mayPty- return $ Terminal handle pty--urxvtArgs :: Int -> Int -> Int -> [Text]-urxvtArgs width height fontSize =- ["-geometry", show width <> "x" <> show height, "-fn", "xft:monospace:size=" <> show fontSize, "-e", "tmux"]--testTmuxProcessConfig :: TmuxTestConf -> FilePath -> FilePath -> Terminal -> IO (ProcessConfig () () ())-testTmuxProcessConfig (TmuxTestConf width height fontSize gui) socket confFile (Terminal handle pty) = do- confFileExists <- doesFileExist confFile- resizePty pty (width, height)- let- stream :: StreamSpec st ()- stream = useHandleClose handle- stdio = setStdin stream . setStdout stream . setStderr stream- tmuxArgs = ["-S", toText socket, "-f", toText confFileArg]- confFileArg = if confFileExists then confFile else "/dev/null"- prc =- if gui- then proc "urxvt" (toString <$> urxvtArgs width height fontSize ++ tmuxArgs)- else proc "tmux" (toString <$> tmuxArgs)- return $ stdio prc--killPid :: Integral a => a -> IO ()-killPid =- void . tryAny . Signal.signalProcess Signal.killProcess . fromIntegral--killProcess :: TmuxNative -> Process () () () -> IO ()-killProcess api prc = do- _ <- runExceptT @TmuxError $ runTmux api $ Tmux.write "kill-server" []- let handle = unsafeProcessHandle prc- mayPid <- getPid handle- maybe (return ()) killPid mayPid---- FIXME find a way to wait for tmux deterministically instead of sleeping--- if the first tmux control mode process from a TmuxProg runs before urxvt has started the server,--- it will not use the test tmux.conf--- maybe start tmux first, then urxvt?-runAndKillTmux :: (TmuxNative -> IO a) -> TmuxNative -> Process () () () -> IO a-runAndKillTmux thunk api prc = do- sleep 0.2- finally (thunk api) (killProcess api prc)--withTestTmux :: TmuxTestConf -> (TmuxNative -> IO a) -> FilePath -> IO a-withTestTmux tConf thunk tempDir = do- let socket = tempDir </> "tmux_socket"- conf <- fixture "u" "tmux.conf"- terminal <- unsafeTerminal- pc <- testTmuxProcessConfig tConf socket conf terminal- withProcessWait pc $ runAndKillTmux thunk (TmuxNative $ Just socket)--tmuxSpec' :: TmuxTestConf -> (TmuxNative -> IO a) -> IO a-tmuxSpec' conf thunk =- withSystemTempDirectory "chiasma-test" $ withTestTmux conf thunk--tmuxSpec :: (TmuxNative -> IO a) -> IO a-tmuxSpec =- tmuxSpec' def { ttcGui = False }--tmuxGuiSpec :: (TmuxNative -> IO a) -> IO a-tmuxGuiSpec =- tmuxSpec' def
@@ -0,0 +1,89 @@+module Chiasma.Tmux where++import Polysemy.Internal (hoistSem)+import Polysemy.Internal.Sing (KnownList (singList))+import Polysemy.Internal.Union (hoist, weakenMid)++import Chiasma.Data.Panes (Panes, TmuxPanes)+import Chiasma.Effect.Codec (Codec)+import Chiasma.Effect.TmuxApi (TmuxApi)+import Chiasma.Effect.TmuxClient (ScopedTmux, TmuxClient)+import Chiasma.Interpreter.TmuxApi (+ InterpretApis (interpretApis),+ RestopApis (restopApis),+ TmuxApis,+ interpretTmuxApi,+ type (<$>),+ )++withTmuxApis' ::+ ∀ commands err i o r a .+ InterpretApis commands err i o r =>+ Member (ScopedTmux i o) r =>+ Sem (TmuxApis commands err ++ TmuxClient i o : r) a ->+ Sem r a+withTmuxApis' =+ scoped_ . interpretApis @commands @err++insertAfter ::+ ∀ left e r a .+ KnownList left =>+ Sem (left ++ r) a ->+ Sem (left ++ e : r) a+insertAfter =+ hoistSem $ hoist (insertAfter @left @e @r) . weakenMid @r (singList @left) (singList @'[e])++withTmuxApis ::+ ∀ commands err i o r .+ KnownList (TmuxApis commands err) =>+ InterpretApis commands err i o r =>+ Member (ScopedTmux i o) r =>+ InterpretersFor (TmuxApis commands err) r+withTmuxApis =+ scoped_ .+ interpretApis @commands @err .+ insertAfter @(TmuxApis commands err) @(TmuxClient i o) @r++withTmuxApis_ ::+ ∀ commands err i o apis r .+ apis ~ TmuxApi <$> commands =>+ KnownList apis =>+ RestopApis commands err i o r =>+ Member (ScopedTmux i o) r =>+ InterpretersFor apis r+withTmuxApis_ =+ scoped_ .+ restopApis @commands @err .+ insertAfter @apis @(TmuxClient i o) @r++withTmux ::+ ∀ command err i o r .+ Members [ScopedTmux i o, Codec command i o !! err] r =>+ InterpreterFor (TmuxApi command !! err) r+withTmux =+ scoped_ . interpretTmuxApi . raiseUnder++withTmux_ ::+ ∀ command err i o r .+ Members [ScopedTmux i o, Codec command i o !! err, Stop err] r =>+ InterpreterFor (TmuxApi command) r+withTmux_ =+ scoped_ .+ interpretTmuxApi .+ raiseUnder .+ restop @err @(TmuxApi command) .+ raiseUnder++withPanes ::+ ∀ p err i o r .+ Members [ScopedTmux i o, Codec (Panes p) i o !! err] r =>+ InterpreterFor (TmuxPanes p !! err) r+withPanes =+ withTmux++withPanes_ ::+ ∀ p err i o r .+ Members [ScopedTmux i o, Codec (Panes p) i o !! err, Stop err] r =>+ InterpreterFor (TmuxPanes p) r+withPanes_ =+ withTmux_
@@ -0,0 +1,7 @@+module Chiasma.TmuxApi (+ module Chiasma.Effect.TmuxApi,+ module Chiasma.Interpreter.TmuxApi,+) where++import Chiasma.Effect.TmuxApi+import Chiasma.Interpreter.TmuxApi
@@ -0,0 +1,68 @@+module Chiasma.TmuxNative where++import Polysemy.Internal.Sing (KnownList)++import Chiasma.Data.CodecError (CodecError)+import Chiasma.Data.Panes (Panes, TmuxPanes)+import Chiasma.Data.TmuxRequest (TmuxRequest)+import Chiasma.Data.TmuxResponse (TmuxResponse)+import Chiasma.Effect.Codec (NativeCodecE)+import Chiasma.Effect.TmuxApi (TmuxApi)+import Chiasma.Effect.TmuxClient (NativeTmux, TmuxClient)+import Chiasma.Interpreter.TmuxApi (InterpretApisNative, RestopApisNative, TmuxApis, type (<$>))+import Chiasma.Tmux (withTmux, withTmuxApis, withTmuxApis', withTmuxApis_, withTmux_)++withTmuxApisNative' ::+ ∀ commands r a .+ InterpretApisNative commands r =>+ Member NativeTmux r =>+ Sem (TmuxApis commands CodecError ++ TmuxClient TmuxRequest TmuxResponse : r) a ->+ Sem r a+withTmuxApisNative' =+ withTmuxApis' @commands @CodecError++withTmuxApisNative ::+ ∀ commands r .+ KnownList (TmuxApis commands CodecError) =>+ InterpretApisNative commands r =>+ Member NativeTmux r =>+ InterpretersFor (TmuxApis commands CodecError) r+withTmuxApisNative =+ withTmuxApis @commands @CodecError++withTmuxApisNative_ ::+ ∀ commands r .+ KnownList (TmuxApi <$> commands) =>+ RestopApisNative commands r =>+ Member NativeTmux r =>+ InterpretersFor (TmuxApi <$> commands) r+withTmuxApisNative_ =+ withTmuxApis_ @commands @CodecError++withTmuxNative ::+ ∀ command r .+ Members [NativeTmux, NativeCodecE command] r =>+ InterpreterFor (TmuxApi command !! CodecError) r+withTmuxNative =+ withTmux++withTmuxNative_ ::+ ∀ command r .+ Members [NativeTmux, NativeCodecE command, Stop CodecError] r =>+ InterpreterFor (TmuxApi command) r+withTmuxNative_ =+ withTmux_++withPanesNative ::+ ∀ p r .+ Members [NativeTmux, NativeCodecE (Panes p)] r =>+ InterpreterFor (TmuxPanes p !! CodecError) r+withPanesNative =+ withTmuxNative++withPanesNative_ ::+ ∀ p r .+ Members [NativeTmux, NativeCodecE (Panes p), Stop CodecError] r =>+ InterpreterFor (TmuxPanes p) r+withPanesNative_ =+ withTmuxNative_
@@ -1,8 +1,10 @@ module Chiasma.Ui.Data.Measure where -import Chiasma.Data.TmuxId (PaneId(..))-import Data.Text.Prettyprint.Doc (Pretty(..), (<+>))+import Control.Lens (makeClassy)+import Prettyprinter (Pretty (..), (<+>)) +import Chiasma.Data.Axis (Axis)+import Chiasma.Data.TmuxId (PaneId (..)) import Chiasma.Ui.Data.Tree (NNode, NTree) data MPane =@@ -11,7 +13,7 @@ _mainPosition :: Int, _offPosition :: Int }- deriving (Eq, Show)+ deriving stock (Eq, Show) makeClassy ''MPane @@ -20,9 +22,9 @@ _reference :: PaneId, _lMainPosition :: Int, _lOffPosition :: Int,- _vertical :: Bool+ _axis :: Axis }- deriving (Eq, Show)+ deriving stock (Eq, Show) makeClassy ''MLayout @@ -31,7 +33,7 @@ _size :: Int, _view :: a }- deriving (Eq, Show)+ deriving stock (Eq, Show) makeClassy ''Measured @@ -39,9 +41,8 @@ type MeasureTreeSub = NNode (Measured MLayout) (Measured MPane) instance Pretty MLayout where- pretty (MLayout (PaneId refId) mainPos offPos vertical') =- "l –" <+> "ref:" <+> pretty refId <+> "pos:" <+> pretty mainPos <+> "(" <> pretty offPos <> ")" <+>- if vertical' then "v" else "h"+ pretty (MLayout (PaneId refId) mainPos offPos axis') =+ "l –" <+> "ref:" <+> pretty refId <+> "pos:" <+> pretty mainPos <+> "(" <> pretty offPos <> ")" <+> pretty axis' instance Pretty MPane where pretty (MPane (PaneId paneId') mainPos offPos) =
@@ -1,18 +1,19 @@ module Chiasma.Ui.Data.RenderableTree where -import Data.Text.Prettyprint.Doc (Doc, Pretty(..), emptyDoc, space, (<+>))+import Prettyprinter (Doc, Pretty (..), emptyDoc, space, (<+>)) -import Chiasma.Data.TmuxId (PaneId(..))+import Chiasma.Data.Axis (Axis)+import Chiasma.Data.TmuxId (PaneId (..)) import Chiasma.Ui.Data.Tree (NNode, NTree)-import Chiasma.Ui.Data.ViewGeometry (ViewGeometry(ViewGeometry))+import Chiasma.Ui.Data.ViewGeometry (ViewGeometry (ViewGeometry)) import Chiasma.Ui.Data.ViewState (ViewState) data RLayout = RLayout { _ref :: RPane,- _vertical :: Bool+ _axis :: Axis }- deriving (Eq, Show)+ deriving stock (Eq, Show) data RPane = RPane {@@ -20,7 +21,7 @@ _top :: Int, _left :: Int }- deriving (Eq, Show)+ deriving stock (Eq, Show) data Renderable a = Renderable {@@ -28,7 +29,7 @@ _geometry :: ViewGeometry, _view :: a }- deriving (Eq, Show)+ deriving stock (Eq, Show) type RenderableLayout = Renderable RLayout type RenderablePane = Renderable RPane@@ -36,8 +37,8 @@ type RenderableNode = NNode RenderableLayout RenderablePane instance Pretty RLayout where- pretty (RLayout (RPane (PaneId refId) _ _) vertical) =- "l –" <+> "ref:" <+> pretty refId <+> "pos:" <+> if vertical then "v" else "h"+ pretty (RLayout (RPane (PaneId refId) _ _) axis) =+ "l –" <+> "ref:" <+> pretty refId <+> "pos:" <+> pretty axis instance Pretty RPane where pretty (RPane (PaneId paneId) top left) =
@@ -1,8 +1,7 @@-{-# LANGUAGE TemplateHaskell #-}- module Chiasma.Ui.Data.Tree where -import Data.Text.Prettyprint.Doc (Pretty(..), vsep, nest)+import Control.Lens (makeClassy)+import Prettyprinter (Pretty (..), nest, vsep) data Tree f l p = Tree {@@ -10,11 +9,11 @@ _forest :: f (Node f l p) } -deriving instance (Eq l, Eq p) => Eq (Tree [] l p)-deriving instance (Show l, Show p) => Show (Tree [] l p)+deriving stock instance (Eq l, Eq (Node [] l p)) => Eq (Tree [] l p)+deriving stock instance (Show l, Show (Node [] l p)) => Show (Tree [] l p) -deriving instance (Eq l, Eq p) => Eq (Tree NonEmpty l p)-deriving instance (Show l, Show p) => Show (Tree NonEmpty l p)+deriving stock instance (Eq l, Eq (Node NonEmpty l p)) => Eq (Tree NonEmpty l p)+deriving stock instance (Show l, Show (Node NonEmpty l p)) => Show (Tree NonEmpty l p) data Node f l p = Sub { _subTree :: Tree f l p }@@ -24,11 +23,11 @@ makeClassy ''Tree makeClassy ''Node -deriving instance (Eq l, Eq p) => Eq (Node [] l p)-deriving instance (Show l, Show p) => Show (Node [] l p)+deriving stock instance (Eq l, Eq p) => Eq (Node [] l p)+deriving stock instance (Show l, Show p) => Show (Node [] l p) -deriving instance (Eq l, Eq p) => Eq (Node NonEmpty l p)-deriving instance (Show l, Show p) => Show (Node NonEmpty l p)+deriving stock instance (Eq l, Eq p) => Eq (Node NonEmpty l p)+deriving stock instance (Show l, Show p) => Show (Node NonEmpty l p) type LTree l p = Tree [] l p type LNode l p = Node [] l p
@@ -1,6 +1,7 @@ module Chiasma.Ui.Data.TreeModError where import Chiasma.Data.Ident (Ident)+ import Chiasma.Ui.Data.View (LayoutView, PaneView) data TreeModError =@@ -17,6 +18,4 @@ AmbiguousLayout Ident Int | NoTrees- deriving (Eq, Show)--deepPrisms ''TreeModError+ deriving stock (Eq, Show)
@@ -1,23 +1,24 @@ module Chiasma.Ui.Data.View where -import Chiasma.Data.Ident (Ident, Identifiable(..))-import Control.Lens (Index, IxValue, Ixed(ix), makeClassy_)+import Control.Lens (Index, IxValue, Ixed (ix), makeClassy, makeClassy_) import Control.Lens.Plated (Plated)+import Data.Bifoldable (Bifoldable (bifoldMap)) import Data.Data (Data)-import Data.Text.Prettyprint.Doc (Doc, Pretty(..), emptyDoc, nest, space, vsep, (<+>))-import Prelude hiding (state)+import Prettyprinter (Doc, Pretty (..), emptyDoc, nest, space, vsep, (<+>)) +import Chiasma.Data.Axis (Axis (Horizontal, Vertical))+import Chiasma.Data.Ident (Ident, Identifiable (..)) import Chiasma.Ui.Data.ViewGeometry (ViewGeometry)-import Chiasma.Ui.Data.ViewState (ViewState(ViewState))+import Chiasma.Ui.Data.ViewState (ViewState (ViewState)) import Chiasma.Ui.Lens.Ident (matchIdentP) data Pane = Pane { _open :: Bool, _pin :: Bool,- _cwd :: Maybe FilePath+ _cwd :: Maybe Text }- deriving (Eq, Show, Data, Generic)+ deriving stock (Eq, Show, Data, Generic) makeClassy ''Pane @@ -26,15 +27,13 @@ newtype Layout = Layout {- vertical :: Bool+ axis :: Axis }- deriving (Eq, Show, Data, Generic)+ deriving stock (Eq, Show, Data, Generic)+ deriving newtype (Default) makeClassy_ ''Layout -instance Default Layout where- def = Layout True- data View a = View { _ident :: Ident,@@ -42,7 +41,7 @@ _geometry :: ViewGeometry, _extra :: a }- deriving (Eq, Show, Data, Generic)+ deriving stock (Eq, Show, Data, Generic) makeClassy ''View @@ -53,8 +52,11 @@ type LayoutView = View Layout instance Pretty Layout where- pretty (Layout vertical) =- if vertical then "▤" else "▥"+ pretty = \case+ Layout Vertical ->+ "➡"+ Layout Horizontal ->+ "⬇" instance Pretty Pane where pretty (Pane open' pin' _) =@@ -75,16 +77,16 @@ consPane :: Ident -> PaneView consPane ident' = View ident' (ViewState False) def (Pane False False Nothing) -consLayoutAs :: Bool -> Ident -> LayoutView-consLayoutAs vert ident' = View ident' (ViewState False) def (Layout vert)+consLayoutAs :: Axis -> Ident -> LayoutView+consLayoutAs axis ident' = View ident' (ViewState False) def (Layout axis) consLayout :: Ident -> LayoutView consLayout =- consLayoutAs False+ consLayoutAs Horizontal consLayoutVertical :: Ident -> LayoutView consLayoutVertical =- consLayoutAs True+ consLayoutAs Vertical instance Identifiable (View a) where identify = _ident@@ -95,7 +97,7 @@ treeData :: l, treeSubs :: [TreeSub l p] }- deriving (Eq, Show, Data, Generic)+ deriving stock (Eq, Show, Data, Generic) instance Bifunctor Tree where first f (Tree l sub) = Tree (f l) (fmap (first f) sub)@@ -110,7 +112,7 @@ TreeNode { _subTree :: Tree l p } | TreeLeaf { _leafData :: p }- deriving (Eq, Show, Data, Generic)+ deriving stock (Eq, Show, Data, Generic) instance Bifunctor TreeSub where first f (TreeNode t) = TreeNode (first f t)@@ -138,7 +140,8 @@ type instance IxValue (Tree l p) = Tree l p instance Identifiable l => Ixed (Tree l p) where- ix = matchIdentP+ ix i =+ matchIdentP i instance (Pretty l, Pretty p) => Pretty (TreeSub l p) where pretty (TreeNode a) =
@@ -1,9 +1,7 @@-{-# LANGUAGE DeriveAnyClass #-}- module Chiasma.Ui.Data.ViewGeometry where import Data.Data (Data)-import Data.Text.Prettyprint.Doc (Doc, Pretty(..), emptyDoc, space, (<+>))+import Prettyprinter (Doc, Pretty (..), emptyDoc, space, (<+>)) data ViewGeometry = ViewGeometry {@@ -14,7 +12,8 @@ weight :: Maybe Float, position :: Maybe Float }- deriving (Eq, Show, Data, Generic, Default)+ deriving stock (Eq, Show, Data, Generic)+ deriving anyclass (Default) mayPretty :: Text -> Maybe Float -> Doc a mayPretty prefix (Just a) =
@@ -6,7 +6,7 @@ ViewState { minimized :: Bool }- deriving (Eq, Show, Data, Generic)+ deriving stock (Eq, Show, Data, Generic) instance Default ViewState where def = ViewState False
@@ -1,23 +1,21 @@-{-# LANGUAGE RankNTypes #-}+module Chiasma.Ui.Lens.Ident where -module Chiasma.Ui.Lens.Ident(- matchIdent,- matchIdentL,- matchIdentP,-) where+import Control.Lens (Prism', prism) -import Control.Lens (Traversal', Prism', filtered, each, prism)-import Chiasma.Data.Ident (Ident, Identifiable(..), sameIdent)+import Chiasma.Data.Ident (Ident, Identifiable (..), sameIdent) matchIdent :: Identifiable a => Ident -> Traversal' a a-matchIdent = filtered . sameIdent+matchIdent i =+ filtered (sameIdent i) matchIdentL :: Identifiable a => Ident -> Traversal' [a] a-matchIdentL ident = each . matchIdent ident+matchIdentL ident =+ each . matchIdent ident identEither :: Identifiable a => Ident -> a -> Either a a identEither target a = if sameIdent target a then Right a else Left a matchIdentP :: Identifiable a => Ident -> Prism' a a-matchIdentP ident = prism id (identEither ident)+matchIdentP ident =+ prism id (identEither ident)
@@ -1,15 +1,15 @@ module Chiasma.Ui.Measure where import qualified Data.List.NonEmpty as NonEmpty (zip)-import GHC.Float (int2Float) -import Chiasma.Data.Maybe (orElse)-import Chiasma.Ui.Data.Measure (MLayout(..), MPane(..), MeasureTree, MeasureTreeSub, Measured(..))-import Chiasma.Ui.Data.RenderableTree (RLayout(..), RPane(..), Renderable(..), RenderableNode, RenderableTree)-import Chiasma.Ui.Data.Tree (Tree(..))-import qualified Chiasma.Ui.Data.Tree as Tree (Node(..))-import Chiasma.Ui.Data.ViewGeometry (ViewGeometry(minSize, maxSize, fixedSize))-import Chiasma.Ui.Data.ViewState (ViewState(ViewState))+import qualified Chiasma.Data.Axis as Axis+import Chiasma.Data.Axis (Axis (Horizontal, Vertical))+import Chiasma.Ui.Data.Measure (MLayout (..), MPane (..), MeasureTree, MeasureTreeSub, Measured (..))+import Chiasma.Ui.Data.RenderableTree (RLayout (..), RPane (..), Renderable (..), RenderableNode, RenderableTree)+import Chiasma.Ui.Data.Tree (Tree (..))+import qualified Chiasma.Ui.Data.Tree as Tree (Node (..))+import Chiasma.Ui.Data.ViewGeometry (ViewGeometry (fixedSize, maxSize, minSize))+import Chiasma.Ui.Data.ViewState (ViewState (ViewState)) import Chiasma.Ui.Measure.Balance (balanceSizes) import Chiasma.Ui.Measure.Weights (viewWeights) @@ -22,7 +22,7 @@ actualSize :: (ViewGeometry -> Maybe Float) -> ViewState -> ViewGeometry -> Maybe Float actualSize getter viewState viewGeom =- orElse (getter viewGeom) (effectiveFixedSize viewState viewGeom)+ getter viewGeom <|> effectiveFixedSize viewState viewGeom actualMinSizes :: NonEmpty (ViewState, ViewGeometry) -> NonEmpty Float actualMinSizes =@@ -44,7 +44,7 @@ balanceSizes minSizes maxSizes weights minimized cells where measureData = fmap subMeasureData views- paneSpacers = int2Float (length views) - 1.0+ paneSpacers = fromIntegral (length views) - 1.0 cells = total - paneSpacers sizesInCells s = if s > 1 then s else s * cells minSizes = fmap sizesInCells (actualMinSizes measureData)@@ -52,25 +52,28 @@ minimized = fmap (uncurry isMinimized) measureData weights = viewWeights measureData -measureSub :: Int -> Int -> Bool -> RenderableNode -> Int -> MeasureTreeSub-measureSub width height vertical (Tree.Sub tree) size =- Tree.Sub $ measureLayout tree newWidth newHeight vertical+measureSub :: Int -> Int -> Axis -> RenderableNode -> Int -> MeasureTreeSub+measureSub width height axis (Tree.Sub tree) size =+ Tree.Sub (measureLayout tree newWidth newHeight axis) where- (newWidth, newHeight) = if vertical then (width, size) else (size, height)-measureSub _ _ vertical (Tree.Leaf (Renderable _ _ (RPane paneId top left))) size =+ (newWidth, newHeight) =+ case axis of+ Vertical -> (width, size)+ Horizontal -> (size, height)+measureSub _ _ (Axis.vertical -> vertical) (Tree.Leaf (Renderable _ _ (RPane paneId top left))) size = Tree.Leaf (Measured size (MPane paneId (if vertical then top else left) (if vertical then left else top))) -measureLayout :: RenderableTree -> Int -> Int -> Bool -> MeasureTree-measureLayout (Tree (Renderable _ _ (RLayout (RPane refId refTop refLeft) vertical)) sub) width height parentVertical =- Tree (Measured sizeInParent (MLayout refId mainPos offPos vertical)) measuredSub+measureLayout :: RenderableTree -> Int -> Int -> Axis -> MeasureTree+measureLayout (Tree (Renderable _ _ (RLayout (RPane refId refTop refLeft) axis)) sub) width height (Axis.vertical -> parentVertical) =+ Tree (Measured sizeInParent (MLayout refId mainPos offPos axis)) measuredSub where sizeInParent = if parentVertical then height else width mainPos = if parentVertical then refTop else refLeft offPos = if parentVertical then refLeft else refTop- subTotalSize = if vertical then height else width- sizes = measureLayoutViews (int2Float subTotalSize) sub- measuredSub = uncurry (measureSub width height vertical) <$> NonEmpty.zip sub sizes+ subTotalSize = if Axis.vertical axis then height else width+ sizes = measureLayoutViews (fromIntegral subTotalSize) sub+ measuredSub = uncurry (measureSub width height axis) <$> NonEmpty.zip sub sizes measureTree :: RenderableTree -> Int -> Int -> MeasureTree measureTree tree width height =- measureLayout tree width height False+ measureLayout tree width height Horizontal
@@ -1,14 +1,7 @@ module Chiasma.Ui.Measure.Balance where import Data.List (zipWith3)-import qualified Data.List.NonEmpty as NonEmpty (- filter,- toList,- zip,- zipWith,- )-import GHC.Float (float2Int, int2Float)-import GHC.Float.RealFracMethods (floorFloatInt)+import qualified Data.List.NonEmpty as NonEmpty import Chiasma.Ui.Measure.Weights ( amendAndNormalizeWeights,@@ -30,24 +23,37 @@ reverseWeights :: NonEmpty Float -> NonEmpty Float reverseWeights weights =- if norm > 0 then fmap (/ norm) r else r+ rev <$> r where- r = fmap (1 -) weights- norm = sum r+ rev a =+ fromMaybe a (a / norm)+ r =+ fmap (1 -) weights+ norm =+ sum r cutSizes :: Balance -> NonEmpty Float-cutSizes (Balance min' _ weights _ total) =- fmap addDist cut+cutSizes (Balance minSizes _ weights _ total) =+ addNegatives <$> truncatedWeighted where- surplus = sum min' - total- dist = fmap (surplus *) (reverseWeights weights)- cut = fmap (uncurry (-)) (NonEmpty.zip min' dist)- negOrZero a = if a < 0 then a else 0- neg = fmap negOrZero cut- negTotal = sum neg- negCount = length (NonEmpty.filter (< 0) neg)- dist2 = negTotal / int2Float (length min' - negCount)- addDist a = if a < 0 then 0 else a + dist2+ surplus =+ sum minSizes - total+ surplusDistWeighted =+ fmap (surplus *) (reverseWeights weights)+ truncatedWeighted =+ fmap (uncurry (-)) (NonEmpty.zip minSizes surplusDistWeighted)+ negOrZero a =+ if a < 0 then a else 0+ neg =+ fmap negOrZero truncatedWeighted+ negTotal =+ sum neg+ negCount =+ length (NonEmpty.filter (< 0) neg)+ negativesDist =+ fromMaybe 0 (negTotal / realToFrac (length minSizes - negCount))+ addNegatives a =+ if a < 0 then 0 else a + negativesDist distributeOnUnbounded :: Balance -> NonEmpty Float distributeOnUnbounded (Balance min' max' weights _ total) =@@ -121,28 +127,37 @@ maxTotal = sum (catMaybes $ NonEmpty.toList $ balanceMax balance) roundSizes :: NonEmpty Float -> NonEmpty Int-roundSizes (head' :| tail') =- roundedHead + (float2Int surplus) :| roundedTail+roundSizes (h :| t) =+ roundedHead + round surplus :| roundedTail where- (surplus, roundedTail) = mapAccumL folder diff0 tail'- (roundedHead, diff0) = diff head'+ (surplus, roundedTail) = mapAccumL folder diff0 t+ (roundedHead, diff0) = diff h folder z a = (z + z1, a1) where (a1, z1) = diff a- diff a = (floorFloatInt a, a - int2Float (floor a))+ diff a = (floor a, a - fromIntegral (floor a :: Int)) +-- |Tmux doesn't render panes smaller than two cells. ensureMinimum2 :: NonEmpty Float -> NonEmpty Float ensureMinimum2 sizes = choose <$> positives where- positive = max 0- positives = positive <$> sizes- positivesCount = length $ NonEmpty.filter (>= 2) sizes- unders = amountUnderTwo <$> positives- sub = sum unders / int2Float positivesCount- amountUnderTwo a = positive (2 - a)- choose a = max 2 (a - sub)+ positive =+ max 0+ positives =+ positive <$> sizes+ overTwoCount =+ length (NonEmpty.filter (>= 2) sizes)+ unders =+ amountUnderTwo <$> positives+ -- If no sizes are larger than 2, nothing will have to be subtracted, all sizes will be clamped to 2+ amountUnderTwoDist =+ fromMaybe 0 (sum unders / realToFrac overTwoCount)+ amountUnderTwo a =+ positive (2 - a)+ choose a =+ max 2 (a - amountUnderTwoDist) rectifySizes :: NonEmpty Float -> NonEmpty Int rectifySizes =
@@ -1,14 +1,9 @@-module Chiasma.Ui.Measure.Weights(- viewWeights,- normalizeWeights,- amendAndNormalizeWeights,-) where+module Chiasma.Ui.Measure.Weights where -import qualified Data.List.NonEmpty as NonEmpty (toList, filter)-import GHC.Float (int2Float)+import qualified Data.List.NonEmpty as NonEmpty (filter, toList) -import Chiasma.Ui.Data.ViewGeometry (ViewGeometry(ViewGeometry))-import Chiasma.Ui.Data.ViewState (ViewState(ViewState))+import Chiasma.Ui.Data.ViewGeometry (ViewGeometry (ViewGeometry))+import Chiasma.Ui.Data.ViewState (ViewState (ViewState)) effectiveWeight :: ViewState -> ViewGeometry -> Maybe Float effectiveWeight (ViewState minimized) (ViewGeometry _ _ fixedSize _ weight _) =@@ -18,18 +13,21 @@ amendWeights weights = fmap (fromMaybe emptyWeight) weights where- total = sum (catMaybes $ NonEmpty.toList weights)- normTotal = if total == 0 then 1 else total- empties = length (NonEmpty.filter isNothing weights)- normEmpties = if empties == 0 then 1 else empties- emptyWeight = normTotal / int2Float normEmpties+ total =+ sum (catMaybes (NonEmpty.toList weights))+ normTotal =+ if total == 0 then 1 else total+ empties =+ length (NonEmpty.filter isNothing weights)+ emptyWeight =+ fromMaybe normTotal (normTotal / realToFrac empties) normalizeWeights :: NonEmpty Float -> NonEmpty Float normalizeWeights weights =- fmap (/ normTotal) weights+ fmap norm weights where- total = sum weights- normTotal = if total == 0 then 1 else total+ norm a =+ fromMaybe a (a / sum weights) amendAndNormalizeWeights :: NonEmpty (Maybe Float) -> NonEmpty Float amendAndNormalizeWeights = normalizeWeights . amendWeights
@@ -3,11 +3,11 @@ import Chiasma.Data.Ident (identText) import Chiasma.Ui.Data.View ( LayoutView,- Pane(Pane),+ Pane (Pane), PaneView,- Tree(Tree),- TreeSub(TreeNode, TreeLeaf),- View(View),+ Tree (Tree),+ TreeSub (TreeLeaf, TreeNode),+ View (View), ViewTree, ) @@ -38,4 +38,8 @@ showViewTree = showTree . formatViewTree printViewTree :: MonadIO m => ViewTree -> m ()-printViewTree = liftIO . traverse_ putStrLn . fmap toString . showViewTree+printViewTree =+ liftIO .+ traverse_ putStrLn .+ fmap toString .+ showViewTree
@@ -1,41 +1,34 @@ module Chiasma.Ui.ViewTree where +import Control.Lens (anyOf, cosmos, ix, mapMOf, transformM)+import Control.Monad.Trans.Class (lift)+import Control.Monad.Trans.Except (ExceptT (ExceptT), runExceptT)+import Control.Monad.Trans.Writer (WriterT, runWriterT, tell)+import Data.Composition ((.:))+import Data.Semigroup (Sum (Sum))+import Exon (exon)+import Prelude hiding (ix, tell)+ import Chiasma.Data.Ident (Ident) import Chiasma.Lens.Tree (- LeafIndexTree(..),+ LeafIndexTree (..), _litTree, leafDataTraversal, )-import Control.Lens (- Traversal,- Traversal',- anyOf,- cosmos,- filtered,- has,- ix,- mapMOf,- over,- transformM,- )-import Control.Monad.Error.Class (throwError)-import Control.Monad.Trans.Writer (WriterT, runWriterT, tell)-import Data.Composition ((.:))--import Chiasma.Ui.Data.TreeModError (TreeModError(PaneMissing, AmbiguousPane, LayoutMissing, AmbiguousLayout))+import Chiasma.Ui.Data.TreeModError (TreeModError (AmbiguousLayout, AmbiguousPane, LayoutMissing, PaneMissing)) import Chiasma.Ui.Data.View (- Pane(Pane),+ Pane (Pane), PaneView,- Tree(Tree),- TreeSub(TreeNode, TreeLeaf),- View(View),+ Tree (Tree),+ TreeSub (TreeLeaf, TreeNode),+ View (View), ViewTree, ViewTreeSub, ) import qualified Chiasma.Ui.Data.View as Pane (open) import qualified Chiasma.Ui.Data.View as TreeSub (leafData) import qualified Chiasma.Ui.Data.View as View (extra)-import Chiasma.Ui.Data.ViewState (ViewState(ViewState))+import Chiasma.Ui.Data.ViewState (ViewState (ViewState)) import Chiasma.Ui.Pane (paneSetOpen, paneToggleOpen) modCounted :: Monad m => (a -> m a) -> a -> WriterT (Sum Int) m a@@ -55,9 +48,9 @@ let st = (transformM $ mapMOf (ix ident) (modCounted f)) tree (result, Sum count) <- lift $ runWriterT st case count of- 1 -> return result- 0 -> throwError $ LayoutMissing ident- n -> throwError $ AmbiguousLayout ident n+ 1 -> pure result+ 0 -> ExceptT (pure (Left (LayoutMissing ident)))+ n -> ExceptT (pure (Left (AmbiguousLayout ident n))) toggleLayout1 :: Ident -> ViewTree -> Either TreeModError ViewTree toggleLayout1 ident tree =@@ -68,9 +61,9 @@ let st = (transformM $ mapMOf (ix ident) (modCounted f)) (LeafIndexTree tree) (result, Sum count) <- lift $ runWriterT st case count of- 1 -> return $ litTree result- 0 -> throwError $ PaneMissing ident- n -> throwError $ AmbiguousPane ident n+ 1 -> pure $ litTree result+ 0 -> ExceptT (pure (Left (PaneMissing ident)))+ n -> ExceptT (pure (Left (AmbiguousPane ident n))) modifyPane :: (PaneView -> PaneView) -> Ident -> ViewTree -> Either TreeModError ViewTree modifyPane modification ident tree =@@ -115,7 +108,7 @@ Multiple Int | Consistent- deriving (Eq, Show)+ deriving stock (Eq, Show) instance Semigroup ToggleStatus where Pristine <> a = a@@ -134,7 +127,7 @@ NotFound | Ambiguous Int- deriving (Eq, Show, Functor)+ deriving stock (Eq, Show, Functor) instance Semigroup (ToggleResult a) where NotFound <> a = a@@ -157,6 +150,12 @@ Success a >>= f = f a NotFound >>= _ = NotFound Ambiguous n >>= _ = Ambiguous n++toggleResultEither :: ToggleResult a -> Either Text a+toggleResultEither = \case+ Success a -> Right a+ NotFound -> Left "not found"+ Ambiguous n -> Left [exon|ambiguous: #{show n}|] openPinnedSubs :: ToggleStatus -> ViewTree -> (ToggleStatus, ViewTree) openPinnedSubs Pristine t =
@@ -1,17 +1,16 @@-{-# LANGUAGE RankNTypes #-}- module Chiasma.View where +import qualified Control.Lens as Lens+import Exon (exon)+import Prettyprinter (Doc, pretty)+import Prettyprinter.Render.Terminal (AnsiStyle)+ import Chiasma.Data.Ident (Ident, identText, sameIdent) import Chiasma.Data.TmuxId (PaneId, SessionId, WindowId)-import Chiasma.Data.View (View(View), viewIdent)-import Chiasma.Data.Views (Views, ViewsError(..))+import Chiasma.Data.View (View (View), viewIdent)+import Chiasma.Data.Views (Views, ViewsError (..)) import qualified Chiasma.Data.Views as Views (log, panes, sessions, windows) import Chiasma.Lens.Where (where1)-import Control.Lens (Lens', over)-import qualified Control.Lens as Lens (over, set, view)-import Data.Text.Prettyprint.Doc (Doc, pretty)-import Data.Text.Prettyprint.Doc.Render.Terminal (AnsiStyle) sameId :: Eq a => a -> View a -> Bool sameId id' (View _ (Just vid)) = id' == vid@@ -26,11 +25,11 @@ find (sameId id') . Lens.view viewsL insertView :: Lens' Views [View a] -> View a -> Views -> Views-insertView viewsL newView = Lens.over viewsL (newView :)+insertView viewsL newView = over viewsL (newView :) updateView :: Lens' Views [View a] -> (Ident -> ViewsError) -> View a -> Views -> Views updateView viewsL _ newView =- Lens.set (viewsL . where1 (sameIdent (viewIdent newView))) newView+ set (viewsL . where1 (sameIdent (viewIdent newView))) newView session :: Ident -> Views -> Either ViewsError (View SessionId) session = view Views.sessions NoSuchSession@@ -78,26 +77,42 @@ type Getter a = Ident -> Views -> Either ViewsError (View a) type Setter a = View a -> Views -> Views -addView :: MonadDeepState s Views m => Setter a -> Ident -> m (View a)-addView setter ident = do- modify $ setter newView- viewsLogS $ "added tmux view " <> identText ident- return newView- where- newView = View ident Nothing--findOrCreateView :: (MonadDeepState s Views m) => Getter a -> Setter a -> Ident -> m (View a)-findOrCreateView getter setter ident = do- existing <- gets $ getter ident- either (const $ addView setter ident) return existing--viewsLog :: MonadDeepState s Views m => Doc AnsiStyle -> m ()+viewsLog ::+ Member (AtomicState Views) r =>+ Doc AnsiStyle ->+ Sem r () viewsLog message =- modify f+ atomicModify' f where f :: Views -> Views f = over Views.log (message :) -viewsLogS :: MonadDeepState s Views m => Text -> m ()+viewsLogS ::+ Member (AtomicState Views) r =>+ Text ->+ Sem r () viewsLogS = viewsLog . pretty++addView ::+ Member (AtomicState Views) r =>+ Setter a ->+ Ident ->+ Sem r (View a)+addView setter ident = do+ atomicModify' (setter newView)+ viewsLogS [exon|added tmux view #{identText ident}|]+ pure newView+ where+ newView =+ View ident Nothing++findOrCreateView ::+ Member (AtomicState Views) r =>+ Getter a ->+ Setter a ->+ Ident ->+ Sem r (View a)+findOrCreateView getter setter ident = do+ existing <- atomicGets (getter ident)+ either (const (addView setter ident)) pure existing
@@ -2,16 +2,12 @@ import Chiasma.Data.Ident (Ident) import Chiasma.Data.TmuxId (PaneId)-import Control.Monad.DeepError (MonadDeepError, hoistEither)-import Control.Monad.DeepState (MonadDeepState, gets)- import Chiasma.Data.Views (Views, ViewsError) import qualified Chiasma.View as Views (paneId) paneId ::- (MonadDeepState s Views m, MonadDeepError e ViewsError m) =>+ Members [AtomicState Views, Stop ViewsError] r => Ident ->- m PaneId-paneId paneIdent = do- pid <- gets $ Views.paneId paneIdent- hoistEither pid+ Sem r PaneId+paneId paneIdent =+ stopEither =<< atomicGets (Views.paneId paneIdent)
@@ -1,64 +1,66 @@ module Chiasma.Window where -import qualified Chiasma.Codec.Data as Codec (Pane(Pane, paneId), Window(Window, windowId))-import qualified Chiasma.Codec.Data.PaneDetail as Codec (PaneDetail(PaneDetail))-import qualified Chiasma.Codec.Data.PaneDetail as PaneDetail (PaneDetail(..))-import qualified Chiasma.Command.Pane as Cmd (closePane, firstWindowPane, windowPanesAs)-import qualified Chiasma.Command.Window as Cmd (newWindow, splitWindowAs, window)+import qualified Data.List.NonEmpty as NonEmpty (head, nonEmpty)+import Path (Abs, Dir, Path, parseAbsDir)+import Prettyprinter (line, pretty, vsep, (<+>))++import qualified Chiasma.Codec.Data.Pane as Pane+import qualified Chiasma.Codec.Data.Pane as Codec (Pane (Pane, paneId))+import Chiasma.Codec.Data.Pane (Pane (Pane))+import qualified Chiasma.Codec.Data.Window as Codec (Window (Window, windowId))+import qualified Chiasma.Command.Pane as Cmd (closePane, firstWindowPane, windowPanes)+import qualified Chiasma.Command.Window as Cmd (newWindow, splitWindowInDir, window)+import Chiasma.Data.Axis (Axis) import Chiasma.Data.Ident (Ident, identText, identify)-import Chiasma.Data.Maybe (findMaybe, maybeExcept, orElse)+import Chiasma.Data.Panes (TmuxPanes) import Chiasma.Data.RenderError (RenderError)-import qualified Chiasma.Data.RenderError as RenderError (RenderError(NoPrincipal))+import qualified Chiasma.Data.RenderError as RenderError (RenderError (NoPrincipal)) import Chiasma.Data.TmuxId (PaneId, SessionId, WindowId)-import Chiasma.Data.TmuxThunk (TmuxThunk)-import qualified Chiasma.Data.View as Tmux (View(View))+import qualified Chiasma.Data.View as Tmux (View (View)) import Chiasma.Data.Views (Views)-import Chiasma.Data.WindowState (WindowState(..))+import Chiasma.Data.WindowState (WindowState (..))+import Chiasma.Effect.TmuxApi (Tmux) import Chiasma.Pane (addPane) import Chiasma.Ui.Data.RenderableTree (- RLayout(..),- RPane(..),- Renderable(..),+ RLayout (..),+ RPane (..),+ Renderable (..), RenderableNode, RenderableTree, )-import qualified Chiasma.Ui.Data.Tree as Tree (Node(Sub, Leaf), Tree(Tree))-import Chiasma.Ui.Data.View (Tree(..), TreeSub(..), ViewTree, ViewTreeSub)-import qualified Chiasma.Ui.Data.View as Ui (Layout(..), Pane(Pane), PaneView, View(View))-import Chiasma.Ui.Data.ViewGeometry (ViewGeometry(ViewGeometry, position))+import qualified Chiasma.Ui.Data.Tree as Tree (Node (Leaf, Sub), Tree (Tree))+import Chiasma.Ui.Data.View (Tree (..), TreeSub (..), ViewTree, ViewTreeSub)+import qualified Chiasma.Ui.Data.View as Ui (Layout (..), Pane (Pane), PaneView, View (View))+import Chiasma.Ui.Data.ViewGeometry (ViewGeometry (ViewGeometry, position)) import Chiasma.Ui.Data.ViewState (ViewState) import Chiasma.View (findOrCreateView, viewsLog, viewsLogS) import qualified Chiasma.View as Views (insertPane, insertWindow, pane, paneById, updatePane, updateWindow, window)-import Control.Monad.Error.Class (MonadError)-import Control.Monad.Free.Class (MonadFree)-import qualified Data.List.NonEmpty as NonEmpty (head, nonEmpty)-import Data.Text.Prettyprint.Doc (line, pretty, vsep, (<+>)) findOrCreateWindow ::- (MonadDeepState s Views m) =>+ Member (AtomicState Views) r => Ident ->- m (Tmux.View WindowId)+ Sem r (Tmux.View WindowId) findOrCreateWindow = findOrCreateView Views.window Views.insertWindow registerWindowId ::- (MonadDeepState s Views m) =>+ Member (AtomicState Views) r => Ident -> WindowId ->- m ()+ Sem r () registerWindowId ident windowId =- modify $ Views.updateWindow $ Tmux.View ident (Just windowId)+ atomicModify' (Views.updateWindow (Tmux.View ident (Just windowId))) spawnWindow ::- (MonadDeepState s Views m, MonadFree TmuxThunk m) =>+ Members [AtomicState Views, Tmux] r => SessionId -> Ident ->- m Codec.Window+ Sem r Codec.Window spawnWindow sid ident = do win@(Codec.Window windowId _ _) <- Cmd.newWindow sid ident registerWindowId ident windowId viewsLogS $ "spawned window in session " <> show sid <> " with id " <> show windowId- return win+ pure win findPrincipalSub :: ViewTreeSub -> Maybe Ui.PaneView findPrincipalSub (TreeNode t) = findPrincipal t@@ -67,109 +69,112 @@ findPrincipal :: ViewTree -> Maybe Ui.PaneView findPrincipal (Tree _ sub) =- findMaybe findPrincipalSub sub+ firstJust findPrincipalSub sub principalPane ::- (MonadDeepState s Views m, MonadError RenderError m) =>+ Members [AtomicState Views, Tmux, Stop RenderError] r => ViewTree ->- m (Ui.PaneView, Tmux.View PaneId)+ Sem r (Ui.PaneView, Tmux.View PaneId) principalPane tree = do- uiPane@(Ui.View uiPaneIdent _ _ _) <- maybeExcept (RenderError.NoPrincipal $ identify tree) $ findPrincipal tree- existingTmuxPane <- gets $ Views.pane uiPaneIdent- tmuxPane <- either (const $ addPane uiPaneIdent) return existingTmuxPane- return (uiPane, tmuxPane)+ uiPane@(Ui.View uiPaneIdent _ _ _) <- stopNote (RenderError.NoPrincipal (identify tree)) $ (findPrincipal tree)+ existingTmuxPane <- atomicGets (Views.pane uiPaneIdent)+ tmuxPane <- either (const $ addPane uiPaneIdent) pure existingTmuxPane+ pure (uiPane, tmuxPane) syncPrincipal ::- (MonadDeepState s Views m, MonadFree TmuxThunk m, MonadError RenderError m) =>+ Members [TmuxPanes Pane, AtomicState Views, Tmux, Stop RenderError] r => WindowId -> ViewTree ->- m ()+ Sem r () syncPrincipal windowId tree@(Tree (Ui.View layoutIdent _ _ _) _) = do- (Codec.Pane paneId _ _) <- Cmd.firstWindowPane windowId- existing <- gets (Views.paneById paneId)+ (Codec.Pane paneId _ _ _ _) <- Cmd.firstWindowPane windowId+ existing <- atomicGets (Views.paneById paneId) case existing of Nothing -> do (_, Tmux.View paneIdent _) <- principalPane tree viewsLog $ "setting principal of layout" <+> pretty layoutIdent <+> " to pane " <+> pretty paneIdent <+> "/" <+> pretty paneId- modify $ Views.updatePane (Tmux.View paneIdent (Just paneId))- _ -> return ()+ atomicModify' $ Views.updatePane (Tmux.View paneIdent (Just paneId))+ _ -> pure () ensureWindow ::- (MonadDeepState s Views m, MonadFree TmuxThunk m, MonadError RenderError m) =>+ Members [TmuxPanes Pane, AtomicState Views, Tmux, Stop RenderError] r => SessionId -> Tmux.View WindowId -> Maybe WindowId -> ViewTree ->- m Codec.Window+ Sem r Codec.Window ensureWindow sid (Tmux.View ident mayWid) newSessionWid tree = do- preexisting <- join <$> traverse Cmd.window (orElse newSessionWid mayWid)- window <- maybe (spawnWindow sid ident) return preexisting+ preexisting <- join <$> traverse Cmd.window (newSessionWid <|> mayWid)+ window <- maybe (spawnWindow sid ident) pure preexisting syncPrincipal (Codec.windowId window) tree- return window+ pure window -findOrCreatePane :: MonadDeepState s Views m => Ident -> m (Tmux.View PaneId)+findOrCreatePane ::+ Member (AtomicState Views) r =>+ Ident ->+ Sem r (Tmux.View PaneId) findOrCreatePane = findOrCreateView Views.pane Views.insertPane nativePane ::- MonadFree TmuxThunk m =>+ Member (TmuxPanes Pane) r => WindowId -> Tmux.View PaneId ->- m (Maybe Codec.PaneDetail)+ Sem r (Maybe Pane) nativePane windowId (Tmux.View _ (Just paneId)) =- find sameId <$> Cmd.windowPanesAs windowId+ find sameId <$> Cmd.windowPanes windowId where- sameId (Codec.PaneDetail i _ _ _ _) = i == paneId-nativePane _ _ = return Nothing+ sameId (Pane i _ _ _ _) = i == paneId+nativePane _ _ = pure Nothing openPane ::- (MonadDeepState s Views m, MonadFree TmuxThunk m) =>- FilePath ->+ Members [AtomicState Views, Tmux] r =>+ Path Abs Dir -> WindowId ->- m Codec.PaneDetail+ Sem r Pane openPane dir windowId = do- detail <- Cmd.splitWindowAs dir windowId- viewsLogS $ "opened pane " <> show (PaneDetail.paneId detail) <> " in window " <> show windowId- return detail+ detail <- Cmd.splitWindowInDir dir windowId+ viewsLogS $ "opened pane " <> show (Pane.paneId detail) <> " in window " <> show windowId+ pure detail ensurePaneOpen ::- (MonadDeepState s Views m, MonadFree TmuxThunk m) =>- FilePath ->- Maybe Codec.PaneDetail ->+ Members [AtomicState Views, Tmux] r =>+ Path Abs Dir ->+ Maybe Pane -> WindowId ->- m Codec.PaneDetail+ Sem r Pane ensurePaneOpen _ (Just detail) _ =- return detail+ pure detail ensurePaneOpen dir Nothing windowId = openPane dir windowId ensurePaneClosed ::- (MonadDeepState s Views m, MonadFree TmuxThunk m) =>- Maybe Codec.PaneDetail ->- m ()-ensurePaneClosed (Just (Codec.PaneDetail i _ _ _ _)) = do+ Members [AtomicState Views, Tmux] r =>+ Maybe Pane ->+ Sem r ()+ensurePaneClosed (Just (Pane i _ _ _ _)) = do viewsLogS $ "closing pane " <> show i Cmd.closePane i-ensurePaneClosed _ = return ()+ensurePaneClosed _ = pure () ensurePane ::- (MonadDeepState s Views m, MonadFree TmuxThunk m) =>- FilePath ->+ Members [TmuxPanes Pane, AtomicState Views, Tmux] r =>+ Path Abs Dir -> WindowId -> Ui.PaneView ->- m (Maybe RenderableNode)+ Sem r (Maybe RenderableNode) ensurePane cwd windowId (Ui.View paneIdent vState geometry (Ui.Pane open _ customDir)) = do tmuxPane <- findOrCreatePane paneIdent existingPane <- nativePane windowId tmuxPane updatedPane <- if open then Just <$> ensurePaneOpen dir existingPane windowId else Nothing <$ ensurePaneClosed existingPane- modify $ Views.updatePane (Tmux.View paneIdent (PaneDetail.paneId <$> updatedPane))- return $ cons <$> updatedPane+ atomicModify' $ Views.updatePane (Tmux.View paneIdent (Pane.paneId <$> updatedPane))+ pure $ cons <$> updatedPane where- dir = fromMaybe cwd customDir- cons (Codec.PaneDetail i _ _ top left) =+ dir = fromMaybe cwd (parseAbsDir . toString =<< customDir)+ cons (Pane i _ _ top left) = Tree.Leaf . Renderable vState geometry $ RPane i top left refPane :: RenderableNode -> RPane@@ -179,12 +184,12 @@ renderableTree :: ViewState -> ViewGeometry ->- Bool ->+ Axis -> [RenderableNode] -> Maybe RenderableTree-renderableTree vState geometry vertical sub = do+renderableTree vState geometry axis sub = do sub' <- NonEmpty.nonEmpty sub- return $ Tree.Tree (Renderable vState geometry (RLayout (refPane $ NonEmpty.head sub') vertical)) sub'+ pure $ Tree.Tree (Renderable vState geometry (RLayout (refPane $ NonEmpty.head sub') axis)) sub' viewPosition :: ViewTreeSub -> Float viewPosition (TreeNode (Tree (Ui.View _ _ ViewGeometry { position = pos } _) _)) =@@ -193,33 +198,33 @@ fromMaybe 0.5 pos ensureView ::- (MonadDeepState s Views m, MonadFree TmuxThunk m) =>- FilePath ->+ Members [TmuxPanes Pane, AtomicState Views, Tmux] r =>+ Path Abs Dir -> WindowId -> ViewTree ->- m (Maybe RenderableTree)+ Sem r (Maybe RenderableTree) ensureView cwd windowId = ensureTree where- ensureTree (Tree (Ui.View layoutIdent vState geometry (Ui.Layout vertical)) sub) = do+ ensureTree (Tree (Ui.View layoutIdent vState geometry (Ui.Layout axis)) sub) = do ensuredSub <- traverse ensureNode sortedSub viewsLog $ pretty ("new sub for layout `" <> identText layoutIdent <> "`:") <> line <> vsep (pretty <$> ensuredSub)- return $ renderableTree vState geometry vertical $ catMaybes ensuredSub+ pure $ renderableTree vState geometry axis $ catMaybes ensuredSub where sortedSub = sortOn viewPosition sub ensureNode (TreeNode t) = do newTree <- ensureTree t- return $ Tree.Sub <$> newTree+ pure $ Tree.Sub <$> newTree ensureNode (TreeLeaf v) = ensurePane cwd windowId v windowState ::- (MonadDeepState s Views m, MonadFree TmuxThunk m) =>+ Member (TmuxPanes Pane) r => Ident -> Codec.Window -> RenderableTree ->- m WindowState+ Sem r WindowState windowState windowIdent window tree = do nativeRef <- Cmd.firstWindowPane (Codec.windowId window)- return $ WindowState window nativeRef windowIdent tree (Codec.paneId nativeRef)+ pure $ WindowState window nativeRef windowIdent tree (Codec.paneId nativeRef)
@@ -1,5 +0,0 @@-module Prelude (- module Chiasma.Prelude,-) where--import Chiasma.Prelude
@@ -1,3 +0,0 @@-# Intro--**chiasma** is a tmux api client for Haskell.
@@ -1,24 +0,0 @@-module Chiasma.Test.CodecTest where--import Chiasma.Codec (TmuxCodec(decode, query), TmuxQuery(TmuxQuery))-import Chiasma.Data.TmuxId (PaneId(..), WindowId(..))-import Hedgehog ((===))--import Chiasma.Test.Util (UnitTest)--data Dat =- Dat {- paneId :: PaneId,- windowId :: WindowId- }- deriving (Eq, Show, Generic)--instance TmuxCodec Dat--test_decode :: UnitTest-test_decode =- Right (Dat (PaneId 1) (WindowId 2)) === decode " %1 @2 "--test_query :: UnitTest-test_query =- TmuxQuery "#{pane_id} #{window_id}" === query @Dat
@@ -1,102 +0,0 @@-module Chiasma.Test.LensTest where--import Chiasma.Data.Ident (Ident(Str))-import Chiasma.Lens.Tree (leafByIdent, modifyLeafByIdent, treesAndSubs)-import Chiasma.Ui.Data.View (- Pane(Pane),- PaneView,- Tree(Tree),- TreeSub(TreeNode, TreeLeaf),- View(View),- ViewTree,- ViewTreeSub,- consLayout,- consPane,- )-import qualified Chiasma.Ui.Data.View as View (_ident, ident)-import Chiasma.Ui.Data.ViewState (ViewState(ViewState))-import Chiasma.Ui.ViewTree (togglePane)-import qualified Chiasma.Ui.ViewTree as ToggleResult (ToggleResult(..))-import Control.Lens (transformM)-import qualified Control.Lens as Lens (set)-import Hedgehog ((===))-import Test.Tasty (TestTree, testGroup)-import Chiasma.Test.Util (UnitTest, unitTest)--id0, id1, id2, id3, id4 :: Ident-id0 = Str "0"-id1 = Str "1"-id2 = Str "2"-id3 = Str "3"-id4 = Str "4"--tree :: ViewTree-tree =- Tree (consLayout id0) [subtree, TreeLeaf (consPane id2)]- where- subtree = st id2 subtree2- subtree2 = st id3 subtree3- subtree3 = st id4 (TreeLeaf openPane)- openPane = View id1 (ViewState False) def (Pane True False Nothing)- st i s =- TreeNode $ Tree (consLayout i) [s]--test_modify :: UnitTest-test_modify = do- let- ident = Str "changed"- modded = modifyLeafByIdent id1 (Lens.set View.ident ident) tree- Nothing === leafByIdent ident tree- Just ident === (View._ident <$> leafByIdent ident modded)--failOnPaneIdent :: Ident -> ViewTree -> Maybe ViewTree-failOnPaneIdent target t@(Tree _ sub) =- t <$ traverse match sub- where- match (TreeLeaf (View i _ _ _)) = if target == i then Nothing else Just ()- match _ = Just ()--test_monadicModify :: UnitTest-test_monadicModify = do- Nothing === (transformM (failOnPaneIdent id2) tree)- Just tree === (transformM (failOnPaneIdent id4) tree)--insertPane :: Ident -> PaneView -> ViewTree -> ViewTree-insertPane targetLayout pane (Tree l sub) =- if View._ident l == targetLayout then Tree l (TreeLeaf pane : sub) else Tree l sub--ensurePaneUnique :: Ident -> ViewTreeSub -> Maybe ViewTreeSub-ensurePaneUnique paneIdent (TreeLeaf (View ident _ _ _)) | ident == paneIdent = Nothing-ensurePaneUnique _ n = Just n--subtreesTarget :: ViewTree-subtreesTarget =- Tree (consLayout id0) [subtree, TreeLeaf (consPane id2)]- where- subtree = TreeNode $ Tree (consLayout id2) [TreeLeaf $ consPane id4, subtree2]- subtree2 = TreeNode $ Tree (consLayout id3) [subtree3]- subtree3 = TreeNode $ Tree (consLayout id4) [TreeLeaf openPane]- openPane = View id1 (ViewState False) def (Pane True False Nothing)--test_subtrees :: UnitTest-test_subtrees =- Just subtreesTarget === treesAndSubs (Just . insertPane id2 (consPane id4)) (ensurePaneUnique id4) tree--togglePaneTree :: ViewTree-togglePaneTree =- Tree (consLayout id0) [TreeLeaf (consPane id0), TreeLeaf (consPane id0), TreeLeaf (consPane id2)]--test_togglePane :: UnitTest-test_togglePane = do- ToggleResult.Ambiguous 2 === togglePane id0 togglePaneTree- ToggleResult.NotFound === togglePane id1 togglePaneTree- ToggleResult.Success (1 :: Int) === (1 <$ togglePane id2 togglePaneTree)--test_lenses :: TestTree-test_lenses =- testGroup "lenses" [- unitTest "modify leaves by ident" test_modify,- unitTest "monadically transform leaves" test_monadicModify,- unitTest "traverse all subtrees" test_subtrees,- unitTest "toggle a pane" test_togglePane- ]
@@ -1,29 +0,0 @@-module Chiasma.Test.OutputParseTest where--import Chiasma.Native.Parse (resultLines)-import qualified Data.Text as T (unlines)-import Hedgehog ((===))--import Chiasma.Test.Util (UnitTest)--paneLine :: Text-paneLine = "%0 100 100"--tmuxOutput :: Text-tmuxOutput = T.unlines [- "%begin 123",- "%end 123",- "%session-changed $0 0",- "%begin 234",- paneLine,- "b",- "%end 234",- "%begin 345",- "c",- "d",- "%end 345"- ]--test_outputParse :: UnitTest-test_outputParse =- Right [[], [paneLine, "b"], ["c", "d"]] === resultLines tmuxOutput
@@ -1,98 +0,0 @@-module Chiasma.Test.PinTest where--import Chiasma.Data.Ident (Ident(Str))-import Chiasma.Ui.Data.View (- Pane(Pane),- Tree(Tree),- TreeSub(TreeNode, TreeLeaf),- View(View),- ViewTree,- ViewTreeSub,- consLayout,- )-import Chiasma.Ui.ViewTree (toggleLayout, togglePane)-import qualified Chiasma.Ui.ViewTree as ToggleResult (ToggleResult(..))-import Hedgehog ((===))-import Test.Tasty (TestTree, testGroup)--import Chiasma.Test.Util (UnitTest, unitTest)--node :: ViewTreeSub -> View Pane -> ViewTreeSub-node sub p =- TreeNode $ Tree (consLayout "l") [sub, TreeLeaf p]--paneWith :: Bool -> Text -> ViewTreeSub-paneWith open i = TreeLeaf $ View (Str i) def def (Pane open False Nothing)--pane :: Text -> ViewTreeSub-pane =- paneWith False--ppaneWithIdent :: Text -> Bool -> View Pane-ppaneWithIdent name open =- View (Str name) def def (Pane open True Nothing)--ppaneWith :: Bool -> View Pane-ppaneWith =- ppaneWithIdent "pin"--ppane :: View Pane-ppane =- ppaneWith False--subtree4 :: ViewTreeSub-subtree4 = node subtree5 ppane--subtree5 :: ViewTreeSub-subtree5 = node subtree6 ppane--subtree6 :: ViewTreeSub-subtree6 = TreeNode $ Tree (consLayout (Str "l")) [pane "p2"]--tree :: ViewTree-tree =- Tree (consLayout (Str "root")) [subtree4, subtree1, subtree4]- where- subtree1 = node subtree2 ppane- subtree2 = node subtree3 ppane- subtree3 = TreeNode $ Tree (consLayout (Str "l1")) [pane "p1"]--target :: ViewTree-target =- Tree (consLayout (Str "root")) [subtree4, subtree1, subtree4]- where- subtree1 = node subtree2 (ppaneWith True)- subtree2 = node subtree3 (ppaneWith True)- subtree3 = TreeNode $ Tree (consLayout (Str "l1")) [TreeLeaf $ View (Str "p1") def def (Pane True False Nothing)]--test_pinOpenNonpinned :: UnitTest-test_pinOpenNonpinned =- ToggleResult.Success target === togglePane (Str "p1") tree--test_layoutPinOpenNonpinned :: UnitTest-test_layoutPinOpenNonpinned =- ToggleResult.Success target === toggleLayout (Str "l1") tree--pinnedTree :: ViewTree-pinnedTree =- Tree (consLayout (Str "root")) [pane "left", subtree]- where- subtree = TreeNode $ Tree (consLayout (Str "l1")) [pane "p2", TreeLeaf $ ppaneWithIdent "p1" False]--pinnedTarget :: ViewTree-pinnedTarget =- Tree (consLayout (Str "root")) [pane "left", subtree]- where- subtree = TreeNode $ Tree (consLayout (Str "l1")) [paneWith False "p2", TreeLeaf $ ppaneWithIdent "p1" True]--test_pinOpenPinned :: UnitTest-test_pinOpenPinned =- ToggleResult.Success pinnedTarget === toggleLayout (Str "l1") pinnedTree--test_pin :: TestTree-test_pin =- testGroup "pin" [- unitTest "open an unpinned pane" test_pinOpenNonpinned,- unitTest "open an unpinned layout" test_layoutPinOpenNonpinned,- unitTest "open a pinned pane" test_pinOpenPinned- ]
@@ -1,30 +0,0 @@-module Chiasma.Test.StreamParseTest where--import Chiasma.Native.StreamParse (TmuxOutputBlock(Success), parseConduit)-import Conduit-import Data.ByteString.Internal (packChars)-import Data.Conduit.List-import qualified Data.Text as T (unpack)-import Hedgehog ((===))--import Chiasma.Test.Util (UnitTest)--paneLine :: Text-paneLine = "%0 100 100"--tmuxOutput :: [Text]-tmuxOutput = [- "%begin 123\n",- "%end 123\n",- "%session-changed $0 0\n%begin 234\n",- paneLine <> "\nb\n%end 234\n",- "%begin 345\n",- "c\n",- "d\n",- "%end 345\n"- ]--test_streamParse :: UnitTest-test_streamParse = do- r <- runConduit $ sourceList tmuxOutput .| mapC (packChars . T.unpack) .| parseConduit .| sinkList- [Success [], Success [paneLine, "b"], Success ["c", "d"]] === r
@@ -1,14 +0,0 @@-module Chiasma.Test.Util where--import Hedgehog (TestT, withTests, property, test)-import Test.Tasty (TestName, TestTree)-import Test.Tasty.Hedgehog (testProperty)--type UnitTest = TestT IO ()--unitTest ::- TestName ->- UnitTest ->- TestTree-unitTest desc =- testProperty desc . withTests 1 . property . test
@@ -1,24 +0,0 @@-module Main where--import Chiasma.Test.CodecTest (test_decode, test_query)-import Chiasma.Test.LensTest (test_lenses)-import Chiasma.Test.OutputParseTest (test_outputParse)-import Chiasma.Test.PinTest (test_pin)-import Chiasma.Test.StreamParseTest (test_streamParse)-import Chiasma.Test.Util (unitTest)-import Test.Tasty (TestTree, defaultMain, testGroup)--tests :: TestTree-tests =- testGroup "all" [- unitTest "tmux-decode a value" test_decode,- unitTest "tmux-encode a query" test_query,- test_lenses,- unitTest "parse tmux output" test_outputParse,- test_pin,- unitTest "parse tmux output in stream" test_streamParse- ]--main :: IO ()-main =- defaultMain tests