Skip to content

Commit

Permalink
feat: Hooks (#99)
Browse files Browse the repository at this point in the history
* feat: Implemented hooks.

Hooks let you define event callbacks that will be called when the
related events are emitted from Diffview. These are the currently
implemented hooks:

- 'diff_buff_read'
- 'view_opened'
- 'view_closed'

See ':h diffview-config-hooks'

* doc: Update README.

* feat: Implement all hooks as autocmds as well.
  • Loading branch information
sindrets committed Nov 30, 2021
1 parent f92d27b commit 2c0f5af
Show file tree
Hide file tree
Showing 11 changed files with 279 additions and 31 deletions.
27 changes: 27 additions & 0 deletions README.md
Expand Up @@ -79,6 +79,7 @@ require'diffview'.setup {
DiffviewOpen = {},
DiffviewFileHistory = {},
},
hooks = {}, -- See ':h diffview-config-hooks'
key_bindings = {
disable_defaults = false, -- Disable the default key bindings
-- The `view` bindings are active in the diff buffers, only when the current
Expand Down Expand Up @@ -156,6 +157,32 @@ that only really make sense specifically in the file panel, such as
invoked from the view, these will target the file currently open in the view
rather than the file under the cursor in the file panel.

### Hooks

The `hooks` table allows you to define callbacks for various events emitted from
Diffview. The available hooks are documented in detail in
`:h diffview-config-hooks`. The hook events are also available as User
autocommands. See `:h diffview-user-autocmds` for more details.

Examples:

```lua
hooks = {
diff_buf_read = function(bufnr)
-- Change local options in diff buffers
vim.opt_local.wrap = false
vim.opt_local.list = false
vim.opt_local.colorcolumn = { 80 }
end,
view_opened = function(view)
print(
("A new %s was opened on tab page %d!")
:format(view:class():name(), view.tabpage)
)
end,
}
```

### Available Unused Mappings

This section documents key-mappable functions that are not mapped by default.
Expand Down
78 changes: 78 additions & 0 deletions doc/diffview.txt
Expand Up @@ -62,6 +62,7 @@ Example configuration with default settings:
DiffviewOpen = {},
DiffviewFileHistory = {},
},
hooks = {}, -- See |diffview-config-hooks|
file_history_panel = {
position = "bottom",
width = 35,
Expand Down Expand Up @@ -157,6 +158,63 @@ default_args *diffview-config-default_args*
DiffviewOpen = { "--untracked-files=no", "--imply-local" }
}
hooks *diffview-config-hooks*
Type: `table<string, function>`, Default: `{}`

This is a table of event names mapped to callback functions. The
callbacks are called when the related events are emitted from
Diffview. The hook events are also available as User autocommands. See
|diffview-user-autocmds| for more details.

Available Events:~
{view_opened} (`fun(view: View)`)
Emitted after a new view has been opened. It's called
after initializing the layout in the new tabpage (all
windows are ready).

Callback Parameters:~
{view} (`View`)
The `View` instance that was opened.

{view_closed} (`fun(view: View)`)
Emitted after closing a view.

Callback Parameters:~
{view} (`View`)
The `View` instance that was closed.

{diff_buf_read} (`fun(bufnr: integer)`)
Emitted after a new diff buffer is ready (the first
time it's created and loaded into a window). Diff
buffers are all buffers with |diff-mode| enabled. That
includes buffers of local files (not created from
git).

This is always called with the new buffer as the
current buffer and the correct diff window as the
current window such that |:setlocal| will apply
settings to the relevant buffer / window.

Callback Parameters:~
{bufnr} (`integer`)
The buffer number of the new buffer.

Examples: >
hooks = {
diff_buf_read = function(bufnr)
-- Change local options in diff buffers
vim.opt_local.wrap = false
vim.opt_local.list = false
vim.opt_local.colorcolumn = { 80 }
end,
view_opened = function(view)
print(
("A new %s was opened on tab page %d!")
:format(view:class():name(), view.tabpage)
)
end,
}
AVAILABLE UNUSED MAPPINGS *diffview-available-unused-mappings*

This section documents key-mappable functions that are not mapped by default.
Expand Down Expand Up @@ -244,6 +302,26 @@ COMMANDS *diffview-commands*
:DiffviewRefresh Update stats and entries in the file list of the
current Diffview.

USER AUTOCOMMANDS *diffview-user-autocmds*

For information on how to use User autocommands, see |User|.

`DiffviewViewOpened` Fires after a new view has been opened. It's called
after initializing the layout in the new tabpage (all
windows are ready).

`DiffviewViewClosed` Fires after a view is closed.

`DiffviewDiffBufRead` Fires after a new diff buffer is ready (the first
time it's created and loaded into a window). Diff
buffers are all buffers with |diff-mode| enabled. That
includes buffers of local files (not created from git).

This is always called with the new buffer as the current
buffer and the correct diff window as the current window
such that |:setlocal| will apply settings to the
relevant buffer / window.

MAPS *diffview-maps*

All listed maps are the defaults, but all mappings can be configured. Most of
Expand Down
11 changes: 11 additions & 0 deletions lua/diffview.lua
Expand Up @@ -22,6 +22,17 @@ end

function M.init()
colors.setup()

-- Set up autocommand emitters
DiffviewGlobal.emitter:on("view_opened", function(_)
vim.cmd("do <nomodeline> User DiffviewViewOpened")
end)
DiffviewGlobal.emitter:on("view_closed", function(_)
vim.cmd("do <nomodeline> User DiffviewViewClosed")
end)
DiffviewGlobal.emitter:on("diff_buf_read", function(_)
vim.cmd("do User DiffviewDiffBufRead")
end)
end

function M.open(...)
Expand Down
123 changes: 98 additions & 25 deletions lua/diffview/api/views/file_entry.lua
@@ -1,5 +1,6 @@
local oop = require("diffview.oop")
local utils = require("diffview.utils")
local async = require("plenary.async")
local FileEntry = require("diffview.views.file_entry").FileEntry
local RevType = require("diffview.git.rev").RevType
local api = vim.api
Expand All @@ -25,93 +26,149 @@ function CFileEntry:init(opt)
end

---@Override
function CFileEntry:load_buffers(git_root, left_winid, right_winid)
function CFileEntry:load_buffers(git_root, left_winid, right_winid, callback)
local splits = {
{
winid = left_winid,
bufid = self.left_bufid,
rev = self.left,
pos = "left",
lines = self.get_file_data(self.kind, self.path, "left"),
producer = function()
return self.get_file_data(self.kind, self.path, "left")
end,
null = self.left_null == true,
ready = false,
},
{
winid = right_winid,
bufid = self.right_bufid,
rev = self.right,
pos = "right",
lines = self.get_file_data(self.kind, self.path, "right"),
producer = function()
return self.get_file_data(self.kind, self.path, "right")
end,
null = self.right_null == true,
ready = false,
},
}

local function on_ready_factory(split)
return function()
split.ready = true
local was_ready = self[split.pos .. "_ready"]
self[split.pos .. "_ready"] = true
if splits[1].ready and splits[2].ready then
for _, sp in ipairs(splits) do
if sp.load then
sp.load()
else
api.nvim_win_set_buf(sp.winid, sp.bufid)
end
if not was_ready then
api.nvim_win_call(sp.winid, function()
DiffviewGlobal.emitter:emit("diff_buf_read", sp.bufid)
end)
end
end
CFileEntry._update_windows(left_winid, right_winid)
if type(callback) == "function" then
callback()
end
end
end
end

self.left_ready = self.left_bufid and api.nvim_buf_is_loaded(self.left_bufid)
self.right_ready = self.right_bufid and api.nvim_buf_is_loaded(self.right_bufid)


if not (self.left_ready and self.right_ready) then
utils.no_win_event_call(function()
CFileEntry.load_null_buffer(left_winid)
CFileEntry.load_null_buffer(right_winid)
end)
end

utils.no_win_event_call(function()
for _, split in ipairs(splits) do
local on_ready = on_ready_factory(split)

if not (split.bufid and api.nvim_buf_is_loaded(split.bufid)) then
if split.rev.type == RevType.LOCAL then
if split.null or CFileEntry.should_null(split.rev, self.status, split.pos) then
local bn = CFileEntry._create_buffer(git_root, split.rev, self.path, split.lines, true)
api.nvim_win_set_buf(split.winid, bn)
local bn = CFileEntry._create_buffer(
git_root,
split.rev,
self.path,
split.producer,
true,
on_ready
)
split.bufid = bn
CFileEntry._attach_buffer(split.bufid)
else
api.nvim_win_call(split.winid, function()
vim.cmd("edit " .. vim.fn.fnameescape(self.absolute_path))
split.bufid = api.nvim_get_current_buf()
end)
-- Load local file
split.load = function()
api.nvim_win_call(split.winid, function()
vim.cmd("edit " .. vim.fn.fnameescape(self.absolute_path))
split.bufid = api.nvim_get_current_buf()
CFileEntry._save_winopts(split.bufid, split.winid)
self[split.pos .. "_bufid"] = split.bufid
CFileEntry._attach_buffer(split.bufid)
end)
end
on_ready()
end
elseif
vim.tbl_contains({ RevType.COMMIT, RevType.INDEX, RevType.CUSTOM }, split.rev.type)
then
-- Load custom file data
local bn
if self.oldpath and split.pos == "left" then
bn = CFileEntry._create_buffer(
git_root,
split.rev,
self.oldpath,
split.lines,
split.null
split.producer,
split.null,
on_ready
)
else
bn = CFileEntry._create_buffer(
git_root,
split.rev,
self.path,
split.lines,
split.null or CFileEntry.should_null(split.rev, self.status, split.pos)
split.producer,
split.null or CFileEntry.should_null(split.rev, self.status, split.pos),
on_ready
)
end
table.insert(self.created_bufs, bn)
api.nvim_win_set_buf(split.winid, bn)
split.bufid = bn
api.nvim_win_call(split.winid, function()
vim.cmd("filetype detect")
end)
CFileEntry._attach_buffer(split.bufid)
end

CFileEntry._attach_buffer(split.bufid)
else
api.nvim_win_set_buf(split.winid, split.bufid)
-- Buffer already exists
CFileEntry._attach_buffer(split.bufid)
on_ready()
end
end
end)

self.left_bufid = splits[1].bufid
self.right_bufid = splits[2].bufid

CFileEntry._update_windows(left_winid, right_winid)
vim.cmd("do WinEnter")
end

---@static
---@Override
function CFileEntry._create_buffer(git_root, rev, path, lines, null)
if null or not lines then
function CFileEntry._create_buffer(git_root, rev, path, producer, null, callback)
if null or type(producer) ~= "function" then
callback()
return CFileEntry._get_null_buffer()
end

local bn = api.nvim_create_buf(false, false)
api.nvim_buf_set_lines(bn, 0, -1, false, lines)

local context
if rev.type == RevType.COMMIT then
Expand Down Expand Up @@ -140,6 +197,22 @@ function CFileEntry._create_buffer(git_root, rev, path, lines, null)
end
end

async.run(function()
-- TODO: Update api to be properly async
local result = producer()
vim.schedule(function()
if api.nvim_buf_is_valid(bn) then
vim.bo[bn].modifiable = true
api.nvim_buf_set_lines(bn, 0, -1, false, result or {})
vim.bo[bn].modifiable = false
vim.api.nvim_buf_call(bn, function()
vim.cmd("filetype detect")
end)
callback()
end
end)
end)

return bn
end

Expand Down
7 changes: 7 additions & 0 deletions lua/diffview/bootstrap.lua
Expand Up @@ -31,8 +31,15 @@ _G.DiffviewGlobal = {
---5: LOADING
---10: RENDERING
debug_level = tonumber(os.getenv("DEBUG_DIFFVIEW")) or 0,
---@type EventEmitter
emitter = require("diffview.events").EventEmitter(),
bootstrap_done = true,
bootstrap_ok = true,
}

DiffviewGlobal.emitter:on_any(function(event, args)
local utils = require("diffview.utils")
require("diffview.config").user_emitter:_nore_emit(event, utils.tbl_unpack(args))
end)

return true

0 comments on commit 2c0f5af

Please sign in to comment.