diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c2f36aa..15c03cd 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,8 +7,8 @@ jobs: name: Luacheck runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Luacheck linter - uses: lunarmodules/luacheck@v0 + uses: lunarmodules/luacheck@v1 with: - args: lua/ --globals vim + args: lua/ --globals vim Snacks diff --git a/.github/workflows/spec.yml b/.github/workflows/spec.yml new file mode 100644 index 0000000..ccb17fb --- /dev/null +++ b/.github/workflows/spec.yml @@ -0,0 +1,49 @@ +name: Spec + +on: [pull_request] + +jobs: + unit_tests: + name: unit tests + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-22.04 + url: https://github.com/neovim/neovim/releases/download/v0.8.0/nvim-linux64.tar.gz + manager: sudo apt-get + packages: -y ripgrep + - os: ubuntu-22.04 + url: https://github.com/neovim/neovim/releases/download/v0.9.0/nvim-linux64.tar.gz + manager: sudo apt-get + packages: -y ripgrep + - os: ubuntu-22.04 + url: https://github.com/neovim/neovim/releases/download/v0.10.0/nvim-linux64.tar.gz + manager: sudo apt-get + packages: -y ripgrep + steps: + - uses: actions/checkout@v4 + - run: date +%F > todays-date + - name: Restore from todays cache + uses: actions/cache@v4 + with: + path: _neovim + key: ${{ runner.os }}-${{ matrix.url }}-${{ hashFiles('todays-date') }} + + - name: Prepare + run: | + sudo update-ca-certificates + ${{ matrix.manager }} update + ${{ matrix.manager }} install ${{ matrix.packages }} + test -d _neovim || { + mkdir -p _neovim + curl -sL ${{ matrix.url }} | tar xzf - --strip-components=1 -C "${PWD}/_neovim" + } + - name: Run tests + run: | + export PATH="${PWD}/_neovim/bin:${PATH}" + export VIM="${PWD}/_neovim/share/nvim/runtime" + nvim --version + make test + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9b3324e --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +test: + nvim --headless -u ./lua/spec/minimal_init.lua -c "PlenaryBustedDirectory lua/spec { sequential = true }" -c cquit diff --git a/README.md b/README.md index 4e3aa9b..38ec691 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ _Grep behaviour_: filter on added, updated or removed code (log content: `-G` op - `` show the entire commit for all files in neovim with diff plugin - `` Open the selected commit in the browser - `` copy the commit hash to clipboard -- `` toggle date and author in entry +- `` cycle author, date and both in entry ### 2. search_log_content_file -- Search in file log content @@ -50,7 +50,7 @@ _Grep behaviour_: filter on added, updated or removed code (log content: `-G` op - `` show the entire commit for all files in neovim with diff plugin - `` Open the selected commit in the browser - `` copy the commit hash to clipboard -- `` toggle date and author in entry +- `` cycle author, date and both in entry ### 3. diff_commit_file -- Diff current file with commit @@ -65,7 +65,7 @@ _Grep behaviour_: filter on commit message. - `` show the entire commit for all files in neovim with diff plugin - `` Open the selected commit in the browser - `` copy the commit hash to clipboard -- `` toggle date and author in entry +- `` cycle author, date and both in entry ### 4. diff_commit_line -- Diff current file with selected line history @@ -82,7 +82,7 @@ _Grep behaviour_: filter on commit message. - `` show the entire commit for all files in neovim with diff plugin - `` opens a the selected commit in the browser - `` copy the commit hash to clipboard -- `` toggle date and author in entry +- `` cycle author, date and both in entry ### 5. diff_branch_file -- Diff file with branch @@ -150,9 +150,10 @@ Enable `show_builtin_git_pickers` to additionally show builtin git pickers. -- customize git diff in previewer -- e.g. flags such as { "--raw" } git_diff_flags = {}, + git_log_flags = {}, -- Show builtin git pickers when executing "show_custom_functions" or :AdvancedGitSearch show_builtin_git_pickers = false, - entry_default_author_or_date = "author", -- one of "author" or "date" + entry_default_author_or_date = "author", -- one of "author", "date" or "both" keymaps = { -- following keymaps can be overridden toggle_date_author = "", @@ -196,6 +197,7 @@ Enable `show_builtin_git_pickers` to additionally show builtin git pickers.
Lazy + To complete this snippet, see [Config](#Config) and [Dependencies](#Dependencies). ```lua @@ -254,6 +256,7 @@ use({
Lazy + To complete this snippet, see [Config](#Config) and [Dependencies](#Dependencies). ```lua @@ -295,6 +298,52 @@ use({
+### Snacks + +
+Lazy + +To complete this snippet, see [Config](#Config) and [Dependencies](#Dependencies). Also see [lua/spec/minimal_init_snacks.lua](./lua/spec/minimal_init_snacks.lua) for an up-to-date version. + +```lua +{ + "aaronhallaert/advanced-git-search.nvim", + cmd = { "AdvancedGitSearch" }, + config = function() + require("advanced_git_search.snacks").setup{ + -- Insert Config here + } + end, + dependencies = { + "folke/snacks.nvim" + }, +} +``` + +
+ +
+Packer + +To complete this snippet, see [Config](#Config) and [Dependencies](#Dependencies). + +```lua +use({ + "aaronhallaert/advanced-git-search.nvim", + config = function() + require("advanced_git_search.snacks").setup{ + -- Insert Config here + } + } + end, + requires = { + -- Insert Dependencies here + }, +}) +``` +
+ + ### Prerequisites - git diff --git a/lua/advanced_git_search/commands/find.lua b/lua/advanced_git_search/commands/find.lua index 0994674..3f42ccf 100644 --- a/lua/advanced_git_search/commands/find.lua +++ b/lua/advanced_git_search/commands/find.lua @@ -1,5 +1,7 @@ local file = require("advanced_git_search.utils.file") local git_utils = require("advanced_git_search.utils.git") +local tbl = require("advanced_git_search.utils.table") +local command_utils = require("advanced_git_search.commands.utils") -- Specify shell commands for each finders in table format local M = {} @@ -29,9 +31,9 @@ M.reflog = function() } end ----@param prompt string|nil ----@param author string|nil ----@param bufnr number|nil +---@param prompt? string +---@param author? string +---@param bufnr? number ---@return table M.git_log_content = function(prompt, author, bufnr) local command = { @@ -40,6 +42,8 @@ M.git_log_content = function(prompt, author, bufnr) "--format='%h %as %an _ %s'", } + command = command_utils.format_git_log_command(command) + if author and author ~= "" and author ~= '""' then table.insert(command, "--author=" .. author) end @@ -56,7 +60,7 @@ M.git_log_content = function(prompt, author, bufnr) table.insert(command, filename) end - return vim.tbl_flatten(command) + return tbl.flatten(command) end ---@param prompt string|nil @@ -71,6 +75,8 @@ M.git_log_file = function(prompt, author, bufnr) "--format='%h %as %an _ %s'", } + command = command_utils.format_git_log_command(command) + if author and author ~= "" and author ~= '""' then table.insert(command, "--author=" .. author) end @@ -84,7 +90,7 @@ M.git_log_file = function(prompt, author, bufnr) table.insert(command, "--follow") table.insert(command, filename) - return vim.tbl_flatten(command) + return tbl.flatten(command) end ---@param prompt string|nil @@ -104,6 +110,8 @@ M.git_log_location = function(prompt, author, bufnr, start_line, end_line) "--format='%h %as %an _ %s'", } + command = command_utils.format_git_log_command(command) + if author and author ~= "" and author ~= '""' then table.insert(command, "--author=" .. author) end @@ -114,11 +122,11 @@ M.git_log_location = function(prompt, author, bufnr, start_line, end_line) table.insert(command, "--grep=" .. prompt) end - return vim.tbl_flatten(command) + return tbl.flatten(command) end M.changed_on_branch = function() - return vim.tbl_flatten({ + local command = { "git", "--no-pager", "diff", @@ -128,7 +136,9 @@ M.changed_on_branch = function() "--merge-base", git_utils.base_branch(), "--relative", - }) + } + + return tbl.flatten(command) end return M diff --git a/lua/advanced_git_search/commands/preview.lua b/lua/advanced_git_search/commands/preview.lua index e7fa210..34be985 100644 --- a/lua/advanced_git_search/commands/preview.lua +++ b/lua/advanced_git_search/commands/preview.lua @@ -6,7 +6,7 @@ local M = {} local empty_tree_commit = "4b825dc642cb6eb9a060e54bf8d69288fbee4904" ----@param bufnr number|nil +---@param bufnr? number ---@param first_commit string ---@param second_commit string ---@return string|nil prev_name, string|nil curr_name @@ -41,7 +41,6 @@ M.git_diff_file = function(first_commit, second_commit, bufnr) local base_cmd = { "git", "diff", - "--color=always", } if prev_name ~= nil and curr_name ~= nil then @@ -66,7 +65,6 @@ M.git_diff_base_branch = function(relative_filename) return cmd_utils.format_git_diff_command({ "git", "diff", - "--color=always", "--diff-filter=ACMR", "--cached", "--merge-base", @@ -80,7 +78,7 @@ end --- @param first_commit string --- @param second_commit string --- @param prompt string ---- @param opts table|nil +--- @param opts? {bufnr?: number} M.git_diff_content = function(first_commit, second_commit, prompt, opts) opts = opts or {} @@ -94,7 +92,6 @@ M.git_diff_content = function(first_commit, second_commit, prompt, opts) local base_cmd = { "git", "diff", - "--color=always", } if prev_name == nil and curr_name == nil then @@ -138,7 +135,6 @@ M.git_diff_branch = function(branch, bufnr) return cmd_utils.format_git_diff_command({ "git", "diff", - "--color=always", branch .. ":" .. branch_filename, current_hash .. ":" .. file.git_relative_path(bufnr), }) @@ -146,7 +142,6 @@ M.git_diff_branch = function(branch, bufnr) return cmd_utils.format_git_diff_command({ "git", "diff", - "--color=always", branch, current_hash, "--", diff --git a/lua/advanced_git_search/commands/utils.lua b/lua/advanced_git_search/commands/utils.lua index 9e04c48..774ff9d 100644 --- a/lua/advanced_git_search/commands/utils.lua +++ b/lua/advanced_git_search/commands/utils.lua @@ -36,6 +36,39 @@ M.format_git_diff_command = function(command, git_flags_ix, git_diff_flags_ix) return command end +--- @param command table +--- @param git_flags_ix number|nil +--- @param git_log_flags_ix number|nil +--- @return table Command with configured git log flags +M.format_git_log_command = function(command, git_flags_ix, git_log_flags_ix) + git_flags_ix = git_flags_ix or 2 + git_log_flags_ix = git_log_flags_ix or 3 + + local git_log_flags = config.git_log_flags() + local git_flags = config.git_flags() + + if git_flags_ix > git_log_flags_ix then + vim.notify( + "git_flags must be inserted before git_log_flags", + vim.log.levels.ERROR + ) + end + + if git_log_flags ~= nil and #git_log_flags > 0 then + for i, flag in ipairs(git_log_flags) do + table.insert(command, git_log_flags_ix + i - 1, flag) + end + end + + if git_flags ~= nil and #git_flags > 0 then + for i, flag in ipairs(git_flags) do + table.insert(command, git_flags_ix + i - 1, flag) + end + end + + return command +end + M.split_query_from_author = function(query) local author = nil local prompt = nil diff --git a/lua/advanced_git_search/fzf/init.lua b/lua/advanced_git_search/fzf/init.lua index 2a81be4..1a4e55f 100644 --- a/lua/advanced_git_search/fzf/init.lua +++ b/lua/advanced_git_search/fzf/init.lua @@ -3,6 +3,9 @@ local config = require("advanced_git_search.utils.config") local setup = require("advanced_git_search.utils.setup") M.setup = function(opts) + opts.git_diff_flags = + vim.tbl_extend("force", opts.git_diff_flags or {}, { "--color=always" }) + config.setup(opts) local pickers = require("advanced_git_search.fzf.pickers") diff --git a/lua/advanced_git_search/fzf/mappings/init.lua b/lua/advanced_git_search/fzf/mappings/init.lua index 011a2c7..3c3e8d8 100644 --- a/lua/advanced_git_search/fzf/mappings/init.lua +++ b/lua/advanced_git_search/fzf/mappings/init.lua @@ -6,7 +6,7 @@ local file_utils = require("advanced_git_search.utils.file") local git_utils = require("advanced_git_search.utils.git") local config = require("advanced_git_search.utils.config") ----FZF: Toggle date or author in picker entry +---FZF: Cycle author/date/both in picker entry ---@return table M.toggle_entry_value = function() return { diff --git a/lua/advanced_git_search/fzf/pickers/utils.lua b/lua/advanced_git_search/fzf/pickers/utils.lua index 2b76ce6..157f1e4 100644 --- a/lua/advanced_git_search/fzf/pickers/utils.lua +++ b/lua/advanced_git_search/fzf/pickers/utils.lua @@ -4,12 +4,24 @@ local config = require("advanced_git_search.utils.config") local M = {} -local show_date_instead_of_author = ( - config.entry_default_author_or_date() == "date" -) +local display_mode + +local function get_display_mode() + if not display_mode then + display_mode = config.entry_default_author_or_date() + end + return display_mode +end M.toggle_show_date_instead_of_author = function() - show_date_instead_of_author = not show_date_instead_of_author + local mode = get_display_mode() + if mode == "author" then + display_mode = "date" + elseif mode == "date" then + display_mode = "both" + else + display_mode = "author" + end end M.make_entry = function(entry) @@ -36,11 +48,16 @@ M.make_entry = function(entry) end -- NOTE: make sure the first value is the commit hash + local mode = get_display_mode() local final_entry - if show_date_instead_of_author then + if mode == "date" then final_entry = color.magenta(hash) .. color.cyan(" " .. date) .. color.yellow(message) + elseif mode == "both" then + final_entry = color.magenta(hash) + .. color.cyan(" " .. date .. " @" .. author) + .. color.yellow(message) else final_entry = color.magenta(hash) .. color.cyan(" @" .. author) diff --git a/lua/advanced_git_search/global_picker.lua b/lua/advanced_git_search/global_picker.lua index 696fd83..9d57e1a 100644 --- a/lua/advanced_git_search/global_picker.lua +++ b/lua/advanced_git_search/global_picker.lua @@ -2,7 +2,7 @@ local M = {} local config = require("advanced_git_search.utils.config") ----@param finder_plugin "telescope"|"fzf_lua" +---@param finder_plugin "telescope"|"fzf_lua"|"snacks" ---@return table local custom_git_functions = function(finder_plugin) local pickers_table = {} @@ -10,6 +10,8 @@ local custom_git_functions = function(finder_plugin) pickers_table = require("advanced_git_search.telescope.pickers") elseif finder_plugin == "fzf_lua" then pickers_table = require("advanced_git_search.fzf.pickers") + elseif finder_plugin == "snacks" then + pickers_table = require("advanced_git_search.snacks.pickers") end return { @@ -44,7 +46,7 @@ local custom_git_functions = function(finder_plugin) } end ----@param finder_plugin "telescope"|"fzf_lua" +---@param finder_plugin "telescope"|"fzf_lua"|"snacks" ---@return table local builtin_git_functions = function(finder_plugin) local builtin_functions = {} @@ -52,12 +54,20 @@ local builtin_git_functions = function(finder_plugin) builtin_functions = require("telescope.builtin") elseif finder_plugin == "fzf_lua" then builtin_functions = require("fzf-lua") + elseif finder_plugin == "snacks" then + builtin_functions = require("snacks").picker end return { { value = "Git commits [builtin]", - func = builtin_functions.git_commits, + func = function() + if finder_plugin == "snacks" then + builtin_functions.git_log() + else + builtin_functions.git_commits() + end + end, }, { value = "Git branches [builtin]", @@ -82,7 +92,7 @@ local function map_item(git_functions_table, f) return t end ----@param finder_plugin "telescope"|"fzf_lua" +---@param finder_plugin "telescope"|"fzf_lua"|"snacks" ---@return table local git_functions_table = function(finder_plugin) local t = {} @@ -100,7 +110,7 @@ local git_functions_table = function(finder_plugin) end ---@param value any ----@param finder_plugin "telescope"|"fzf_lua" +---@param finder_plugin "telescope"|"fzf_lua"|"snacks" M.execute_git_function = function(value, finder_plugin) for _, v in pairs(git_functions_table(finder_plugin)) do if v["value"] == value then @@ -110,7 +120,7 @@ M.execute_git_function = function(value, finder_plugin) end end ----@param finder_plugin "telescope"|"fzf_lua" +---@param finder_plugin "telescope"|"fzf_lua"|"snacks" ---@return table M.keys = function(finder_plugin) return map_item(git_functions_table(finder_plugin), function(v) diff --git a/lua/advanced_git_search/snacks/actions.lua b/lua/advanced_git_search/snacks/actions.lua new file mode 100644 index 0000000..df30b15 --- /dev/null +++ b/lua/advanced_git_search/snacks/actions.lua @@ -0,0 +1,68 @@ +local global_actions = require("advanced_git_search.actions") +local git_utils = require("advanced_git_search.utils.git") +local config = require("advanced_git_search.utils.config") +local file_utils = require("advanced_git_search.utils.file") + +---@class AdvancedGitSearch.Snacks.Mapping +---@field key string +---@field action fun(picker: snacks.picker, item: snacks.picker.finder.Item) + +local M = {} + +---@type AdvancedGitSearch.Snacks.Mapping +M.open_commit_in_browser = { + key = config.get_keymap("open_commit_in_browser") or "", + action = function(_, item) + global_actions.open_in_browser(item.commit) + end, +} + +---@param bufnr number +---@return AdvancedGitSearch.Snacks.Mapping +M.open_diff_buffer_with_selected_commit = function(bufnr) + return { + key = "", + action = function(_, item) + global_actions.open_diff_view( + item.commit, + file_utils.git_relative_path(bufnr) + ) + end, + } ---@type AdvancedGitSearch.Snacks.Mapping +end + +---@param bufnr number +---@return AdvancedGitSearch.Snacks.Mapping +M.diff_buffer_with_branch = function(bufnr) + return { + key = "", + action = function(_, item) + local branch = item.text + global_actions.open_diff_view( + branch, + git_utils.file_name_on_commit( + branch, + file_utils.git_relative_path(bufnr) + ) + ) + end, + } +end + +---@type AdvancedGitSearch.Snacks.Mapping +M.show_entire_commit = { + key = config.get_keymap("show_entire_commit") or "", + action = function(_, item) + global_actions.open_commit(item.commit) + end, +} + +---@type AdvancedGitSearch.Snacks.Mapping +M.copy_commit_hash = { + key = config.get_keymap("copy_commit_hash") or "", + action = function(_, item) + global_actions.copy_to_clipboard(item.commit) + end, +} + +return M diff --git a/lua/advanced_git_search/snacks/finders/init.lua b/lua/advanced_git_search/snacks/finders/init.lua new file mode 100644 index 0000000..e584c3f --- /dev/null +++ b/lua/advanced_git_search/snacks/finders/init.lua @@ -0,0 +1,95 @@ +local finder_commands = require("advanced_git_search.commands.find") +local snack_transformers = require("advanced_git_search.snacks.transformers") +local prompt_utils = require("advanced_git_search.utils.prompt") +local utils = require("advanced_git_search.utils") + +local M = {} + +---@param f_opts? {bufnr?: number} +---@return snacks.picker.finder +M.git_log_content = function(f_opts) + f_opts = f_opts or { bufnr = nil } + + return function(opts, ctx) + local prompt = prompt_utils.parse(ctx.picker.input:get()) + + local git_log = finder_commands.git_log_content( + string.format("%s", utils.escape_term(prompt.query)), + prompt.author, + f_opts.bufnr + ) + local args = { unpack(git_log, 2) } + return require("snacks.picker.source.proc").proc({ + opts, + { + cmd = git_log[1], + args = args, + transform = snack_transformers.git_log(), + }, + }, ctx) + end +end + +---@param bufnr number +---@param s_start number +---@param s_end number +---@return snacks.picker.finder +M.git_log_location = function(bufnr, s_start, s_end) + return function(opts, ctx) + local prompt = prompt_utils.parse(ctx.picker.input:get()) + + local git_log = finder_commands.git_log_location( + string.format("%s", utils.escape_term(prompt.query)), + prompt.author, + bufnr, + s_start, + s_end + ) + return require("snacks.picker.source.proc").proc({ + opts, + { + cmd = git_log[1], + args = { unpack(git_log, 2) }, + transform = snack_transformers.git_log(), + }, + }, ctx) + end +end + +---@param bufnr number +---@return snacks.picker.finder +M.git_log_file = function(bufnr) + return function(opts, ctx) + local prompt = prompt_utils.parse(ctx.picker.input:get()) + + local git_log = finder_commands.git_log_file( + string.format("%s", utils.escape_term(prompt.query)), + prompt.author, + bufnr + ) + return require("snacks.picker.source.proc").proc({ + opts, + { + cmd = git_log[1], + args = { unpack(git_log, 2) }, + transform = snack_transformers.git_log(), + }, + }, ctx) + end +end + +---@return snacks.picker.finder +M.git_branches = function() + return function(opts, ctx) + local git_branches = finder_commands.git_branches() + return require("snacks.picker.source.proc").proc({ + opts, + { + cmd = git_branches[1], + args = { unpack(git_branches, 2) }, + }, + }, ctx) + end +end + +return M diff --git a/lua/advanced_git_search/snacks/formatters.lua b/lua/advanced_git_search/snacks/formatters.lua new file mode 100644 index 0000000..8b4998b --- /dev/null +++ b/lua/advanced_git_search/snacks/formatters.lua @@ -0,0 +1,55 @@ +local M = {} +local config = require("advanced_git_search.utils.config") + +---@return snacks.picker.format +M.git_log = function() + return function(item, picker) + local a = Snacks.picker.util.align + local mode = config.entry_default_author_or_date() + + local ret = {} ---@type snacks.picker.Highlight[] + ret[#ret + 1] = { picker.opts.icons.git.commit, "SnacksPickerGitCommit" } + ret[#ret + 1] = { + a(item.commit, 8, { truncate = true }), + "SnacksPickerGitCommit", + } + ret[#ret + 1] = { " " } + if mode == "date" then + ret[#ret + 1] = { + a(item.date, 10, { truncate = true }), + "SnacksPickerGitDate", + } + ret[#ret + 1] = { " " } + elseif mode == "both" then + ret[#ret + 1] = { + a(item.date, 10, { truncate = true }), + "SnacksPickerGitDate", + } + ret[#ret + 1] = { " " } + ret[#ret + 1] = { + a(item.author, 15, { truncate = true }), + "SnacksPickerGitDate", + } + ret[#ret + 1] = { " " } + else + ret[#ret + 1] = { + a(item.author, 15, { truncate = true }), + "SnacksPickerGitDate", + } + ret[#ret + 1] = { " " } + end + ret[#ret + 1] = { item.msg, "SnacksPickerGitMsg" } + return ret + end +end + +---@return snacks.picker.format +M.git_branches = function() + return function(item, _) + local ret = {} ---@type snacks.picker.Highlight[] + ret[#ret + 1] = { item.text, "SnacksPickerGitCommit" } + return ret + end +end + +return M diff --git a/lua/advanced_git_search/snacks/init.lua b/lua/advanced_git_search/snacks/init.lua new file mode 100644 index 0000000..832bddb --- /dev/null +++ b/lua/advanced_git_search/snacks/init.lua @@ -0,0 +1,12 @@ +local M = {} +local config = require("advanced_git_search.utils.config") +local setup = require("advanced_git_search.utils.setup") + +M.setup = function(opts) + config.setup(opts) + + local pickers = require("advanced_git_search.snacks.pickers") + setup.setup_user_command(pickers) +end + +return M diff --git a/lua/advanced_git_search/snacks/pickers/init.lua b/lua/advanced_git_search/snacks/pickers/init.lua new file mode 100644 index 0000000..5683126 --- /dev/null +++ b/lua/advanced_git_search/snacks/pickers/init.lua @@ -0,0 +1,157 @@ +local Snacks = require("snacks") +local snack_previewers = require("advanced_git_search.snacks.previewers") +local snack_formatters = require("advanced_git_search.snacks.formatters") +local snack_finders = require("advanced_git_search.snacks.finders") +local global_picker = require("advanced_git_search.global_picker") +local snacks_actions = require("advanced_git_search.snacks.actions") + +local M = {} + +---@return table +local commit_actions = function() + return { + win = { + input = { + keys = { + [snacks_actions.open_commit_in_browser.key] = { + "open_commit_in_browser", + mode = { "n", "i" }, + }, + [snacks_actions.copy_commit_hash.key] = { + "copy_commit_hash", + mode = { "n", "i" }, + }, + [snacks_actions.show_entire_commit.key] = { + "show_entire_commit", + mode = { "n", "i" }, + }, + }, + }, + }, + actions = { + open_commit_in_browser = snacks_actions.open_commit_in_browser.action, + copy_commit_hash = snacks_actions.copy_commit_hash.action, + show_entire_commit = snacks_actions.show_entire_commit.action, + confirm = snacks_actions.open_diff_buffer_with_selected_commit( + vim.fn.bufnr() + ).action, + }, + } +end + +M.search_log_content = function() + Snacks.picker.pick(nil, { + finder = snack_finders.git_log_content(), + format = snack_formatters.git_log(), + preview = snack_previewers.git_diff_content(), + live = true, + actions = commit_actions().actions, + win = commit_actions().win, + }) +end + +M.search_log_content_file = function() + local bufnr = vim.fn.bufnr() + + Snacks.picker.pick(nil, { + finder = snack_finders.git_log_content({ bufnr = bufnr }), + format = snack_formatters.git_log(), + preview = snack_previewers.git_diff_content({ bufnr = bufnr }), + actions = commit_actions().actions, + win = commit_actions().win, + live = true, + }) +end + +M.diff_commit_line = function() + local bufnr = vim.fn.bufnr() + local s_start = vim.fn.getpos("'<")[2] + local s_end = vim.fn.getpos("'>")[2] + + if s_start == 0 or s_end == 0 then + vim.notify( + "No visual selection", + vim.log.levels.WARN, + { title = "Advanced Git Search" } + ) + return + end + + Snacks.picker.pick(nil, { + finder = snack_finders.git_log_location(bufnr, s_start, s_end), + format = snack_formatters.git_log(), + preview = snack_previewers.git_diff_file({ bufnr = bufnr }), + actions = commit_actions().actions, + win = commit_actions().win, + live = true, + }) +end + +M.diff_commit_file = function() + local bufnr = vim.fn.bufnr() + + Snacks.picker.pick(nil, { + finder = snack_finders.git_log_file(bufnr), + format = snack_formatters.git_log(), + preview = snack_previewers.git_diff_file({ bufnr = bufnr }), + actions = commit_actions().actions, + win = commit_actions().win, + live = true, + }) +end + +M.diff_branch_file = function() + local bufnr = vim.fn.bufnr() + + Snacks.picker.pick(nil, { + finder = snack_finders.git_branches(), + format = snack_formatters.git_branches(), + preview = snack_previewers.git_diff_branch({ bufnr = bufnr }), + actions = { + confirm = snacks_actions.diff_buffer_with_branch(bufnr).action, + }, + }) +end + +M.show_custom_functions = function() + local keys = global_picker.keys("snacks") + + local items = {} ---@type snacks.picker.finder.Item[] + + for _, key in ipairs(keys) do + table.insert(items, { text = key }) + end + + -- Snacks.picker. + Snacks.picker.pick(nil, { + items = items, + layout = { + preview = false, + layout = { + height = math.floor( + math.min(vim.o.lines * 0.8 - 10, #items + 2) + 0.5 + ), + }, + }, + win = { + input = { + keys = { + [""] = { "open_picker", mode = { "n", "i" } }, + }, + }, + }, + actions = { + open_picker = function(picker, item) + picker:close() + global_picker.execute_git_function(item.text, "snacks") + end, + }, + format = function(item) + local ret = {} ---@type snacks.picker.Highlight[] + ret[#ret + 1] = { item.text, "SnacksPickerGitMsg" } + return ret + end, + }) +end + +return M diff --git a/lua/advanced_git_search/snacks/previewers/init.lua b/lua/advanced_git_search/snacks/previewers/init.lua new file mode 100644 index 0000000..6e8e961 --- /dev/null +++ b/lua/advanced_git_search/snacks/previewers/init.lua @@ -0,0 +1,96 @@ +local utils = require("advanced_git_search.utils") +local git_utils = require("advanced_git_search.utils.git") +local preview_commands = require("advanced_git_search.commands.preview") +local ns = vim.api.nvim_create_namespace("ags_snacks_preview") + +local M = {} + +---@param opts? {bufnr?: number} +---@return snacks.picker.preview +M.git_diff_content = function(opts) + opts = opts or { bufnr = nil } + + return function(preview_ctx) + local prompt = preview_ctx.picker.input:get() + + local search_content, _ = prompt:match("^(.*) @(.*)") + + local prev_commit = + git_utils.previous_commit_hash(preview_ctx.item.commit) + local git_log_preview = preview_commands.git_diff_content( + prev_commit, + preview_ctx.item.commit, + string.format("%s", utils.escape_term(search_content)), + { bufnr = opts.bufnr } + ) + + require("snacks.picker.preview").cmd(git_log_preview, preview_ctx, { + ft = "diff", + add = function(text, l) + vim.api.nvim_buf_set_lines( + preview_ctx.buf, + l - 1, + l, + false, + { text } + ) + local col = string.find(text, utils.escape_chars(prompt)) + if prompt and prompt ~= "" and prompt ~= '""' and col then + vim.api.nvim_buf_set_extmark( + preview_ctx.buf, + ns, + l - 1, + col - 1, + { + end_row = l - 1, + end_col = col - 1 + string.len(prompt), + hl_group = "SnacksPickerSearch", + } + ) + end + end, + }) + end +end + +---@param opts? {bufnr?: number} +---@return snacks.picker.preview +M.git_diff_file = function(opts) + opts = opts or { bufnr = nil } + + return function(preview_ctx) + local prev_commit = + git_utils.previous_commit_hash(preview_ctx.item.commit) + local git_diff_file_preview = preview_commands.git_diff_file( + prev_commit, + preview_ctx.item.commit, + opts.bufnr + ) + + require("snacks.picker.preview").cmd( + git_diff_file_preview, + preview_ctx, + { + ft = "diff", + } + ) + end +end + +---@param opts? {bufnr?: number} +---@return snacks.picker.preview +M.git_diff_branch = function(opts) + opts = opts or { bufnr = nil } + + return function(preview_ctx) + require("snacks.picker.preview").cmd( + preview_commands.git_diff_branch(preview_ctx.item.text, opts.bufnr), + preview_ctx, + { + ft = "diff", + } + ) + end +end + +return M diff --git a/lua/advanced_git_search/snacks/transformers.lua b/lua/advanced_git_search/snacks/transformers.lua new file mode 100644 index 0000000..24f5800 --- /dev/null +++ b/lua/advanced_git_search/snacks/transformers.lua @@ -0,0 +1,23 @@ +local M = {} + +---@return snacks.picker.transform +M.git_log = function() + return function(item) + -- "--format='%h %as %an _ %s'", + + local commit, date, author, subject = + item.text:match("^'(%S+) (%S+) (%S.-%S) _ (.+)'$") + + if not commit then + vim.print(("failed to parse log item:\n%q"):format(item.text)) + return false + end + item.commit = commit + item.msg = subject + item.date = date + item.author = author + return item + end +end + +return M diff --git a/lua/advanced_git_search/telescope/finders/init.lua b/lua/advanced_git_search/telescope/finders/init.lua index 592b025..7949b26 100644 --- a/lua/advanced_git_search/telescope/finders/init.lua +++ b/lua/advanced_git_search/telescope/finders/init.lua @@ -8,7 +8,7 @@ local utils = require("advanced_git_search.utils") local M = {} M.git_branches_finder = function() - return finders.new_oneshot_job(finder_commands.git_branches()) + return finders.new_oneshot_job(finder_commands.git_branches(), {}) end --- Returns all commits that changed the visual selection in the buffer @@ -56,7 +56,7 @@ M.git_log_file_finder = function(bufnr) end M.changed_files_on_current_branch_finder = function() - return finders.new_oneshot_job(finder_commands.changed_on_branch()) + return finders.new_oneshot_job(finder_commands.changed_on_branch(), {}) end return M diff --git a/lua/advanced_git_search/telescope/finders/utils.lua b/lua/advanced_git_search/telescope/finders/utils.lua index 9a5ab93..3961a85 100644 --- a/lua/advanced_git_search/telescope/finders/utils.lua +++ b/lua/advanced_git_search/telescope/finders/utils.lua @@ -2,15 +2,27 @@ local utils = require("advanced_git_search.utils") local entry_display = require("telescope.pickers.entry_display") local config = require("advanced_git_search.utils.config") -local show_date_instead_of_author = ( - config.entry_default_author_or_date() == "date" -) +local display_mode + +local function get_display_mode() + if not display_mode then + display_mode = config.entry_default_author_or_date() + end + return display_mode +end local M = {} local last_prompt = nil M.toggle_show_date_instead_of_author = function() - show_date_instead_of_author = not show_date_instead_of_author + local mode = get_display_mode() + if mode == "author" then + display_mode = "date" + elseif mode == "date" then + display_mode = "both" + else + display_mode = "author" + end end --- Parse "--format=%C(auto)%h %as %C(green)%an _ %Creset %s" to table @@ -37,42 +49,62 @@ M.git_log_entry_maker = function(entry) end end - local second_width - if show_date_instead_of_author then - second_width = 10 - else - second_width = #author - end - - local displayer = entry_display.create({ - separator = " ", - items = { - { width = 7 }, - { width = second_width }, - { remaining = true }, - }, - }) + local displayer + local make_display + local mode = get_display_mode() - local make_display = function(display_entry) - if show_date_instead_of_author then + if mode == "both" then + displayer = entry_display.create({ + separator = " ", + items = { + { width = 7 }, + { width = 10 }, + { width = #author }, + { remaining = true }, + }, + }) + make_display = function(display_entry) return displayer({ { display_entry.opts.commit_hash, "TelescopeResultsIdentifier", }, { display_entry.opts.date, "TelescopeResultsVariable" }, - { display_entry.opts.message, "TelescopeResultsConstant" }, - }) - else - return displayer({ - { - display_entry.opts.commit_hash, - "TelescopeResultsIdentifier", - }, { display_entry.opts.author, "TelescopeResultsVariable" }, { display_entry.opts.message, "TelescopeResultsConstant" }, }) end + else + local second_width = mode == "date" and 10 or #author + displayer = entry_display.create({ + separator = " ", + items = { + { width = 7 }, + { width = second_width }, + { remaining = true }, + }, + }) + make_display = function(display_entry) + if mode == "date" then + return displayer({ + { + display_entry.opts.commit_hash, + "TelescopeResultsIdentifier", + }, + { display_entry.opts.date, "TelescopeResultsVariable" }, + { display_entry.opts.message, "TelescopeResultsConstant" }, + }) + else + return displayer({ + { + display_entry.opts.commit_hash, + "TelescopeResultsIdentifier", + }, + { display_entry.opts.author, "TelescopeResultsVariable" }, + { display_entry.opts.message, "TelescopeResultsConstant" }, + }) + end + end end return { diff --git a/lua/advanced_git_search/telescope/mappings/init.lua b/lua/advanced_git_search/telescope/mappings/init.lua index db554b1..32ddf35 100644 --- a/lua/advanced_git_search/telescope/mappings/init.lua +++ b/lua/advanced_git_search/telescope/mappings/init.lua @@ -19,13 +19,14 @@ M.omnimap = omnimap -- create a local function and assign it to a map to get which_key description ------------------------------------------------------------------------------- -local toggle_date_author = function(prompt_bufnr) +-- Telescope: Cycle author/date/both in picker entry +local cycle_date_author = function(prompt_bufnr) require("advanced_git_search.telescope.finders.utils").toggle_show_date_instead_of_author() action_state.get_current_picker(prompt_bufnr):refresh() end M.toggle_entry_value = function(map) - omnimap(map, config.get_keymap("toggle_date_author"), toggle_date_author) + omnimap(map, config.get_keymap("toggle_date_author"), cycle_date_author) end ------------------------------------------------------------------------------- diff --git a/lua/advanced_git_search/telescope/pickers/init.lua b/lua/advanced_git_search/telescope/pickers/init.lua index a594fa4..b4a497b 100644 --- a/lua/advanced_git_search/telescope/pickers/init.lua +++ b/lua/advanced_git_search/telescope/pickers/init.lua @@ -22,15 +22,18 @@ M.changed_on_branch = function() local theme_opts = config.telescope_theme("changed_on_branch") pickers - .new(vim.tbl_extend("force", { - results_title = "Modified " - .. git_utils.base_branch() - .. " -> " - .. git_utils.current_branch(), - sorter = sorters.get_fuzzy_file(), - finder = telescope_ags_finders.changed_files_on_current_branch_finder(), - previewer = telescope_ags_previewers.changed_files_on_current_branch_previewer(), - }, theme_opts)) + .new( + vim.tbl_extend("force", { + results_title = "Modified " + .. git_utils.base_branch() + .. " -> " + .. git_utils.current_branch(), + sorter = sorters.get_fuzzy_file(), + finder = telescope_ags_finders.changed_files_on_current_branch_finder(), + previewer = telescope_ags_previewers.changed_files_on_current_branch_previewer(), + }, theme_opts), + {} + ) :find() end @@ -43,21 +46,24 @@ M.diff_branch_file = function() local theme_opts = config.telescope_theme("diff_branch_file") pickers - .new(vim.tbl_extend("force", { - results_title = "Local branches :: *" .. current_branch, - prompt_title = "Branch name", - finder = telescope_ags_finders.git_branches_finder(), - sorter = sorters.get_fuzzy_file(), - previewer = telescope_ags_previewers.git_diff_branch_file_previewer( - bufnr - ), - attach_mappings = function(_, map) - telescope_ags_mappings.open_diff_view_current_file_selected_branch( - map - ) - return true - end, - }, theme_opts)) + .new( + vim.tbl_extend("force", { + results_title = "Local branches :: *" .. current_branch, + prompt_title = "Branch name", + finder = telescope_ags_finders.git_branches_finder(), + sorter = sorters.get_fuzzy_file(), + previewer = telescope_ags_previewers.git_diff_branch_file_previewer( + bufnr + ), + attach_mappings = function(_, map) + telescope_ags_mappings.open_diff_view_current_file_selected_branch( + map + ) + return true + end, + }, theme_opts), + {} + ) :find() end @@ -82,29 +88,33 @@ M.diff_commit_line = function() -- git log -L741,751:'app/models/patients/patient.rb'\ -- --format='%C(auto)%h \t %as \t %C(green)%an _ %Creset %s' pickers - .new(vim.tbl_extend("force", { - results_title = "Commits that affected the selected lines", - prompt_title = "Commit message", - finder = telescope_ags_finders.git_log_location_finder( - bufnr, - s_start, - s_end - ), - previewer = telescope_ags_previewers.git_diff_commit_file_previewer( - bufnr - ), - sorter = sorters.highlighter_only(), - attach_mappings = function(_, map) - telescope_ags_mappings.open_diff_view_current_file_selected_commit( - map - ) - telescope_ags_mappings.open_selected_commit_in_browser(map) - telescope_ags_mappings.copy_commit_hash_to_clipboard(map) - telescope_ags_mappings.show_entire_commit(map) - telescope_ags_mappings.toggle_entry_value(map) - return true - end, - }, theme_opts)) + .new( + vim.tbl_extend("force", { + results_title = "Commits that affected the selected lines", + prompt_title = "Commit message", + finder = telescope_ags_finders.git_log_location_finder( + bufnr, + s_start, + s_end + ), + previewer = telescope_ags_previewers.git_diff_commit_file_previewer({ + bufnr = bufnr, + preview_query = vim.fn.getline("."), + }), + sorter = sorters.highlighter_only(), + attach_mappings = function(_, map) + telescope_ags_mappings.open_diff_view_current_file_selected_commit( + map + ) + telescope_ags_mappings.open_selected_commit_in_browser(map) + telescope_ags_mappings.copy_commit_hash_to_clipboard(map) + telescope_ags_mappings.show_entire_commit(map) + telescope_ags_mappings.toggle_entry_value(map) + return true + end, + }, theme_opts), + {} + ) :find() end @@ -117,22 +127,25 @@ M.search_log_content = function() -- git log -L741,751:'app/models/patients/patient.rb' \ -- --format='%C(auto)%h \t %as \t %C(green)%an _ %Creset %s' pickers - .new(vim.tbl_extend("force", { - results_title = "Commits", - prompt_title = "Git log content (added, removed or updated text)", - finder = telescope_ags_finders.git_log_content_finder({}), - previewer = telescope_ags_previewers.git_diff_content_previewer(), - attach_mappings = function(_, map) - telescope_ags_mappings.open_diff_view_current_file_selected_commit( - map - ) - telescope_ags_mappings.open_selected_commit_in_browser(map) - telescope_ags_mappings.copy_commit_hash_to_clipboard(map) - telescope_ags_mappings.show_entire_commit(map) - telescope_ags_mappings.toggle_entry_value(map) - return true - end, - }, theme_opts)) + .new( + vim.tbl_extend("force", { + results_title = "Commits", + prompt_title = "Git log content (added, removed or updated text)", + finder = telescope_ags_finders.git_log_content_finder({}), + previewer = telescope_ags_previewers.git_diff_content_previewer(), + attach_mappings = function(_, map) + telescope_ags_mappings.open_diff_view_current_file_selected_commit( + map + ) + telescope_ags_mappings.open_selected_commit_in_browser(map) + telescope_ags_mappings.copy_commit_hash_to_clipboard(map) + telescope_ags_mappings.show_entire_commit(map) + telescope_ags_mappings.toggle_entry_value(map) + return true + end, + }, theme_opts), + {} + ) :find() end @@ -146,27 +159,30 @@ M.search_log_content_file = function() -- git log -L741,751:'app/models/patients/patient.rb' \ -- --format='%C(auto)%h \t %as \t %C(green)%an _ %Creset %s' pickers - .new(vim.tbl_extend("force", { - results_title = "Commits", - prompt_title = "Git log content (added, removed or updated text in this file)", - finder = telescope_ags_finders.git_log_content_finder({ - bufnr = vim.fn.bufnr(), - }), - previewer = telescope_ags_previewers.git_diff_content_previewer({ - bufnr = vim.fn.bufnr(), - }), - attach_mappings = function(_, map) - telescope_ags_mappings.open_diff_view_current_file_selected_commit( - map - ) - telescope_ags_mappings.open_selected_commit_in_browser(map) - telescope_ags_mappings.copy_commit_hash_to_clipboard(map) - telescope_ags_mappings.show_entire_commit(map) - telescope_ags_mappings.toggle_entry_value(map) - - return true - end, - }, theme_opts)) + .new( + vim.tbl_extend("force", { + results_title = "Commits", + prompt_title = "Git log content (added, removed or updated text in this file)", + finder = telescope_ags_finders.git_log_content_finder({ + bufnr = vim.fn.bufnr(), + }), + previewer = telescope_ags_previewers.git_diff_content_previewer({ + bufnr = vim.fn.bufnr(), + }), + attach_mappings = function(_, map) + telescope_ags_mappings.open_diff_view_current_file_selected_commit( + map + ) + telescope_ags_mappings.open_selected_commit_in_browser(map) + telescope_ags_mappings.copy_commit_hash_to_clipboard(map) + telescope_ags_mappings.show_entire_commit(map) + telescope_ags_mappings.toggle_entry_value(map) + + return true + end, + }, theme_opts), + {} + ) :find() end @@ -177,26 +193,29 @@ M.diff_commit_file = function() local theme_opts = config.telescope_theme("diff_commit_file") pickers - .new(vim.tbl_extend("force", { - results_title = "Commits that affected this file (renamed files included)", - prompt_title = "Commit message", - finder = telescope_ags_finders.git_log_file_finder(bufnr), - previewer = telescope_ags_previewers.git_diff_commit_file_previewer( - bufnr - ), - sorter = sorters.highlighter_only(), - attach_mappings = function(_, map) - telescope_ags_mappings.open_diff_view_current_file_selected_commit( - map - ) - telescope_ags_mappings.show_entire_commit(map) - telescope_ags_mappings.toggle_entry_value(map) - telescope_ags_mappings.open_selected_commit_in_browser(map) - telescope_ags_mappings.copy_commit_hash_to_clipboard(map) - - return true - end, - }, theme_opts)) + .new( + vim.tbl_extend("force", { + results_title = "Commits that affected this file (renamed files included)", + prompt_title = "Commit message", + finder = telescope_ags_finders.git_log_file_finder(bufnr), + previewer = telescope_ags_previewers.git_diff_commit_file_previewer({ + bufnr = bufnr, + }), + sorter = sorters.highlighter_only(), + attach_mappings = function(_, map) + telescope_ags_mappings.open_diff_view_current_file_selected_commit( + map + ) + telescope_ags_mappings.show_entire_commit(map) + telescope_ags_mappings.toggle_entry_value(map) + telescope_ags_mappings.open_selected_commit_in_browser(map) + telescope_ags_mappings.copy_commit_hash_to_clipboard(map) + + return true + end, + }, theme_opts), + {} + ) :find() end @@ -205,17 +224,21 @@ M.checkout_reflog = function() local theme_opts = config.telescope_theme("checkout_reflog") pickers - .new(vim.tbl_extend("force", { - results_title = "Git Reflog, to checkout", - finder = finders.new_oneshot_job( - require("advanced_git_search.commands.find").reflog() - ), - sorter = sorters.get_fuzzy_file(), - attach_mappings = function(_, map) - telescope_ags_mappings.checkout_reflog_entry(map) - return true - end, - }, theme_opts)) + .new( + vim.tbl_extend("force", { + results_title = "Git Reflog, to checkout", + finder = finders.new_oneshot_job( + require("advanced_git_search.commands.find").reflog(), + {} + ), + sorter = sorters.get_fuzzy_file(), + attach_mappings = function(_, map) + telescope_ags_mappings.checkout_reflog_entry(map) + return true + end, + }, theme_opts), + {} + ) :find() end @@ -226,27 +249,30 @@ M.show_custom_functions = function() local theme_opts = config.telescope_theme("show_custom_functions") pickers - .new(vim.tbl_extend("force", { - prompt_title = "Git actions", - finder = finders.new_table(keys), - sorter = sorters.get_fuzzy_file(), - attach_mappings = function(_, map) - telescope_ags_mappings.omnimap( - map, - "", - function(prompt_bufnr) - actions.close(prompt_bufnr) - local selection = action_state.get_selected_entry() - global_picker.execute_git_function( - selection.value, - "telescope" - ) - end - ) - - return true - end, - }, theme_opts)) + .new( + vim.tbl_extend("force", { + prompt_title = "Git actions", + finder = finders.new_table(keys), + sorter = sorters.get_fuzzy_file(), + attach_mappings = function(_, map) + telescope_ags_mappings.omnimap( + map, + "", + function(prompt_bufnr) + actions.close(prompt_bufnr) + local selection = action_state.get_selected_entry() + global_picker.execute_git_function( + selection.value, + "telescope" + ) + end + ) + + return true + end, + }, theme_opts), + {} + ) :find() end diff --git a/lua/advanced_git_search/telescope/previewers/init.lua b/lua/advanced_git_search/telescope/previewers/init.lua index 71fb261..b22a7a7 100644 --- a/lua/advanced_git_search/telescope/previewers/init.lua +++ b/lua/advanced_git_search/telescope/previewers/init.lua @@ -2,22 +2,77 @@ local previewers = require("telescope.previewers") local file_utils = require("advanced_git_search.utils.file") local git_utils = require("advanced_git_search.utils.git") local preview_commands = require("advanced_git_search.commands.preview") +local putils = require("telescope.previewers.utils") + +local search_teardown = function(self) + if self.state and self.state.hl_id then + pcall(vim.fn.matchdelete, self.state.hl_id, self.state.hl_win) + self.state.hl_id = nil + end +end + +local search_cb_jump = function(self, bufnr, query) + if not query or query == "" then + return + end + + vim.api.nvim_buf_call(bufnr, function() + pcall(vim.fn.matchdelete, self.state.hl_id, self.state.winid) + vim.cmd("keepjumps norm! gg") + vim.fn.search(query, "W") + vim.cmd("norm! zz") + + self.state.hl_id = vim.fn.matchadd("TelescopePreviewMatch", query) + end) +end + +local make_whitespace_regex = function(str) + local new = str:gsub("\n", "\\(.*\\n\\)*.*") + return new:gsub("%s+", "\\s*") +end local M = {} +---@class DiffCommitFilePreviewerOpts +---@field bufnr number +---@field preview_query string|nil + --- Shows a diff of the commit in the finder entry, filtered on the file of the current buffer -M.git_diff_commit_file_previewer = function(bufnr) - return previewers.new_termopen_previewer({ +--- +--- @param opts DiffCommitFilePreviewerOpts +M.git_diff_commit_file_previewer = function(opts) + return previewers.new_buffer_previewer({ title = "Changes on selected commit for: " - .. file_utils.file_name(bufnr), - get_command = function(entry) + .. file_utils.file_name(opts.bufnr), + teardown = search_teardown, + get_buffer_by_name = function(_, entry) + return entry.value + end, + define_preview = function(self, entry, _) local commit_hash = entry.opts.commit_hash local prev_commit = git_utils.previous_commit_hash(commit_hash) - return preview_commands.git_diff_file( + local cmd = preview_commands.git_diff_file( prev_commit, commit_hash, - bufnr + opts.bufnr ) + + putils.job_maker(cmd, self.state.bufnr, { + value = entry.value, + bufname = self.state.bufname, + callback = function(preview_bufnr) + if vim.api.nvim_buf_is_valid(preview_bufnr) then + if opts.preview_query then + search_cb_jump( + self, + preview_bufnr, + make_whitespace_regex(opts.preview_query) + ) + end + putils.regex_highlighter(preview_bufnr, "diff") + end + end, + }) end, }) end @@ -28,18 +83,34 @@ end M.git_diff_content_previewer = function(opts) opts = opts or {} - return previewers.new_termopen_previewer({ + return previewers.new_buffer_previewer({ title = "Changes including prompt string", - get_command = function(entry) + teardown = search_teardown, + get_buffer_by_name = function(_, entry) + return entry.value + end, + define_preview = function(self, entry, _) local commit_hash = entry.opts.commit_hash local prompt = entry.opts.prompt local prev_commit = git_utils.previous_commit_hash(commit_hash) - return preview_commands.git_diff_content( + local cmd = preview_commands.git_diff_content( prev_commit, commit_hash, prompt, { bufnr = opts.bufnr } ) + + putils.job_maker(cmd, self.state.bufnr, { + value = entry.value, + bufname = self.state.bufname, + cwd = opts.cwd, + callback = function(bufnr) + if vim.api.nvim_buf_is_valid(bufnr) then + search_cb_jump(self, bufnr, entry.opts.prompt) + putils.regex_highlighter(bufnr, "diff") + end + end, + }) end, }) end @@ -57,11 +128,26 @@ end --- Shows a diff of the branch in the finder entry relative to the passed filename M.git_diff_branch_file_previewer = function(bufnr) local filename = file_utils.file_name(bufnr) - return previewers.new_termopen_previewer({ + + return previewers.new_buffer_previewer({ title = "Diff of current buffer and selected branch for: " .. filename, - get_command = function(entry) + get_buffer_by_name = function(_, entry) + return entry.value + end, + define_preview = function(self, entry, _) local branch = entry.value - return preview_commands.git_diff_branch(branch, bufnr) + + local cmd = preview_commands.git_diff_branch(branch, bufnr) + + putils.job_maker(cmd, self.state.bufnr, { + value = entry.value, + bufname = self.state.bufname, + callback = function(pbufnr) + if vim.api.nvim_buf_is_valid(pbufnr) then + putils.regex_highlighter(pbufnr, "diff") + end + end, + }) end, }) end diff --git a/lua/advanced_git_search/utils/config.lua b/lua/advanced_git_search/utils/config.lua index f904611..bcdb7bb 100644 --- a/lua/advanced_git_search/utils/config.lua +++ b/lua/advanced_git_search/utils/config.lua @@ -9,6 +9,7 @@ M.setup = function(ext_config) or "GBrowse {commit_hash}" ext_config.diff_plugin = ext_config.diff_plugin or "fugitive" ext_config.git_diff_flags = ext_config.git_diff_flags or {} + ext_config.git_log_flags = ext_config.git_log_flags or {} ext_config.show_builtin_git_pickers = ext_config.show_builtin_git_pickers or false @@ -34,6 +35,21 @@ M.git_diff_flags = function() return git_diff_flags end +M.git_log_flags = function() + local git_log_flags = config["git_log_flags"] or {} + + if type(git_log_flags) ~= "table" then + vim.notify( + "git_log_flags must be a table", + vim.log.levels.ERROR, + { title = "Advanced Git Search" } + ) + return nil + end + + return git_log_flags +end + local function get_keymaps() local keymaps = { toggle_date_author = "", @@ -55,7 +71,7 @@ local function get_keymaps() return keymaps end ----@param entry +---@param entry string ---@param format string|nil "C", "ctrl" (default: "C") ---@return string "" or "ctrl-key" M.get_keymap = function(entry, format) diff --git a/lua/advanced_git_search/utils/file.lua b/lua/advanced_git_search/utils/file.lua index 988d8a6..c83bb74 100644 --- a/lua/advanced_git_search/utils/file.lua +++ b/lua/advanced_git_search/utils/file.lua @@ -1,6 +1,7 @@ local uv = vim.loop local validate = vim.validate local utils = require("advanced_git_search.utils") +local tbl = require("advanced_git_search.utils.table") local M = {} @@ -10,7 +11,7 @@ end M.git_dir = function() return M.find_first_ancestor_dir_or_file( - M.path.sanitize(vim.fn.getcwd()), + M.path.sanitize(vim.uv.cwd()), ".git" ) end @@ -59,7 +60,7 @@ M.git_relative_path_to_relative_path = function(git_relative_path) M.path.sanitize(vim.fn.getcwd()), ".git" ) - local project_dir = M.path.sanitize(vim.fn.getcwd()) + local project_dir = M.path.sanitize(vim.uv.cwd()) local absolute_path = git_dir .. "/" .. git_relative_path project_dir = utils.escape_chars(project_dir .. "/") @@ -125,7 +126,7 @@ M.path = (function() end local function path_join(...) - return table.concat(vim.tbl_flatten({ ... }), "/") + return table.concat(tbl.flatten({ ... }), "/") end -- Traverse the path calling cb along the way. @@ -217,7 +218,7 @@ function M.search_ancestors(startpath, func) end function M.root_pattern(...) - local patterns = vim.tbl_flatten({ ... }) + local patterns = tbl.flatten({ ... }) local function matcher(path) for _, pattern in ipairs(patterns) do for _, p in diff --git a/lua/advanced_git_search/utils/git.lua b/lua/advanced_git_search/utils/git.lua index e41d25e..5f0fb65 100644 --- a/lua/advanced_git_search/utils/git.lua +++ b/lua/advanced_git_search/utils/git.lua @@ -1,4 +1,5 @@ local utils = require("advanced_git_search.utils") +local command_utils = require("advanced_git_search.commands.utils") local file = require("advanced_git_search.utils.file") local command_util = require("advanced_git_search.utils.command") @@ -12,10 +13,21 @@ local all_commit_hashes = function() end local all_commit_hashes_touching_file = function(git_relative_file_path) + local git_log_cmd = { + "git", + "log", + "--follow", + "--pretty=format:'%H'", + "--", + git_relative_file_path, + } + + git_log_cmd = command_utils.format_git_log_command(git_log_cmd) + local command = "cd " .. file.git_dir() - .. " && git log --follow --pretty=format:'%H' -- " - .. git_relative_file_path + .. " && " + .. table.concat(git_log_cmd, " ") local output = command_util.execute(command) return utils.split_string(output, "\n") diff --git a/lua/advanced_git_search/utils/init.lua b/lua/advanced_git_search/utils/init.lua index c2bb999..45ca4fd 100644 --- a/lua/advanced_git_search/utils/init.lua +++ b/lua/advanced_git_search/utils/init.lua @@ -51,6 +51,8 @@ M.escape_term = function(x) x:gsub("%%", "\\%%") :gsub("^%^", "\\%^") :gsub("%$$", "\\%$") + :gsub("%{", "\\%{") + :gsub("%}", "\\%}") :gsub("%(", "\\%(") :gsub("%)", "\\%)") :gsub("%.", "\\%.") diff --git a/lua/advanced_git_search/utils/prompt.lua b/lua/advanced_git_search/utils/prompt.lua new file mode 100644 index 0000000..07c0820 --- /dev/null +++ b/lua/advanced_git_search/utils/prompt.lua @@ -0,0 +1,36 @@ +local M = {} + +---@param prompt string +---@return table {query: string, author?: string} +M.parse = function(prompt) + local query, author = prompt:match("^(.-)%s*@%s*(.*)$") + + if query then + return { + query = query ~= "" and query or nil, + author = author ~= "" and author or nil, + } + end + + -- there is only an author specified + local author_only = prompt:match("^.*@%s*(.*)$") + + if author_only then + return { + query = nil, + author = author, + } + end + + -- there is only an @ symbol (no query or author) + if prompt:match("^%s*@%s*$") then + return { query = nil, author = nil } + end + + return { + query = prompt, + author = nil, + } +end + +return M diff --git a/lua/advanced_git_search/utils/table.lua b/lua/advanced_git_search/utils/table.lua index 5d387b6..9caffc6 100644 --- a/lua/advanced_git_search/utils/table.lua +++ b/lua/advanced_git_search/utils/table.lua @@ -12,4 +12,16 @@ M.index_of = function(array, value) return -1 end +M.flatten = function(tbl) + local result + + if vim.iter ~= nil then + result = vim.iter(tbl):flatten():totable() + else + result = vim.tbl_flatten(tbl) + end + + return result +end + return M diff --git a/lua/spec/minimal_init.lua b/lua/spec/minimal_init.lua new file mode 100644 index 0000000..d1f8210 --- /dev/null +++ b/lua/spec/minimal_init.lua @@ -0,0 +1,39 @@ +local M = {} + +function M.root(root) + local f = debug.getinfo(1, "S").source:sub(2) + return vim.fn.fnamemodify(f, ":p:h:h") .. "/" .. (root or "") +end + +---@param plugin string +function M.load(plugin) + local name = plugin:match(".*/(.*)") + local package_root = M.root(".tests/site/pack/deps/start/") + if not vim.loop.fs_stat(package_root .. name) then + print("Installing " .. plugin) + vim.fn.mkdir(package_root, "p") + vim.fn.system({ + "git", + "clone", + "--depth=1", + "https://github.com/" .. plugin .. ".git", + package_root .. "/" .. name, + }) + end +end + +function M.setup() + vim.cmd([[set runtimepath=$VIMRUNTIME]]) + vim.opt.runtimepath:append(M.root()) + vim.opt.runtimepath:append("./") + vim.opt.packpath = { M.root(".tests/site") } + M.load("nvim-lua/plenary.nvim") + vim.env.XDG_CONFIG_HOME = M.root(".tests/config") + vim.env.XDG_DATA_HOME = M.root(".tests/data") + vim.env.XDG_STATE_HOME = M.root(".tests/state") + vim.env.XDG_CACHE_HOME = M.root(".tests/cache") + + print("Setup complete...\n") +end + +M.setup() diff --git a/lua/spec/minimal_init_fzf.lua b/lua/spec/minimal_init_fzf.lua index c7e69ae..c4b9b69 100644 --- a/lua/spec/minimal_init_fzf.lua +++ b/lua/spec/minimal_init_fzf.lua @@ -54,6 +54,7 @@ function M.setup() require("advanced_git_search.fzf").setup({ git_flags = { "-c", "delta.side-by-side=false" }, git_diff_flags = {}, + git_log_flags = {}, show_builtin_git_pickers = true, diff_plugin = "diffview", entry_default_author_or_date = "author", diff --git a/lua/spec/minimal_init_snacks.lua b/lua/spec/minimal_init_snacks.lua new file mode 100644 index 0000000..3746f90 --- /dev/null +++ b/lua/spec/minimal_init_snacks.lua @@ -0,0 +1,74 @@ +local M = {} + +function M.root(root) + local f = debug.getinfo(1, "S").source:sub(2) + return vim.fn.fnamemodify(f, ":p:h:h") .. "/" .. (root or "") +end + +---@param plugin string +function M.load(plugin) + local name = plugin:match(".*/(.*)") + local package_root = M.root(".tests/site/pack/deps/start/") + if not vim.loop.fs_stat(package_root .. name) then + print("Installing " .. plugin) + vim.fn.mkdir(package_root, "p") + vim.fn.system({ + "git", + "clone", + "--depth=1", + "https://github.com/" .. plugin .. ".git", + package_root .. "/" .. name, + }) + end +end + +function M.setup() + vim.opt.runtimepath:remove(vim.fn.expand("~/.config/nvim")) + vim.opt.packpath:remove(vim.fn.expand("~/.local/share/nvim/site")) + vim.opt.runtimepath:append(M.root()) + vim.opt.runtimepath:append("./") + + -- vim.fn.delete(M.root(".tests"), "rf") + vim.opt.packpath = { M.root(".tests/site") } + + -- dependencies + M.load("folke/snacks.nvim") + M.load("tpope/vim-fugitive") + M.load("tpope/vim-rhubarb") + M.load("sindrest/diffview.nvim") + + vim.env.XDG_CONFIG_HOME = M.root(".tests/config") + vim.env.XDG_DATA_HOME = M.root(".tests/data") + vim.env.XDG_STATE_HOME = M.root(".tests/state") + vim.env.XDG_CACHE_HOME = M.root(".tests/cache") + + vim.g.mapleader = " " + print("Setup complete...\n") + require("snacks").setup({ + bigfile = { enabled = false }, + dashboard = { enabled = false }, + explorer = { enabled = false }, + indent = { enabled = false }, + input = { enabled = false }, + picker = { enabled = true }, + notifier = { enabled = false }, + quickfile = { enabled = false }, + scope = { enabled = false }, + scroll = { enabled = false }, + statuscolumn = { enabled = false }, + words = { enabled = false }, + }) + + require("advanced_git_search.snacks").setup({ + git_flags = { "-c", "delta.side-by-side=false" }, + git_diff_flags = {}, + git_log_flags = {}, + show_builtin_git_pickers = true, + diff_plugin = "diffview", + entry_default_author_or_date = "author", + }) + + print("Config complete...\n") +end + +M.setup() diff --git a/lua/spec/minimal_init_telescope.lua b/lua/spec/minimal_init_telescope.lua index 08a1bfb..6b58723 100644 --- a/lua/spec/minimal_init_telescope.lua +++ b/lua/spec/minimal_init_telescope.lua @@ -51,6 +51,7 @@ function M.setup() advanced_git_search = { git_flags = { "-c", "delta.side-by-side=false" }, git_diff_flags = {}, + git_log_flags = {}, show_builtin_git_pickers = true, diff_plugin = "diffview", }, diff --git a/lua/spec/prompt_pattern_spec.lua b/lua/spec/prompt_pattern_spec.lua new file mode 100644 index 0000000..e37efdf --- /dev/null +++ b/lua/spec/prompt_pattern_spec.lua @@ -0,0 +1,64 @@ +local prompt = require("advanced_git_search.utils.prompt") + +describe("parse prompt", function() + it("extracts nothing although there is an @ sign", function() + local result = prompt.parse("@") + + assert.are.same(nil, result.query) + assert.are.same(nil, result.author) + + local result_with_spaces = prompt.parse(" @ ") + + assert.are.same(nil, result_with_spaces.query) + assert.are.same(nil, result_with_spaces.author) + end) + + it("extracts query only", function() + local result = prompt.parse("function() end") + + assert.are.same("function() end", result.query) + assert.are.same(nil, result.author) + end) + + it("extracts query only although there is an @ sign", function() + local result = prompt.parse("function() end @") + + assert.are.same("function() end", result.query) + assert.are.same(nil, result.author) + end) + + it("extracts author only", function() + local result = prompt.parse("@aaron") + + assert.are.same(nil, result.query) + assert.are.same("aaron", result.author) + end) + + it("extracts author only with spaces", function() + local result = prompt.parse("@aaron hallaert") + + assert.are.same(nil, result.query) + assert.are.same("aaron hallaert", result.author) + end) + + it("extracts author only with prefix whitespace", function() + local result = prompt.parse(" @aaron") + + assert.are.same(nil, result.query) + assert.are.same("aaron", result.author) + end) + + it("separates query from author", function() + local result = prompt.parse("Dit is een test @Aaron") + + assert.are.same("Dit is een test", result.query) + assert.are.same("Aaron", result.author) + end) + + it("separates query from author without a space", function() + local result = prompt.parse("Dit is een test@Aaron") + + assert.are.same("Dit is een test", result.query) + assert.are.same("Aaron", result.author) + end) +end)