packages feed

ribosome-0.9.9.9: lib/Ribosome/Api/Buffer.hs

-- |API functions for buffers.
module Ribosome.Api.Buffer where

import qualified Data.Text as Text (null)
import Exon (exon)
import Path (Abs, Dir, File, Path, parseAbsFile, parseRelFile, (</>))

import Ribosome.Api.Path (nvimCwd)
import Ribosome.Data.FileBuffer (FileBuffer (FileBuffer))
import qualified Ribosome.Host.Api.Data as Data
import Ribosome.Host.Api.Data (Buffer)
import Ribosome.Host.Api.Effect (
  bufferGetLines,
  bufferGetName,
  bufferGetNumber,
  bufferGetOption,
  bufferIsValid,
  bufferSetLines,
  nvimBufDelete,
  nvimCallFunction,
  nvimCommand,
  nvimGetCurrentBuf,
  nvimWinSetBuf,
  vimGetBuffers,
  vimGetCurrentBuffer,
  vimGetCurrentWindow,
  )
import Ribosome.Host.Class.Msgpack.Encode (toMsgpack)
import Ribosome.Host.Data.RpcError (RpcError)
import qualified Ribosome.Host.Effect.Rpc as Rpc
import Ribosome.Host.Effect.Rpc (Rpc)
import Ribosome.Host.Modify (silentBang)
import Ribosome.Host.Path (pathText)

-- |Load a 'Path' into a buffer in the current window using @:edit@.
edit ::
  Member Rpc r =>
  Path b t ->
  Sem r ()
edit path =
  silentBang do
    nvimCommand [exon|edit #{pathText path}|]

-- |Call the Neovim function @buflisted@ for a buffer, indicating whether it is shown in the buffer list (@:ls@).
buflisted ::
  Member (Rpc !! RpcError) r =>
  Buffer ->
  Sem r Bool
buflisted buf = do
  resumeAs False do
    num <- bufferGetNumber buf
    nvimCallFunction "buflisted" [toMsgpack num]

-- |Return the entire content of the given buffer.
bufferContent ::
  Member Rpc r =>
  Buffer ->
  Sem r [Text]
bufferContent buffer =
  bufferGetLines buffer 0 (-1) False

-- |Return the entire content of the current buffer.
currentBufferContent ::
  Member Rpc r =>
  Sem r [Text]
currentBufferContent =
  bufferContent =<< vimGetCurrentBuffer

-- |Replace the content of the given buffer.
setBufferContent ::
  Member Rpc r =>
  Buffer ->
  [Text] ->
  Sem r ()
setBufferContent buffer =
  bufferSetLines buffer 0 (-1) False

-- |Replace the content of the current buffer.
setCurrentBufferContent ::
  Member Rpc r =>
  [Text] ->
  Sem r ()
setCurrentBufferContent content = do
  buffer <- vimGetCurrentBuffer
  setBufferContent buffer content

-- |Replace a single line in the given buffer.
setBufferLine ::
  Member Rpc r =>
  Buffer ->
  Int ->
  Text ->
  Sem r ()
setBufferLine buffer line text =
  bufferSetLines buffer line (line + 1) False [text]

-- |Execute an action only if the given buffer is valid, i.e. it exists but may be unloaded.
whenValid ::
  Member Rpc r =>
  (Buffer -> Sem r ()) ->
  Buffer ->
  Sem r ()
whenValid use buffer =
  whenM (bufferIsValid buffer) (use buffer)

-- |Execute an action with the given buffer's number if it is valid.
withBufferNumber ::
  Member Rpc r =>
  (Int -> Sem r ()) ->
  Buffer ->
  Sem r ()
withBufferNumber run =
 whenValid (run <=< bufferGetNumber)

-- |Force-delete a buffer, discarding changes.
closeBuffer ::
  Member Rpc r =>
  Buffer ->
  Sem r ()
closeBuffer =
  silentBang . withBufferNumber del
  where
    del number =
      nvimCommand [exon|bdelete! #{show number}|]

-- |Force-wipe a buffer, discarding changes.
wipeBuffer ::
  Member Rpc r =>
  Buffer ->
  Sem r ()
wipeBuffer =
  whenValid \ b -> nvimBufDelete b [("force", toMsgpack True)]

-- |Force-unload a buffer, discarding changes.
unloadBuffer ::
  Member Rpc r =>
  Buffer ->
  Sem r ()
unloadBuffer =
  whenValid \ b -> nvimBufDelete b [("force", toMsgpack True), ("unload", toMsgpack True)]

-- |Add a buffer to the list without loading it.
addBuffer ::
  Member Rpc r =>
  Text ->
  Sem r ()
addBuffer path =
  nvimCommand [exon|badd #{path}|]

-- |Construct a file buffer from a path if it is parseable.
fileBuffer ::
  Path Abs Dir ->
  Buffer ->
  Text ->
  Maybe FileBuffer
fileBuffer cwd buffer (toString -> path) =
  FileBuffer buffer <$> (parseAbsFile path <|> (cwd </>) <$> parseRelFile path)

-- |Get all buffers in the buffer list whose name is a path.
fileBuffers ::
  Member Rpc r =>
  Sem r [FileBuffer]
fileBuffers = do
  cwd <- nvimCwd
  buffers <- vimGetBuffers
  names <- Rpc.sync (foldMap (fmap pure . Data.bufferGetName) buffers)
  pure (catMaybes (zipWith (fileBuffer cwd) buffers names))

-- |Find the buffer whose name is the given path.
bufferForFile ::
  Member Rpc r =>
  Path Abs File ->
  Sem r (Maybe FileBuffer)
bufferForFile target =
  find sameBuffer <$> fileBuffers
  where
    sameBuffer (FileBuffer _ path) =
      path == target

-- |Return the name of the current buffer.
currentBufferName ::
  Member Rpc r =>
  Sem r Text
currentBufferName =
  bufferGetName =<< vimGetCurrentBuffer

-- |Set the current buffer.
setCurrentBuffer ::
  Member Rpc r =>
  Buffer ->
  Sem r ()
setCurrentBuffer buf = do
  win <- vimGetCurrentWindow
  nvimWinSetBuf win buf

-- |Indicate whether the given buffer is a file, i.e. has empty @buftype@.
bufferIsFile ::
  Member Rpc r =>
  Buffer ->
  Sem r Bool
bufferIsFile buf =
  Text.null <$> bufferGetOption buf "buftype"

-- |Return the number of buffers in the list.
bufferCount ::
  Member Rpc r =>
  Sem r Natural
bufferCount =
  fromIntegral . length <$> vimGetBuffers

-- |Return the file system path of the given buffer, if its name is a valid path.
bufferPath ::
  Member Rpc r =>
  Buffer ->
  Sem r (Maybe (Path Abs File))
bufferPath buffer = do
  Rpc.sync do
    cwd <- Data.vimCallFunction "getcwd" []
    name <- Data.bufferGetName buffer
    pure (fileBuffer cwd buffer name <&> \ (FileBuffer _ path) -> path)

-- |Return the file system path of the current buffer, if its name is a valid path.
currentBufferPath ::
  Member Rpc r =>
  Sem r (Maybe (Path Abs File))
currentBufferPath =
  bufferPath =<< nvimGetCurrentBuf

-- |Filter out the unlisted buffers from the given list.
filterListed ::
  Member Rpc r =>
  [Buffer] ->
  Sem r [Buffer]
filterListed bufs = do
  nums <- Rpc.sync (traverse Data.bufferGetNumber bufs)
  listedFlags <- Rpc.sync (traverse (Data.nvimCallFunction "buflisted" . pure . toMsgpack) nums)
  let listed = mapMaybe chooseListed (zip bufs listedFlags)
  buftypes :: [Text] <- Rpc.sync (traverse (\ buf -> Data.bufferGetOption buf "buftype") listed)
  pure (mapMaybe chooseEmptyTypes (zip bufs buftypes))
  where
    chooseListed (b, l) =
      if l then Just b else Nothing
    chooseEmptyTypes = \case
      (b, "") -> Just b
      (_, _) -> Nothing