packages feed

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)