proteome-0.9.9.9: lib/Proteome/Grep/Replace.hs
module Proteome.Grep.Replace where
import qualified Data.List.NonEmpty as NonEmpty (toList)
import qualified Data.Text as Text
import Path (parseAbsFile)
import Prelude hiding (group)
import Ribosome (
Buffer,
Handler,
Rpc,
RpcError,
Scratch,
ScratchId (ScratchId),
ScratchState (ScratchState),
mapReport,
pathText,
resumeReport,
toMsgpack,
)
import Ribosome.Api (bufferGetLines, bufferSetLines, bufferSetOption, nvimCommand, vimCallFunction)
import Ribosome.Api.Autocmd (bufferAutocmd)
import Ribosome.Api.Buffer (addBuffer, bufferContent, bufferForFile, wipeBuffer)
import Ribosome.Api.Option (withOption)
import Ribosome.Data.FileBuffer (FileBuffer (FileBuffer))
import qualified Ribosome.Data.FloatOptions as FloatBorder
import Ribosome.Data.FloatOptions (FloatOptions (FloatOptions))
import Ribosome.Host.Data.RpcType (group)
import qualified Ribosome.Scratch as Scratch
import qualified Proteome.Data.Env as Env (replace)
import Proteome.Data.Env (Env)
import qualified Proteome.Data.GrepOutputLine as GrepOutputLine
import Proteome.Data.GrepOutputLine (GrepOutputLine (GrepOutputLine))
import Proteome.Data.Replace (Replace (Replace))
import qualified Proteome.Data.ReplaceError as ReplaceError (ReplaceError (BadReplacement, CouldntLoadBuffer))
import Proteome.Data.ReplaceError (ReplaceError)
scratchName :: Text
scratchName =
"proteome-replace"
replaceBuffer ::
Members [Scratch, Rpc, AtomicState Env] r =>
NonEmpty GrepOutputLine ->
Sem r ()
replaceBuffer lines' = do
scratch <- Scratch.show content options
let buffer = Scratch.buffer scratch
bufferSetOption buffer "buftype" ("acwrite" :: Text)
bufferAutocmd buffer "BufWriteCmd" def { group = Just "ProteomeReplace" } "silent! ProReplaceSave"
bufferAutocmd buffer "BufUnload" def { group = Just "ProteomeReplace" } "silent! ProReplaceQuit"
atomicModify' (#replace ?~ Replace scratch lines')
where
content =
GrepOutputLine.content <$> lines'
options =
def {
Scratch.name = ScratchId scratchName,
Scratch.modify = True,
Scratch.focus = True,
Scratch.filetype = Just scratchName
}
-- If the deleted line was surrounded by blank lines or buffer edges, there will be extraneous whitespace.
-- First check whether the line number of the deleted line was line 0 and its content is now empty.
-- Then do the same for the last line.
-- Finally, check if both the preceding and current line are empty.
deleteExtraBlankLine ::
Member Rpc r =>
Buffer ->
Int ->
Sem r ()
deleteExtraBlankLine buffer line = do
check (line - 2) line [""]
check line (line + 1) [""]
check (line - 1) line ["", ""]
where
check l r target = do
content <- readLines l (r + 1)
when (content == target) delete
readLines l r =
bufferGetLines buffer (clamp0 l) r False
delete =
bufferSetLines buffer line (line + 1) False []
clamp0 a | a < 0 = 0
clamp0 a = a
fileBuffer ::
Member Rpc r =>
Text ->
Sem r (Maybe FileBuffer)
fileBuffer path =
join <$> traverse bufferForFile (parseAbsFile (toString path))
replaceLine ::
Members [Rpc, Stop ReplaceError] r =>
Text ->
GrepOutputLine ->
Sem r (Maybe Buffer)
replaceLine updatedLine (GrepOutputLine path line _ _) = do
exists <- isJust <$> bufferForFile path
unless exists (addBuffer (pathText path))
() <- vimCallFunction "bufload" [toMsgpack path]
FileBuffer buffer _ <- stopNote (ReplaceError.CouldntLoadBuffer path) =<< bufferForFile path
bufferSetLines buffer line (line + 1) False replacement
deleteExtraBlankLine buffer line
pure (bool (Just buffer) Nothing exists)
where
replacement =
[updatedLine | not (Text.null updatedLine)]
lineNumberDesc :: (Text, GrepOutputLine) -> Int
lineNumberDesc (_, GrepOutputLine _ number _ _) =
-number
replaceFloatOptions :: FloatOptions
replaceFloatOptions =
FloatOptions def 1 1 0 0 False def Nothing FloatBorder.None True False (Just def) (Just 1)
withReplaceEnv ::
Members [Rpc !! RpcError, Rpc, Resource] r =>
Sem r [Maybe Buffer] ->
Sem r ()
withReplaceEnv run = do
withOption "hidden" True do
transient <- run
resume_ (nvimCommand "noautocmd wall")
traverse_ wipeBuffer (catMaybes transient)
replaceLines ::
Members [Rpc !! RpcError, Rpc, Resource, Stop ReplaceError] r =>
Buffer ->
[(Text, GrepOutputLine)] ->
Sem r ()
replaceLines scratchBuffer lines' = do
bufferSetOption scratchBuffer "buftype" ("nofile" :: Text)
withReplaceEnv do
traverse (uncurry replaceLine) (sortOn lineNumberDesc lines')
bufferSetOption scratchBuffer "buftype" ("acwrite" :: Text)
bufferSetOption scratchBuffer "modified" False
deleteLines ::
Members [Rpc !! RpcError, Rpc, Resource, Stop ReplaceError] r =>
[GrepOutputLine] ->
Sem r ()
deleteLines lines' =
withReplaceEnv do
traverse (uncurry replaceLine) (sortOn lineNumberDesc (zip (repeat "") lines'))
replaceSave ::
Members [Rpc !! RpcError, Rpc, Resource, Stop ReplaceError] r =>
Replace ->
Sem r ()
replaceSave (Replace (ScratchState _ _ buffer _ _ _ _) lines') = do
updatedLines <- bufferContent buffer
if length updatedLines /= length lines'
then badReplacement
else replaceLines buffer (zip updatedLines (NonEmpty.toList lines'))
where
badReplacement =
stop ReplaceError.BadReplacement
-- TODO quit after saving, controlled by setting
proReplaceSave ::
Members [AtomicState Env, Rpc !! RpcError, Resource] r =>
Handler r ()
proReplaceSave =
resumeReport $ mapReport do
traverse_ replaceSave =<< atomicGets Env.replace
proReplaceQuit ::
Member (AtomicState Env) r =>
Handler r ()
proReplaceQuit =
atomicModify' (#replace .~ Nothing)