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