packages feed

proteome-0.9.9.9: lib/Proteome/Buffers.hs

module Proteome.Buffers where

import Control.Lens (elemOf, view)
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Text as Text
import Exon (exon)
import Ribosome (
  Handler,
  Report,
  Rpc,
  RpcError,
  ScratchId (ScratchId),
  SettingError,
  Settings,
  mapReport,
  pathText,
  resumeReport,
  )
import Ribosome.Api (
  bufferGetName,
  bufferGetNumber,
  bufferIsFile,
  buflisted,
  ensureMainWindow,
  nvimBufIsLoaded,
  nvimCommand,
  nvimCwd,
  setCurrentBuffer,
  vimGetCurrentBuffer,
  vimGetCurrentWindow,
  vimSetCurrentWindow,
  )
import Ribosome.Menu (
  Filter (Fuzzy),
  Mappings,
  MenuAction (Render),
  MenuItem (MenuItem),
  MenuWidget,
  ModalState,
  ModalWindowMenus,
  deleteSelected,
  menuState,
  modal,
  staticWindowMenu,
  unselected,
  use,
  withFocus,
  withSelection',
  )
import Ribosome.Scratch (ScratchOptions (..))
import qualified Ribosome.Settings as Settings

import Proteome.Buffers.Syntax (buffersSyntax)
import qualified Proteome.Data.Env as Env
import Proteome.Data.Env (Env)
import qualified Proteome.Data.ListedBuffer as ListedBuffer
import Proteome.Data.ListedBuffer (ListedBuffer (ListedBuffer))
import Proteome.Menu (handleResult)
import qualified Proteome.Settings as Settings

newtype BufferAction =
  Load ListedBuffer
  deriving stock (Eq, Show)

type BuffersState =
  ModalState ListedBuffer

loadListedBuffer ::
  Member Rpc r =>
  ListedBuffer ->
  Sem r ()
loadListedBuffer (ListedBuffer buffer number _) =
  ifM (nvimBufIsLoaded buffer) (setCurrentBuffer buffer) (nvimCommand [exon|buffer #{show number}|])

load ::
  MenuWidget BuffersState r BufferAction
load =
  withFocus (pure . Load)

compensateForMissingActiveBuffer ::
  Member Rpc r =>
  NonEmpty ListedBuffer ->
  [ListedBuffer] ->
  Sem r ()
compensateForMissingActiveBuffer _ [] =
  nvimCommand "enew"
compensateForMissingActiveBuffer marked (next : _) = do
  prev <- vimGetCurrentWindow
  void ensureMainWindow
  current <- vimGetCurrentBuffer
  when (elemOf (each . #buffer) current marked) (loadListedBuffer next)
  vimSetCurrentWindow prev

deleteListedBuffersWith ::
  Member Rpc r =>
  Text ->
  NonEmpty ListedBuffer ->
  Sem r ()
deleteListedBuffersWith deleter bufs =
  nvimCommand [exon|#{deleter} #{numbers}|]
  where
    numbers =
      unwords (show . ListedBuffer.number <$> NonEmpty.toList bufs)

deleteWith ::
  Member Rpc r =>
  Text ->
  MenuWidget BuffersState r a
deleteWith deleter =
  menuState $ withSelection' \ delete -> do
    keep <- fmap (view #meta) <$> use unselected
    compensateForMissingActiveBuffer delete keep
    deleteListedBuffersWith deleter delete
    deleteSelected
    pure Render

moveCurrentLast ::
  Member Rpc r =>
  [MenuItem ListedBuffer] ->
  Sem r [MenuItem ListedBuffer]
moveCurrentLast items = do
  current <- vimGetCurrentBuffer
  pure $ spin current items []
  where
    spin current (item : rest) result | item ^. lens == current =
      result ++ rest ++ [item]
    spin current (item : rest) result =
      spin current rest (item : result)
    spin _ [] result =
      result
    lens =
      #meta . #buffer

buffers ::
  Members [AtomicState Env, Settings !! SettingError, Rpc, Rpc !! RpcError] r =>
  Sem r [MenuItem ListedBuffer]
buffers = do
  cwd <- nvimCwd
  bufs <- filterM bufferIsFile =<< filterM buflisted =<< atomicGets Env.buffers
  numbers <- traverse bufferGetNumber bufs
  names <- traverse bufferGetName bufs
  let items = item (pathText cwd) (padding numbers) <$> zip3 bufs numbers names
  ifM (Settings.or False Settings.buffersCurrentLast) (moveCurrentLast items) (pure items)
  where
    padding =
      Text.length . show . maximum
    item cwd pad (buf, num, name) =
      MenuItem (ListedBuffer buf num name) text' text'
      where
        text' =
          " * " <> padded pad (show num) <> "  " <> strip cwd name
    padded pad num =
      Text.replicate (pad - Text.length num) " " <> num
    strip cwd name =
      fromMaybe name $ Text.stripPrefix cwd name

actions ::
  Member Rpc r =>
  Mappings BuffersState r BufferAction
actions =
  [
    ("<cr>", load),
    ("d", deleteWith "bdelete"),
    ("D", deleteWith "bdelete!"),
    ("w", deleteWith "bwipeout"),
    ("W", deleteWith "bwipeout!")
  ]

bufferAction ::
  Member Rpc r =>
  BufferAction ->
  Sem r ()
bufferAction = \case
  Load buf ->
    loadListedBuffer buf

type BuffersStack =
  [
    ModalWindowMenus () ListedBuffer !! RpcError,
    AtomicState Env,
    Settings !! SettingError,
    Rpc !! RpcError,
    Log
  ]

buffersMenu ::
  ∀ r .
  Members BuffersStack r =>
  Members [Rpc, Stop Report] r =>
  Sem r ()
buffersMenu = do
  items <- buffers
  result <- mapReport do
    staticWindowMenu items (modal Fuzzy) (def & #items .~ scratchOptions) actions
  handleResult bufferAction result
  where
    scratchOptions =
      def {
        name = ScratchId name,
        syntax = [buffersSyntax],
        filetype = Just name
      }
    name =
      "proteome-buffers"

proBuffers ::
  Members BuffersStack r =>
  Handler r ()
proBuffers =
  resumeReport @Rpc do
    buffersMenu