ribosome-0.9.9.9: lib/Ribosome/Internal/Scratch.hs
{-# options_haddock prune #-}
-- |Internal logic for 'Ribosome.Scratch'.
module Ribosome.Internal.Scratch where
import qualified Data.Map.Strict as Map
import Data.MessagePack (Object)
import Exon (exon)
import qualified Polysemy.Log as Log
import Prelude hiding (group)
import Ribosome.Api.Autocmd (bufferAutocmd, eventignore)
import Ribosome.Api.Buffer (setBufferContent, wipeBuffer)
import Ribosome.Api.Syntax (executeWindowSyntax)
import Ribosome.Api.Tabpage (closeTabpage)
import Ribosome.Api.Window (closeWindow)
import Ribosome.Data.FloatOptions (FloatOptions, enter)
import Ribosome.Data.PluginName (PluginName (PluginName))
import Ribosome.Data.ScratchId (ScratchId (ScratchId, unScratchId))
import Ribosome.Data.ScratchOptions (ScratchOptions (ScratchOptions, filetype, name), focus, mappings, syntax)
import qualified Ribosome.Data.ScratchState as ScratchState
import Ribosome.Data.ScratchState (ScratchState (ScratchState))
import Ribosome.Host.Api.Data (Buffer, Tabpage, Window)
import Ribosome.Host.Api.Effect (
bufferGetName,
bufferSetName,
bufferSetOption,
nvimBufIsLoaded,
nvimCreateBuf,
nvimDelAutocmd,
nvimOpenWin,
vimCommand,
vimGetCurrentBuffer,
vimGetCurrentTabpage,
vimGetCurrentWindow,
vimSetCurrentWindow,
windowGetBuffer,
windowIsValid,
windowSetHeight,
windowSetOption,
windowSetWidth,
)
import Ribosome.Host.Class.Msgpack.Decode (fromMsgpack)
import Ribosome.Host.Class.Msgpack.Encode (toMsgpack)
import Ribosome.Host.Data.RpcError (RpcError)
import Ribosome.Host.Data.RpcType (AutocmdId (AutocmdId), group)
import Ribosome.Host.Effect.Rpc (Rpc)
import Ribosome.Mapping (activateBufferMapping)
import Ribosome.PluginName (pluginNamePascalCase)
createScratchTab :: Member Rpc r => Sem r Tabpage
createScratchTab = do
vimCommand "tabnew"
vimGetCurrentTabpage
createRegularWindow ::
Member Rpc r =>
Bool ->
Bool ->
Maybe Int ->
Sem r (Buffer, Window)
createRegularWindow vertical bottom size = do
vimCommand prefixedCmd
buf <- vimGetCurrentBuffer
win <- vimGetCurrentWindow
pure (buf, win)
where
prefixedCmd = locationPrefix <> " " <> sizePrefix <> cmd
cmd = if vertical then "vnew" else "new"
sizePrefix = maybe "" show size
locationPrefix = if bottom then "belowright" else "aboveleft"
floatConfig ::
FloatOptions ->
Map Text Object
floatConfig =
fromRight Map.empty . fromMsgpack . toMsgpack
createFloatWith ::
Member Rpc r =>
Bool ->
Bool ->
FloatOptions ->
Sem r (Buffer, Window)
createFloatWith listed scratch options = do
buffer <- nvimCreateBuf listed scratch
window <- nvimOpenWin buffer (enter options) (floatConfig options)
pure (buffer, window)
createFloat ::
Member Rpc r =>
FloatOptions ->
Sem r (Buffer, Window)
createFloat =
createFloatWith True True
createScratchWindow ::
Member Rpc r =>
Bool ->
Bool ->
Bool ->
Maybe FloatOptions ->
Maybe Int ->
Sem r (Buffer, Window)
createScratchWindow vertical wrap bottom float size = do
(buffer, win) <- createWindow
windowSetOption win "wrap" (toMsgpack wrap)
windowSetOption win "number" (toMsgpack False)
windowSetOption win "cursorline" (toMsgpack True)
windowSetOption win "colorcolumn" (toMsgpack ("" :: Text))
windowSetOption win "foldmethod" (toMsgpack ("manual" :: Text))
windowSetOption win "conceallevel" (toMsgpack (2 :: Int))
windowSetOption win "concealcursor" (toMsgpack ("nvic" :: Text))
pure (buffer, win)
where
createWindow =
maybe regular createFloat float
regular =
createRegularWindow vertical bottom size
createScratchUiInTab :: Member Rpc r => Sem r (Buffer, Window, Maybe Tabpage)
createScratchUiInTab = do
tab <- createScratchTab
win <- vimGetCurrentWindow
buffer <- windowGetBuffer win
pure (buffer, win, Just tab)
createScratchUi ::
Member Rpc r =>
ScratchOptions ->
Sem r (Buffer, Window, Maybe Tabpage)
createScratchUi (ScratchOptions False vertical wrap _ _ bottom _ float size _ _ _ _ _) =
uncurry (,,Nothing) <$> createScratchWindow vertical wrap bottom float size
createScratchUi _ =
createScratchUiInTab
configureScratchBuffer ::
Member Rpc r =>
Buffer ->
Maybe Text ->
ScratchId ->
Sem r ()
configureScratchBuffer buffer ft (ScratchId name) = do
bufferSetOption buffer "bufhidden" ("wipe" :: Text)
bufferSetOption buffer "buftype" ("nofile" :: Text)
bufferSetOption buffer "swapfile" False
traverse_ (bufferSetOption buffer "filetype") ft
bufferSetName buffer name
setupScratchBuffer ::
Members [Rpc, Log] r =>
Window ->
Buffer ->
Maybe Text ->
ScratchId ->
Sem r Buffer
setupScratchBuffer window buffer ft name = do
valid <- nvimBufIsLoaded buffer
Log.debug [exon|#{if valid then "" else "in"}valid scratch buffer|]
validBuffer <- if valid then pure buffer else windowGetBuffer window
configureScratchBuffer validBuffer ft name
pure validBuffer
setupDeleteAutocmd ::
Members [Rpc, Reader PluginName] r =>
ScratchId ->
Buffer ->
Sem r AutocmdId
setupDeleteAutocmd (ScratchId name) buffer = do
PluginName pname <- pluginNamePascalCase
bufferAutocmd buffer "BufDelete" def { group = Just "RibosomeScratch" } (deleteCall pname)
where
deleteCall pname =
[exon|silent! call #{pname}DeleteScratch('#{name}')|]
setupScratchIn ::
Members [Rpc, AtomicState (Map ScratchId ScratchState), Reader PluginName, Log] r =>
Buffer ->
Window ->
Window ->
Maybe Tabpage ->
ScratchOptions ->
Sem r ScratchState
setupScratchIn buffer previous window tab options@(ScratchOptions {..}) = do
validBuffer <- setupScratchBuffer window buffer filetype name
traverse_ (executeWindowSyntax window) syntax
traverse_ (activateBufferMapping validBuffer) mappings
unless focus (vimSetCurrentWindow previous)
auId <- setupDeleteAutocmd name validBuffer
let scratch = ScratchState name options validBuffer window previous tab auId
atomicModify' (Map.insert name scratch)
pure scratch
createScratch ::
Members [Rpc, AtomicState (Map ScratchId ScratchState), Reader PluginName, Log, Resource] r =>
ScratchOptions ->
Sem r ScratchState
createScratch options = do
Log.debug [exon|creating new scratch: #{show options}|]
previous <- vimGetCurrentWindow
(buffer, window, tab) <- eventignore (createScratchUi options)
eventignore $ setupScratchIn buffer previous window tab options
bufferStillLoaded ::
Members [Rpc !! RpcError, Rpc] r =>
ScratchId ->
Buffer ->
Sem r Bool
bufferStillLoaded (ScratchId name) buffer =
(&&) <$> loaded <*> loadedName
where
loaded =
nvimBufIsLoaded buffer
loadedName =
resumeAs @RpcError False ((name ==) <$> bufferGetName buffer)
updateScratch ::
Members [Rpc !! RpcError, Rpc, AtomicState (Map ScratchId ScratchState), Reader PluginName, Log, Resource] r =>
ScratchState ->
ScratchOptions ->
Sem r ScratchState
updateScratch oldScratch@(ScratchState name _ oldBuffer oldWindow _ _ _) options = do
Log.debug [exon|updating existing scratch `#{coerce name}`|]
ifM (windowIsValid oldWindow) attemptReuseWindow reset
where
attemptReuseWindow =
ifM (bufferStillLoaded name oldBuffer) (pure oldScratch) closeAndReset
closeAndReset =
closeWindow oldWindow *> reset
reset =
createScratch options
lookupScratch ::
Member (AtomicState (Map ScratchId ScratchState)) r =>
ScratchId ->
Sem r (Maybe ScratchState)
lookupScratch name =
atomicGets (Map.lookup name)
ensureScratch ::
Members [Rpc !! RpcError, Rpc, AtomicState (Map ScratchId ScratchState), Reader PluginName, Log, Resource] r =>
ScratchOptions ->
Sem r ScratchState
ensureScratch options = do
f <- maybe createScratch updateScratch <$> lookupScratch (options ^. #name)
f options
withModifiable ::
Member Rpc r =>
Buffer ->
ScratchOptions ->
Sem r a ->
Sem r a
withModifiable buffer options thunk =
if isWrite then thunk else wrap
where
isWrite =
options ^. #modify
wrap =
update True *> thunk <* update False
update =
bufferSetOption buffer "modifiable"
setScratchContent ::
Foldable t =>
Members [Rpc !! RpcError, Rpc] r =>
ScratchState ->
t Text ->
Sem r ()
setScratchContent (ScratchState _ options buffer win _ _ _) lines' = do
withModifiable buffer options $ setBufferContent buffer (toList lines')
when (options ^. #resize) (resume_ @RpcError @Rpc (setSize win size))
where
size =
max 1 calculateSize
calculateSize =
if vertical then fromMaybe 50 maxSize else min (length lines') (fromMaybe 30 maxSize)
maxSize =
options ^. #maxSize
vertical =
options ^. #vertical
setSize =
if vertical then windowSetWidth else windowSetHeight
showInScratch ::
Foldable t =>
Members [Rpc !! RpcError, Rpc, AtomicState (Map ScratchId ScratchState), Reader PluginName, Log, Resource] r =>
t Text ->
ScratchOptions ->
Sem r ScratchState
showInScratch lines' options = do
scratch <- ensureScratch options
scratch <$ setScratchContent scratch lines'
showInScratchDef ::
Foldable t =>
Members [Rpc !! RpcError, Rpc, AtomicState (Map ScratchId ScratchState), Reader PluginName, Log, Resource] r =>
t Text ->
Sem r ScratchState
showInScratchDef lines' =
showInScratch lines' def
killScratch ::
Members [Rpc !! RpcError, AtomicState (Map ScratchId ScratchState), Log] r =>
ScratchState ->
Sem r ()
killScratch (ScratchState name _ buffer window _ tab (AutocmdId auId)) = do
Log.debug [exon|Killing scratch buffer `#{unScratchId name}`|]
atomicModify' (Map.delete @_ @ScratchState name)
resume_ (nvimDelAutocmd auId)
traverse_ (resume_ . closeTabpage) tab
resume_ (closeWindow window)
resume_ (wipeBuffer buffer)
scratchPreviousWindow ::
Member (AtomicState (Map ScratchId ScratchState)) r =>
ScratchId ->
Sem r (Maybe Window)
scratchPreviousWindow =
fmap (fmap ScratchState.previous) . lookupScratch
scratchWindow ::
Member (AtomicState (Map ScratchId ScratchState)) r =>
ScratchId ->
Sem r (Maybe Window)
scratchWindow =
fmap (fmap ScratchState.window) . lookupScratch
scratchBuffer ::
Member (AtomicState (Map ScratchId ScratchState)) r =>
ScratchId ->
Sem r (Maybe Buffer)
scratchBuffer =
fmap (fmap ScratchState.buffer) . lookupScratch