Skip to content

Commit

Permalink
feat(git): Add bcommits_range picker (#2398)
Browse files Browse the repository at this point in the history
* Filter bcommits by selection in visual mode

* Split bcommits_range into new picker

* Add option to run bcommits_range as operator

Starts operator-pending mode and shows commits in the range of lines
covered by the next text object or motion

* Rename range arguments to "first" and "last"

Can't use start/end, since end is an annoying keyword to use in lua
and start/stop doesn't fit as well

* Move operators functionality to new module

* Run bcommits if no range given to bcommits_range

* Make bcommits_range default to current line

Instead of calling bcommits

* Improve documentation of telescope.operators

* Add default value for last_operator

Default to a no-op callback

* Update bcommits_range for detached worktrees

See #2597

* Rename range arguments to "from" and "to"

* Move shared bcommits picker into single function
  • Loading branch information
aaronkollasch authored Jul 22, 2023
1 parent f78d956 commit e7e6492
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 73 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ Built-in functions. Ready to be bound to any key you like.
|-------------------------------------|------------------------------------------------------------------------------------------------------------|
| `builtin.git_commits` | Lists git commits with diff preview, checkout action `<cr>`, reset mixed `<C-r>m`, reset soft `<C-r>s` and reset hard `<C-r>h` |
| `builtin.git_bcommits` | Lists buffer's git commits with diff preview and checks them out on `<cr>` |
| `builtin.git_bcommits_range` | Lists buffer's git commits in a range of lines. Use options `from` and `to` to specify the range. In visual mode, lists commits for the selected lines |
| `builtin.git_branches` | Lists all branches with log preview, checkout action `<cr>`, track action `<C-t>`, rebase action`<C-r>`, create action `<C-a>`, switch action `<C-s>`, delete action `<C-d>` and merge action `<C-y>` |
| `builtin.git_status` | Lists current changes per file with diff preview and add action. (Multi-selection still WIP) |
| `builtin.git_stash` | Lists stash items in current repository with ability to apply them on `<cr>` |
Expand Down
30 changes: 30 additions & 0 deletions doc/telescope.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,36 @@ builtin.git_bcommits({opts}) *telescope.builtin.git_bcommits()*
{"git","log","--pretty=oneline","--abbrev-commit"}


builtin.git_bcommits_range({opts}) *telescope.builtin.git_bcommits_range()*
Lists commits for a range of lines in the current buffer with diff preview
In visual mode, lists commits for the selected lines
With operator mode enabled, lists commits inside the text object/motion
- Default keymaps or your overridden `select_` keys:
- `<cr>`: checks out the currently selected commit
- `<c-v>`: opens a diff in a vertical split
- `<c-x>`: opens a diff in a horizontal split
- `<c-t>`: opens a diff in a new tab


Parameters: ~
{opts} (table) options to pass to the picker

Options: ~
{cwd} (string) specify the path of the repo
{use_git_root} (boolean) if we should use git root as cwd or the cwd
(important for submodule) (default: true)
{current_file} (string) specify the current file that should be used
for bcommits (default: current buffer)
{git_command} (table) command that will be executed. the last
element must be "-L".
{"git","log","--pretty=oneline","--abbrev-commit","--no-patch","-L"}
{from} (number) the first line number in the range
(default: current line)
{to} (number) the last line number in the range
(default: the value of `from`)
{operator} (boolean) select lines in operator-pending mode
(default: false)

builtin.git_branches({opts}) *telescope.builtin.git_branches()*
List branches for current directory, with output from `git log --oneline`
shown in the preview window
Expand Down
192 changes: 119 additions & 73 deletions lua/telescope/builtin/__git.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ local actions = require "telescope.actions"
local action_state = require "telescope.actions.state"
local finders = require "telescope.finders"
local make_entry = require "telescope.make_entry"
local operators = require "telescope.operators"
local pickers = require "telescope.pickers"
local previewers = require "telescope.previewers"
local utils = require "telescope.utils"
Expand Down Expand Up @@ -110,87 +111,132 @@ local get_current_buf_line = function(winnr)
return vim.trim(vim.api.nvim_buf_get_lines(vim.api.nvim_win_get_buf(winnr), lnum - 1, lnum, false)[1])
end

local bcommits_picker = function(opts, title, finder)
return pickers.new(opts, {
prompt_title = title,
finder = finder,
previewer = {
previewers.git_commit_diff_to_parent.new(opts),
previewers.git_commit_diff_to_head.new(opts),
previewers.git_commit_diff_as_was.new(opts),
previewers.git_commit_message.new(opts),
},
sorter = conf.file_sorter(opts),
attach_mappings = function()
actions.select_default:replace(actions.git_checkout_current_buffer)
local transfrom_file = function()
return opts.current_file and Path:new(opts.current_file):make_relative(opts.cwd) or ""
end

local get_buffer_of_orig = function(selection)
local value = selection.value .. ":" .. transfrom_file()
local content = utils.get_os_command_output({ "git", "--no-pager", "show", value }, opts.cwd)

local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, content)
vim.api.nvim_buf_set_name(bufnr, "Original")
return bufnr
end

local vimdiff = function(selection, command)
local ft = vim.bo.filetype
vim.cmd "diffthis"

local bufnr = get_buffer_of_orig(selection)
vim.cmd(string.format("%s %s", command, bufnr))
vim.bo.filetype = ft
vim.cmd "diffthis"

vim.api.nvim_create_autocmd("WinClosed", {
buffer = bufnr,
nested = true,
once = true,
callback = function()
vim.api.nvim_buf_delete(bufnr, { force = true })
end,
})
end

actions.select_vertical:replace(function(prompt_bufnr)
actions.close(prompt_bufnr)
local selection = action_state.get_selected_entry()
vimdiff(selection, "leftabove vert sbuffer")
end)

actions.select_horizontal:replace(function(prompt_bufnr)
actions.close(prompt_bufnr)
local selection = action_state.get_selected_entry()
vimdiff(selection, "belowright sbuffer")
end)

actions.select_tab:replace(function(prompt_bufnr)
actions.close(prompt_bufnr)
local selection = action_state.get_selected_entry()
vim.cmd("tabedit " .. transfrom_file())
vimdiff(selection, "leftabove vert sbuffer")
end)
return true
end,
})
end

git.bcommits = function(opts)
opts.current_line = (opts.current_file == nil) and get_current_buf_line(opts.winnr) or nil
opts.current_file = vim.F.if_nil(opts.current_file, vim.api.nvim_buf_get_name(opts.bufnr))
opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_git_commits(opts))
opts.git_command =
vim.F.if_nil(opts.git_command, git_command({ "log", "--pretty=oneline", "--abbrev-commit", "--follow" }, opts))

pickers
.new(opts, {
prompt_title = "Git BCommits",
finder = finders.new_oneshot_job(
vim.tbl_flatten {
opts.git_command,
opts.current_file,
},
opts
),
previewer = {
previewers.git_commit_diff_to_parent.new(opts),
previewers.git_commit_diff_to_head.new(opts),
previewers.git_commit_diff_as_was.new(opts),
previewers.git_commit_message.new(opts),
},
sorter = conf.file_sorter(opts),
attach_mappings = function()
actions.select_default:replace(actions.git_checkout_current_buffer)
local transfrom_file = function()
return opts.current_file and Path:new(opts.current_file):make_relative(opts.cwd) or ""
end

local get_buffer_of_orig = function(selection)
local value = selection.value .. ":" .. transfrom_file()
local content = utils.get_os_command_output({ "git", "--no-pager", "show", value }, opts.cwd)

local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, content)
vim.api.nvim_buf_set_name(bufnr, "Original")
return bufnr
end

local vimdiff = function(selection, command)
local ft = vim.bo.filetype
vim.cmd "diffthis"

local bufnr = get_buffer_of_orig(selection)
vim.cmd(string.format("%s %s", command, bufnr))
vim.bo.filetype = ft
vim.cmd "diffthis"

vim.api.nvim_create_autocmd("WinClosed", {
buffer = bufnr,
nested = true,
once = true,
callback = function()
vim.api.nvim_buf_delete(bufnr, { force = true })
end,
})
end
local title = "Git BCommits"
local finder = finders.new_oneshot_job(
vim.tbl_flatten {
opts.git_command,
opts.current_file,
},
opts
)
bcommits_picker(opts, title, finder):find()
end

actions.select_vertical:replace(function(prompt_bufnr)
actions.close(prompt_bufnr)
local selection = action_state.get_selected_entry()
vimdiff(selection, "leftabove vert sbuffer")
end)

actions.select_horizontal:replace(function(prompt_bufnr)
actions.close(prompt_bufnr)
local selection = action_state.get_selected_entry()
vimdiff(selection, "belowright sbuffer")
end)

actions.select_tab:replace(function(prompt_bufnr)
actions.close(prompt_bufnr)
local selection = action_state.get_selected_entry()
vim.cmd("tabedit " .. transfrom_file())
vimdiff(selection, "leftabove vert sbuffer")
end)
return true
end,
})
:find()
git.bcommits_range = function(opts)
opts.current_line = (opts.current_file == nil) and get_current_buf_line(opts.winnr) or nil
opts.current_file = vim.F.if_nil(opts.current_file, vim.api.nvim_buf_get_name(opts.bufnr))
opts.entry_maker = vim.F.if_nil(opts.entry_maker, make_entry.gen_from_git_commits(opts))
opts.git_command = vim.F.if_nil(
opts.git_command,
git_command({ "log", "--pretty=oneline", "--abbrev-commit", "--no-patch", "-L" }, opts)
)
local visual = string.find(vim.fn.mode(), "[vV]") ~= nil

local line_number_first = opts.from
local line_number_last = vim.F.if_nil(opts.to, line_number_first)
if visual then
line_number_first = vim.F.if_nil(line_number_first, vim.fn.line "v")
line_number_last = vim.F.if_nil(line_number_last, vim.fn.line ".")
elseif opts.operator then
opts.operator = false
opts.operator_callback = true
operators.run_operator(git.bcommits_range, opts)
return
elseif opts.operator_callback then
line_number_first = vim.fn.line "'["
line_number_last = vim.fn.line "']"
elseif line_number_first == nil then
line_number_first = vim.F.if_nil(line_number_first, vim.fn.line ".")
line_number_last = vim.F.if_nil(line_number_last, vim.fn.line ".")
end
local line_range =
string.format("%d,%d:%s", line_number_first, line_number_last, Path:new(opts.current_file):make_relative(opts.cwd))

local title = "Git BCommits in range"
local finder = finders.new_oneshot_job(
vim.tbl_flatten {
opts.git_command,
line_range,
},
opts
)
bcommits_picker(opts, title, finder):find()
end

git.branches = function(opts)
Expand Down
18 changes: 18 additions & 0 deletions lua/telescope/builtin/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,24 @@ builtin.git_commits = require_on_exported_call("telescope.builtin.__git").commit
---@field git_command table: command that will be executed. {"git","log","--pretty=oneline","--abbrev-commit"}
builtin.git_bcommits = require_on_exported_call("telescope.builtin.__git").bcommits

--- Lists commits for a range of lines in the current buffer with diff preview
--- In visual mode, lists commits for the selected lines
--- With operator mode enabled, lists commits inside the text object/motion
--- - Default keymaps or your overridden `select_` keys:
--- - `<cr>`: checks out the currently selected commit
--- - `<c-v>`: opens a diff in a vertical split
--- - `<c-x>`: opens a diff in a horizontal split
--- - `<c-t>`: opens a diff in a new tab
---@param opts table: options to pass to the picker
---@field cwd string: specify the path of the repo
---@field use_git_root boolean: if we should use git root as cwd or the cwd (important for submodule) (default: true)
---@field current_file string: specify the current file that should be used for bcommits (default: current buffer)
---@field git_command table: command that will be executed. the last element must be "-L". {"git","log","--pretty=oneline","--abbrev-commit","--no-patch","-L"}
---@field from number: the first line number in the range (default: current line)
---@field to number: the last line number in the range (default: the value of `from`)
---@field operator boolean: select lines in operator-pending mode (default: false)
builtin.git_bcommits_range = require_on_exported_call("telescope.builtin.__git").bcommits_range

--- List branches for current directory, with output from `git log --oneline` shown in the preview window
--- - Default keymaps:
--- - `<cr>`: checks out the currently selected branch
Expand Down
23 changes: 23 additions & 0 deletions lua/telescope/operators.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
local operators = {}

local last_operator = { callback = function(_) end, opts = {} }

--- Execute the last saved operator callback and options
operators.operator_callback = function()
last_operator.callback(last_operator.opts)
end

--- Enters operator-pending mode, then executes callback.
--- See `:h map-operator`
---
---@param callback function: the function to call after exiting operator-pending
---@param opts table: options to pass to the callback
operators.run_operator = function(callback, opts)
last_operator = { callback = callback, opts = opts }
vim.o.operatorfunc = "v:lua.require'telescope.operators'.operator_callback"
-- feed g@ to enter operator-pending mode
-- 'i' required for which-key compatibility, etc.
vim.api.nvim_feedkeys("g@", "mi", false)
end

return operators

0 comments on commit e7e6492

Please sign in to comment.