packages feed

cornelis-0.2.0.0: src/Cornelis/InfoWin.hs

{-# LANGUAGE OverloadedStrings #-}

module Cornelis.InfoWin (closeInfoWindows, showInfoWindow, buildInfoBuffer) where

import           Prelude hiding (Left, Right)
import           Control.Monad (unless)
import           Control.Monad.State.Class
import           Cornelis.Pretty
import           Cornelis.Types
import           Cornelis.Utils (withBufferStuff, windowsForBuffer, savingCurrentWindow, visibleBuffers)
import           Data.Foldable (for_)
import qualified Data.Map as M
import qualified Data.Set as S
import qualified Data.Text as T
import           Data.Traversable (for)
import qualified Data.Vector as V
import           Neovim
import           Neovim.API.Text
import           Prettyprinter (layoutPretty, LayoutOptions (LayoutOptions), PageWidth (AvailablePerLine))
import           Prettyprinter.Render.Text (renderStrict)


cornelisWindowVar :: Text
cornelisWindowVar = "cornelis_window"

getBufferVariableOfWindow :: NvimObject o => Text -> Window -> Neovim env (Maybe o)
getBufferVariableOfWindow variableName window = do
  window_is_valid window >>= \case
    False -> pure Nothing
    True -> do
      buffer <- window_get_buffer window
      let getVariableValue = Just . fromObjectUnsafe <$> buffer_get_var buffer variableName
      getVariableValue `catchNeovimException` const (pure Nothing)

closeInfoWindowsForUnseenBuffers :: Neovim CornelisEnv ()
closeInfoWindowsForUnseenBuffers = do
  seen <- S.fromList . fmap snd <$> visibleBuffers
  bufs <- gets cs_buffers
  let known = M.keysSet bufs
      unseen = known S.\\ seen
  for_ unseen $ \b -> do
    for_ (M.lookup b bufs) $ \bs -> do
      ws <- windowsForBuffer $ iw_buffer $ bs_info_win bs
      for_ ws $ flip nvim_win_close True

closeInfoWindows :: Neovim env ()
closeInfoWindows = do
  ws <- vim_get_windows
  for_ ws $ \w -> getBufferVariableOfWindow cornelisWindowVar w >>= \case
    Just True -> nvim_win_close w True
    _ -> pure ()


closeInfoWindowForBuffer :: BufferStuff -> Neovim CornelisEnv ()
closeInfoWindowForBuffer bs = do
  let InfoBuffer ib = bs_info_win bs
  ws <- windowsForBuffer ib
  for_ ws $ flip nvim_win_close True



showInfoWindow :: Buffer -> Doc HighlightGroup -> Neovim CornelisEnv ()
showInfoWindow b doc = withBufferStuff b $ \bs -> do
  let ib = bs_info_win bs
  closeInfoWindowsForUnseenBuffers

  vis <- visibleBuffers
  ns <- asks ce_namespace

  -- Check if the info win still exists, and if so, just modify it
  found <- fmap or $
    for vis $ \(w, vb) -> do
      case vb == iw_buffer ib of
        False -> pure False
        True -> do
          writeInfoBuffer ns ib doc
          resizeInfoWin w ib
          pure True

  -- Otherwise we need to rebuild it
  unless found $ do
    closeInfoWindowForBuffer bs
    writeInfoBuffer ns ib doc
    ws <- windowsForBuffer b
    for_ ws $ buildInfoWindow ib



buildInfoBuffer :: Neovim env InfoBuffer
buildInfoBuffer = do
  b <-
    -- not listed in the buffer list, is throwaway
    nvim_create_buf False True

  -- Setup things in the buffer
  void $ buffer_set_var b cornelisWindowVar $ ObjectBool True
  nvim_buf_set_option b "modifiable" $ ObjectBool False
  nvim_buf_set_option b "filetype" $ ObjectString "agdainfo"
  pure $ InfoBuffer b




buildInfoWindow :: InfoBuffer -> Window -> Neovim CornelisEnv Window
buildInfoWindow (InfoBuffer split_buf) w = savingCurrentWindow $ do
  nvim_set_current_win w
  max_height <-  asks $ T.pack . show . cc_max_height . ce_config
  max_width <- asks $ T.pack . show . cc_max_width . ce_config
  asks (cc_split_location . ce_config) >>= \case
    Vertical   -> vim_command $ max_width <> " vsplit"
    Horizontal -> vim_command $ max_height <> " split"
    OnLeft     -> vim_command $ "topleft " <> max_width <> " vsplit"
    OnRight    -> vim_command $ "botright " <> max_width <> " vsplit"
    OnTop      -> vim_command $ "topleft " <> max_height <> " split"
    OnBottom   -> vim_command $ "botright " <> max_height <> " split"
  split_win <- nvim_get_current_win
  nvim_win_set_buf split_win split_buf

  -- Setup things in the window
  nvim_win_set_option split_win "relativenumber" $ ObjectBool False
  nvim_win_set_option split_win "number" $ ObjectBool False

  resizeInfoWin split_win (InfoBuffer split_buf)

  pure split_win

resizeInfoWin :: Window -> InfoBuffer -> Neovim CornelisEnv ()
resizeInfoWin w ib = do
  t <- nvim_buf_get_lines (iw_buffer ib) 0 (-1) False
  max_size <- asks $ cc_max_height . ce_config
  let size = min max_size $ fromIntegral $ V.length t
  asks (cc_split_location . ce_config) >>= \case
    Vertical   -> pure ()
    Horizontal -> window_set_height w size
    OnLeft     -> pure ()
    OnRight    -> pure ()
    OnTop      -> window_set_height w size
    OnBottom   -> window_set_height w size



writeInfoBuffer :: Int64 -> InfoBuffer -> Doc HighlightGroup -> Neovim env ()
writeInfoBuffer ns iw doc = do
  -- TODO(sandy): Bad choice for a window, but good enough?
  w <- nvim_get_current_win
  width <- window_get_width w

  let sds = layoutPretty (LayoutOptions (AvailablePerLine (fromIntegral width) 0.8)) doc
      (hls, sds') = renderWithHlGroups sds
      s = T.lines $ renderStrict sds'

  let b = iw_buffer iw
  nvim_buf_set_option b "modifiable" $ ObjectBool True
  buffer_set_lines b 0 (-1) True $ V.fromList s

  for_ (concatMap spanInfoHighlights hls) $ \(InfoHighlight (l, sc) ec hg) ->
    nvim_buf_add_highlight
      b ns
      (T.pack $ show hg)
      l sc ec
  nvim_buf_set_option b "modifiable" $ ObjectBool False