chiasma-0.2.0.0: lib/Chiasma/Window.hs
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 Chiasma.Data.Ident (Ident, identText, identify)
import Chiasma.Data.Maybe (findMaybe, maybeExcept, orElse)
import Chiasma.Data.RenderError (RenderError)
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 Chiasma.Data.Views (Views)
import Chiasma.Data.WindowState (WindowState(..))
import Chiasma.Pane (addPane)
import Chiasma.Ui.Data.RenderableTree (
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 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) =>
Ident ->
m (Tmux.View WindowId)
findOrCreateWindow =
findOrCreateView Views.window Views.insertWindow
registerWindowId ::
(MonadDeepState s Views m) =>
Ident ->
WindowId ->
m ()
registerWindowId ident windowId =
modify $ Views.updateWindow $ Tmux.View ident (Just windowId)
spawnWindow ::
(MonadDeepState s Views m, MonadFree TmuxThunk m) =>
SessionId ->
Ident ->
m 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
findPrincipalSub :: ViewTreeSub -> Maybe Ui.PaneView
findPrincipalSub (TreeNode t) = findPrincipal t
findPrincipalSub (TreeLeaf p@(Ui.View _ _ _ (Ui.Pane True _ _))) = Just p
findPrincipalSub _ = Nothing
findPrincipal :: ViewTree -> Maybe Ui.PaneView
findPrincipal (Tree _ sub) =
findMaybe findPrincipalSub sub
principalPane ::
(MonadDeepState s Views m, MonadError RenderError m) =>
ViewTree ->
m (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)
syncPrincipal ::
(MonadDeepState s Views m, MonadFree TmuxThunk m, MonadError RenderError m) =>
WindowId ->
ViewTree ->
m ()
syncPrincipal windowId tree@(Tree (Ui.View layoutIdent _ _ _) _) = do
(Codec.Pane paneId _ _) <- Cmd.firstWindowPane windowId
existing <- gets (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 ()
ensureWindow ::
(MonadDeepState s Views m, MonadFree TmuxThunk m, MonadError RenderError m) =>
SessionId ->
Tmux.View WindowId ->
Maybe WindowId ->
ViewTree ->
m 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
syncPrincipal (Codec.windowId window) tree
return window
findOrCreatePane :: MonadDeepState s Views m => Ident -> m (Tmux.View PaneId)
findOrCreatePane =
findOrCreateView Views.pane Views.insertPane
nativePane ::
MonadFree TmuxThunk m =>
WindowId ->
Tmux.View PaneId ->
m (Maybe Codec.PaneDetail)
nativePane windowId (Tmux.View _ (Just paneId)) =
find sameId <$> Cmd.windowPanesAs windowId
where
sameId (Codec.PaneDetail i _ _ _ _) = i == paneId
nativePane _ _ = return Nothing
openPane ::
(MonadDeepState s Views m, MonadFree TmuxThunk m) =>
FilePath ->
WindowId ->
m Codec.PaneDetail
openPane dir windowId = do
detail <- Cmd.splitWindowAs dir windowId
viewsLogS $ "opened pane " <> show (PaneDetail.paneId detail) <> " in window " <> show windowId
return detail
ensurePaneOpen ::
(MonadDeepState s Views m, MonadFree TmuxThunk m) =>
FilePath ->
Maybe Codec.PaneDetail ->
WindowId ->
m Codec.PaneDetail
ensurePaneOpen _ (Just detail) _ =
return 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
viewsLogS $ "closing pane " <> show i
Cmd.closePane i
ensurePaneClosed _ = return ()
ensurePane ::
(MonadDeepState s Views m, MonadFree TmuxThunk m) =>
FilePath ->
WindowId ->
Ui.PaneView ->
m (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
where
dir = fromMaybe cwd customDir
cons (Codec.PaneDetail i _ _ top left) =
Tree.Leaf . Renderable vState geometry $ RPane i top left
refPane :: RenderableNode -> RPane
refPane (Tree.Sub (Tree.Tree (Renderable _ _ (RLayout ref _)) _)) = ref
refPane (Tree.Leaf (Renderable _ _ pane)) = pane
renderableTree ::
ViewState ->
ViewGeometry ->
Bool ->
[RenderableNode] ->
Maybe RenderableTree
renderableTree vState geometry vertical sub = do
sub' <- NonEmpty.nonEmpty sub
return $ Tree.Tree (Renderable vState geometry (RLayout (refPane $ NonEmpty.head sub') vertical)) sub'
viewPosition :: ViewTreeSub -> Float
viewPosition (TreeNode (Tree (Ui.View _ _ ViewGeometry { position = pos } _) _)) =
fromMaybe 0.5 pos
viewPosition (TreeLeaf (Ui.View _ _ ViewGeometry { position = pos } _)) =
fromMaybe 0.5 pos
ensureView ::
(MonadDeepState s Views m, MonadFree TmuxThunk m) =>
FilePath ->
WindowId ->
ViewTree ->
m (Maybe RenderableTree)
ensureView cwd windowId =
ensureTree
where
ensureTree (Tree (Ui.View layoutIdent vState geometry (Ui.Layout vertical)) 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
where
sortedSub = sortOn viewPosition sub
ensureNode (TreeNode t) = do
newTree <- ensureTree t
return $ Tree.Sub <$> newTree
ensureNode (TreeLeaf v) =
ensurePane cwd windowId v
windowState ::
(MonadDeepState s Views m, MonadFree TmuxThunk m) =>
Ident ->
Codec.Window ->
RenderableTree ->
m WindowState
windowState windowIdent window tree = do
nativeRef <- Cmd.firstWindowPane (Codec.windowId window)
return $ WindowState window nativeRef windowIdent tree (Codec.paneId nativeRef)