Files

171 lines
5.0 KiB
Lua

local frame = require("scratch_comments.ui.frame")
local notify = require("scratch_comments.ui.notify")
local M = {}
local mode_names = {
n = "NORMAL",
i = "INSERT",
v = "VISUAL",
V = "VISUAL",
["\22"] = "VISUAL",
c = "COMMAND",
R = "REPLACE",
}
---@class ScratchCommentWindow
---@field id? string unset until a new comment is saved
---@field bufnr? integer
---@field source_name? string
---@field line integer
---@field source_win integer
---@field comment_win? integer
---@field on_close? fun()
---@field on_saved? fun()
---@type ScratchCommentWindow?
local current
-- A quickfix row with no source location still edits in the file area, while
-- closing its card returns focus to the list.
---@param record ScratchCommentWindow
---@return integer?
local function editing_window(record)
local source = record.source_win
if
vim.api.nvim_win_is_valid(source)
and vim.bo[vim.api.nvim_win_get_buf(source)].buftype ~= "quickfix"
then
return source
end
local file_win, other_win
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
if vim.api.nvim_win_get_config(win).relative == "" then
local bufnr = vim.api.nvim_win_get_buf(win)
if record.source_name and vim.api.nvim_buf_get_name(bufnr) == record.source_name then
return win
end
if vim.bo[bufnr].buftype == "" then
file_win = file_win or win
elseif vim.bo[bufnr].buftype ~= "quickfix" then
other_win = other_win or win
end
end
end
if file_win or other_win then
return file_win or other_win
end
-- Quickfix cannot provide a source area on its own. Make one if there is
-- room; otherwise keep the list usable rather than drawing over it.
if not pcall(function()
vim.cmd("aboveleft new")
end) then
notify.warn("Make room for an editing window to open this comment")
return nil
end
return vim.api.nvim_get_current_win()
end
---@param spec ScratchFrame
---@param record ScratchCommentWindow
function M.open(spec, record)
spec.on_close = function()
if current and current.comment_win == record.comment_win then
current = nil
end
if record.on_close then
record.on_close()
end
end
local source_buffer_name = record.source_name
or (record.bufnr and vim.api.nvim_buf_get_name(record.bufnr))
or "comment"
local source_name = vim.fn.fnamemodify(source_buffer_name, ":t")
spec.comment_name = source_name .. ":" .. record.line .. " [comment]"
local parent = editing_window(record)
if not parent then
return
end
local panes = frame.open(spec, parent)
local comment_win, comment_buf = panes.comment_win, panes.comment_buf
record.comment_win = comment_win
current = record
local function show_mode()
if vim.api.nvim_win_is_valid(comment_win) then
local mode = vim.api.nvim_get_mode().mode:sub(1, 1)
local name = mode_names[mode] or "NORMAL"
vim.api.nvim_win_set_config(comment_win, {
title = " " .. (spec.comment_title or "comment") .. " [" .. name .. "] ",
title_pos = "center",
})
vim.cmd.redraw()
end
end
show_mode()
vim.api.nvim_create_autocmd("ModeChanged", { buffer = comment_buf, callback = show_mode })
for _, buf in ipairs({ comment_buf, panes.context_buf }) do
vim.keymap.set("n", "<Esc>", function()
if vim.bo[comment_buf].modified then
notify.warn("Save with :w, or discard with :q!")
return
end
pcall(vim.api.nvim_win_close, comment_win, true)
end, { buffer = buf, desc = "Close the comment window" })
end
vim.bo[comment_buf].buftype = "acwrite"
if spec.comment == "" then
vim.cmd.startinsert()
end
vim.api.nvim_create_autocmd("BufWriteCmd", {
buffer = comment_buf,
callback = function()
local lines = vim.api.nvim_buf_get_lines(comment_buf, 0, -1, false)
local text = vim.trim(table.concat(lines, "\n"))
local result = spec.on_save and spec.on_save(text)
if result == false then
return
end
vim.bo[comment_buf].modified = false
if record.on_saved then
record.on_saved()
end
if result == "deleted" then
vim.schedule(function()
if vim.api.nvim_win_is_valid(comment_win) then
vim.api.nvim_win_close(comment_win, true)
end
end)
end
end,
})
end
-- Closes the open comment window, unless it has unsaved changes.
---@return boolean closed
function M.close()
local open = M.current()
if not open then
return true
end
if vim.bo[vim.api.nvim_win_get_buf(open.comment_win)].modified then
return false
end
-- A command replacing the card (e.g. :CommentExport!) owns the next focus.
-- Only a user closing the card should return to its source or list.
open.on_close = nil
pcall(vim.api.nvim_win_close, open.comment_win, true)
return true
end
---@return ScratchCommentWindow? window the open comment window, if any
function M.current()
if current and vim.api.nvim_win_is_valid(current.comment_win) then
return current
end
end
return M