diff --git a/lua/neogit/buffers/commit_view/init.lua b/lua/neogit/buffers/commit_view/init.lua index 60dbed42f..c57989942 100644 --- a/lua/neogit/buffers/commit_view/init.lua +++ b/lua/neogit/buffers/commit_view/init.lua @@ -5,6 +5,7 @@ local git = require("neogit.lib.git") local config = require("neogit.config") local popups = require("neogit.popups") local status_maps = require("neogit.config").get_reversed_status_maps() +local notification = require("neogit.lib.notification") local api = vim.api @@ -174,6 +175,20 @@ function M:open(kind) }, mappings = { n = { + ["o"] = function() + if not vim.ui.open then + notification.warn("Requires Neovim >= 0.10") + return + end + + local uri = git.remote.commit_url(self.commit_info.oid) + if uri then + notification.info(("Opening %q in your browser."):format(uri)) + vim.ui.open(uri) + else + notification.warn("Couldn't determine commit URL to open") + end + end, [""] = function() local c = self.buffer.ui:get_component_under_cursor(function(c) return c.options.highlight == "NeogitFilePath" @@ -272,19 +287,38 @@ function M:open(kind) vim.cmd("normal! zt") end end, - [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) + [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) p { commits = { self.commit_info.oid } } end), [popups.mapping_for("BranchPopup")] = popups.open("branch", function(p) p { commits = { self.commit_info.oid } } end), + [popups.mapping_for("CherryPickPopup")] = popups.open("cherry_pick", function(p) + p { commits = { self.commit_info.oid } } + end), [popups.mapping_for("CommitPopup")] = popups.open("commit", function(p) p { commit = self.commit_info.oid } end), + [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) + p { + section = { name = "log" }, + item = { name = self.commit_info.oid }, + } + end), [popups.mapping_for("FetchPopup")] = popups.open("fetch"), + -- help + [popups.mapping_for("IgnorePopup")] = popups.open("ignore", function(p) + local path = self.buffer.ui:get_hunk_or_filename_under_cursor() + p { + paths = { path and path.escaped_path }, + worktree_root = git.repo.worktree_root, + } + end), + [popups.mapping_for("LogPopup")] = popups.open("log"), [popups.mapping_for("MergePopup")] = popups.open("merge", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } end), + [popups.mapping_for("PullPopup")] = popups.open("pull"), [popups.mapping_for("PushPopup")] = popups.open("push", function(p) p { commit = self.commit_info.oid } end), @@ -292,26 +326,18 @@ function M:open(kind) p { commit = self.commit_info.oid } end), [popups.mapping_for("RemotePopup")] = popups.open("remote"), + [popups.mapping_for("ResetPopup")] = popups.open("reset", function(p) + p { commit = self.commit_info.oid } + end), [popups.mapping_for("RevertPopup")] = popups.open("revert", function(p) local item = self.buffer.ui:get_hunk_or_filename_under_cursor() or {} p { commits = { self.commit_info.oid }, hunk = item.hunk } end), - [popups.mapping_for("ResetPopup")] = popups.open("reset", function(p) - p { commit = self.commit_info.oid } - end), + [popups.mapping_for("StashPopup")] = popups.open("stash"), [popups.mapping_for("TagPopup")] = popups.open("tag", function(p) p { commit = self.commit_info.oid } end), - [popups.mapping_for("PullPopup")] = popups.open("pull"), - [popups.mapping_for("DiffPopup")] = popups.open("diff", function(p) - p { - section = { name = "log" }, - item = { name = self.commit_info.oid }, - } - end), - [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) - p { commits = { self.commit_info.oid } } - end), + [popups.mapping_for("WorktreePopup")] = popups.open("worktree"), [status_maps["Close"]] = function() self:close() end, diff --git a/lua/neogit/buffers/log_view/init.lua b/lua/neogit/buffers/log_view/init.lua index 92978614c..d285e0378 100644 --- a/lua/neogit/buffers/log_view/init.lua +++ b/lua/neogit/buffers/log_view/init.lua @@ -6,6 +6,8 @@ local status_maps = require("neogit.config").get_reversed_status_maps() local CommitViewBuffer = require("neogit.buffers.commit_view") local util = require("neogit.lib.util") local a = require("plenary.async") +local notification = require("neogit.lib.notification") +local git = require("neogit.lib.git") ---@class LogViewBuffer ---@field commits CommitLogEntry[] @@ -127,6 +129,25 @@ function M:open() end), }, n = { + ["o"] = function() + if not vim.ui.open then + notification.warn("Requires Neovim >= 0.10") + return + end + + local oid = self.buffer.ui:get_commit_under_cursor() + if not oid then + return + end + + local uri = git.remote.commit_url(oid) + if uri then + notification.info(("Opening %q in your browser."):format(uri)) + vim.ui.open(uri) + else + notification.warn("Couldn't determine commit URL to open") + end + end, [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } end), diff --git a/lua/neogit/buffers/reflog_view/init.lua b/lua/neogit/buffers/reflog_view/init.lua index 0187a187d..986fca7d1 100644 --- a/lua/neogit/buffers/reflog_view/init.lua +++ b/lua/neogit/buffers/reflog_view/init.lua @@ -4,6 +4,8 @@ local config = require("neogit.config") local popups = require("neogit.popups") local status_maps = require("neogit.config").get_reversed_status_maps() local CommitViewBuffer = require("neogit.buffers.commit_view") +local notification = require("neogit.lib.notification") +local git = require("neogit.lib.git") ---@class ReflogViewBuffer ---@field entries ReflogEntry[] @@ -100,6 +102,25 @@ function M:open(_) end), }, n = { + ["o"] = function() + if not vim.ui.open then + notification.warn("Requires Neovim >= 0.10") + return + end + + local oid = self.buffer.ui:get_commit_under_cursor() + if not oid then + return + end + + local uri = git.remote.commit_url(oid) + if uri then + notification.info(("Opening %q in your browser."):format(uri)) + vim.ui.open(uri) + else + notification.warn("Couldn't determine commit URL to open") + end + end, [popups.mapping_for("BisectPopup")] = popups.open("bisect", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } end), diff --git a/lua/neogit/buffers/status/actions.lua b/lua/neogit/buffers/status/actions.lua index f363f8599..a28d2fec6 100644 --- a/lua/neogit/buffers/status/actions.lua +++ b/lua/neogit/buffers/status/actions.lua @@ -93,6 +93,7 @@ end local M = {} ---@param self StatusBuffer +---@return fun(): nil M.v_discard = function(self) return a.void(function() local selection = self.buffer.ui:get_selection() @@ -217,6 +218,7 @@ M.v_discard = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_stage = function(self) return a.void(function() local selection = self.buffer.ui:get_selection() @@ -273,6 +275,7 @@ M.v_stage = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_unstage = function(self) return a.void(function() local selection = self.buffer.ui:get_selection() @@ -318,6 +321,7 @@ M.v_unstage = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_branch_popup = function(self) return popups.open("branch", function(p) p { commits = self.buffer.ui:get_commits_in_selection() } @@ -325,6 +329,7 @@ M.v_branch_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_cherry_pick_popup = function(self) return popups.open("cherry_pick", function(p) p { commits = self.buffer.ui:get_commits_in_selection() } @@ -332,6 +337,7 @@ M.v_cherry_pick_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_commit_popup = function(self) return popups.open("commit", function(p) local commits = self.buffer.ui:get_commits_in_selection() @@ -342,6 +348,7 @@ M.v_commit_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_merge_popup = function(self) return popups.open("merge", function(p) local commits = self.buffer.ui:get_commits_in_selection() @@ -352,6 +359,7 @@ M.v_merge_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_push_popup = function(self) return popups.open("push", function(p) local commits = self.buffer.ui:get_commits_in_selection() @@ -362,6 +370,7 @@ M.v_push_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_rebase_popup = function(self) return popups.open("rebase", function(p) local commits = self.buffer.ui:get_commits_in_selection() @@ -372,6 +381,7 @@ M.v_rebase_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_revert_popup = function(self) return popups.open("revert", function(p) p { commits = self.buffer.ui:get_commits_in_selection() } @@ -379,6 +389,7 @@ M.v_revert_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_reset_popup = function(self) return popups.open("reset", function(p) local commits = self.buffer.ui:get_commits_in_selection() @@ -389,6 +400,7 @@ M.v_reset_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_tag_popup = function(self) return popups.open("tag", function(p) local commits = self.buffer.ui:get_commits_in_selection() @@ -399,6 +411,7 @@ M.v_tag_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_stash_popup = function(self) return popups.open("stash", function(p) local stash = self.buffer.ui:get_yankable_under_cursor() @@ -407,6 +420,7 @@ M.v_stash_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_diff_popup = function(self) return popups.open("diff", function(p) local section = self.buffer.ui:get_selection().section @@ -416,6 +430,7 @@ M.v_diff_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_ignore_popup = function(self) return popups.open("ignore", function(p) p { paths = self.buffer.ui:get_filepaths_in_selection(), worktree_root = git.repo.worktree_root } @@ -423,6 +438,7 @@ M.v_ignore_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_bisect_popup = function(self) return popups.open("bisect", function(p) p { commits = self.buffer.ui:get_commits_in_selection() } @@ -430,31 +446,37 @@ M.v_bisect_popup = function(self) end ---@param _self StatusBuffer +---@return fun(): nil M.v_remote_popup = function(_self) return popups.open("remote") end ---@param _self StatusBuffer +---@return fun(): nil M.v_fetch_popup = function(_self) return popups.open("fetch") end ---@param _self StatusBuffer +---@return fun(): nil M.v_pull_popup = function(_self) return popups.open("pull") end ---@param _self StatusBuffer +---@return fun(): nil M.v_help_popup = function(_self) return popups.open("help") end ---@param _self StatusBuffer +---@return fun(): nil M.v_log_popup = function(_self) return popups.open("log") end ---@param self StatusBuffer +---@return fun(): nil M.v_margin_popup = function(self) return popups.open("margin", function(p) p { buffer = self } @@ -462,11 +484,13 @@ M.v_margin_popup = function(self) end ---@param _self StatusBuffer +---@return fun(): nil M.v_worktree_popup = function(_self) return popups.open("worktree") end ---@param self StatusBuffer +---@return fun(): nil M.n_down = function(self) return function() if vim.v.count > 0 then @@ -482,6 +506,7 @@ M.n_down = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_up = function(self) return function() if vim.v.count > 0 then @@ -497,6 +522,7 @@ M.n_up = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_toggle = function(self) return function() local fold = self.buffer.ui:get_fold_under_cursor() @@ -516,6 +542,7 @@ M.n_toggle = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_open_fold = function(self) return function() local fold = self.buffer.ui:get_fold_under_cursor() @@ -535,6 +562,7 @@ M.n_open_fold = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_close_fold = function(self) return function() local fold = self.buffer.ui:get_fold_under_cursor() @@ -550,11 +578,13 @@ M.n_close_fold = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_close = function(self) return require("neogit.lib.ui.helpers").close_topmost(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_open_or_scroll_down = function(self) return function() local commit = self.buffer.ui:get_commit_under_cursor() @@ -565,6 +595,7 @@ M.n_open_or_scroll_down = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_open_or_scroll_up = function(self) return function() local commit = self.buffer.ui:get_commit_under_cursor() @@ -575,6 +606,7 @@ M.n_open_or_scroll_up = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_refresh_buffer = function(self) return a.void(function() self:dispatch_refresh({ update_diffs = { "*:*" } }, "n_refresh_buffer") @@ -582,6 +614,7 @@ M.n_refresh_buffer = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_depth1 = function(self) return function() local section = self.buffer.ui:get_current_section() @@ -600,6 +633,7 @@ M.n_depth1 = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_depth2 = function(self) return function() local section = self.buffer.ui:get_current_section() @@ -627,6 +661,7 @@ M.n_depth2 = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_depth3 = function(self) return function() local section = self.buffer.ui:get_current_section() @@ -656,6 +691,7 @@ M.n_depth3 = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_depth4 = function(self) return function() local section = self.buffer.ui:get_current_section() @@ -682,6 +718,7 @@ M.n_depth4 = function(self) end ---@param _self StatusBuffer +---@return fun(): nil M.n_command_history = function(_self) return a.void(function() require("neogit.buffers.git_command_history"):new():show() @@ -689,6 +726,7 @@ M.n_command_history = function(_self) end ---@param _self StatusBuffer +---@return fun(): nil M.n_show_refs = function(_self) return a.void(function() require("neogit.buffers.refs_view").new(git.refs.list_parsed(), git.repo.worktree_root):open() @@ -696,6 +734,7 @@ M.n_show_refs = function(_self) end ---@param self StatusBuffer +---@return fun(): nil M.n_yank_selected = function(self) return function() local yank = self.buffer.ui:get_yankable_under_cursor() @@ -714,6 +753,7 @@ M.n_yank_selected = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_discard = function(self) return a.void(function() git.index.update() @@ -947,6 +987,7 @@ M.n_discard = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_go_to_next_hunk_header = function(self) return function() local c = self.buffer.ui:get_component_under_cursor(function(c) @@ -978,6 +1019,7 @@ M.n_go_to_next_hunk_header = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_go_to_previous_hunk_header = function(self) return function() local function previous_hunk_header(self, line) @@ -1004,6 +1046,7 @@ M.n_go_to_previous_hunk_header = function(self) end ---@param _self StatusBuffer +---@return fun(): nil M.n_init_repo = function(_self) return function() git.init.init_repo() @@ -1011,6 +1054,7 @@ M.n_init_repo = function(_self) end ---@param self StatusBuffer +---@return fun(): nil M.n_rename = function(self) return a.void(function() local selection = self.buffer.ui:get_selection() @@ -1046,6 +1090,7 @@ M.n_rename = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_untrack = function(self) return a.void(function() local selection = self.buffer.ui:get_selection() @@ -1076,6 +1121,7 @@ M.n_untrack = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.v_untrack = function(self) return a.void(function() local selection = self.buffer.ui:get_selection() @@ -1103,6 +1149,7 @@ M.v_untrack = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_stage = function(self) return a.void(function() local stagable = self.buffer.ui:get_hunk_or_filename_under_cursor() @@ -1183,6 +1230,7 @@ M.n_stage = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_stage_all = function(self) return a.void(function() git.status.stage_all() @@ -1191,6 +1239,7 @@ M.n_stage_all = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_stage_unstaged = function(self) return a.void(function() git.status.stage_modified() @@ -1199,6 +1248,7 @@ M.n_stage_unstaged = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_unstage = function(self) return a.void(function() local unstagable = self.buffer.ui:get_hunk_or_filename_under_cursor() @@ -1235,6 +1285,7 @@ M.n_unstage = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_unstage_staged = function(self) return a.void(function() git.status.unstage_all() @@ -1243,6 +1294,7 @@ M.n_unstage_staged = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_goto_file = function(self) return function() local item = self.buffer.ui:get_item_under_cursor() @@ -1264,6 +1316,7 @@ M.n_goto_file = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_tab_open = function(self) return function() local item = self.buffer.ui:get_item_under_cursor() @@ -1275,6 +1328,7 @@ M.n_tab_open = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_split_open = function(self) return function() local item = self.buffer.ui:get_item_under_cursor() @@ -1286,6 +1340,7 @@ M.n_split_open = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_vertical_split_open = function(self) return function() local item = self.buffer.ui:get_item_under_cursor() @@ -1297,6 +1352,7 @@ M.n_vertical_split_open = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_branch_popup = function(self) return popups.open("branch", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } @@ -1304,6 +1360,7 @@ M.n_branch_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_bisect_popup = function(self) return popups.open("bisect", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } @@ -1311,6 +1368,7 @@ M.n_bisect_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_cherry_pick_popup = function(self) return popups.open("cherry_pick", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } @@ -1318,6 +1376,7 @@ M.n_cherry_pick_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_commit_popup = function(self) return popups.open("commit", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } @@ -1325,6 +1384,7 @@ M.n_commit_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_merge_popup = function(self) return popups.open("merge", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } @@ -1332,6 +1392,7 @@ M.n_merge_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_push_popup = function(self) return popups.open("push", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } @@ -1339,6 +1400,7 @@ M.n_push_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_rebase_popup = function(self) return popups.open("rebase", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } @@ -1346,6 +1408,7 @@ M.n_rebase_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_revert_popup = function(self) return popups.open("revert", function(p) p { commits = { self.buffer.ui:get_commit_under_cursor() } } @@ -1353,6 +1416,7 @@ M.n_revert_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_reset_popup = function(self) return popups.open("reset", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } @@ -1360,6 +1424,7 @@ M.n_reset_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_tag_popup = function(self) return popups.open("tag", function(p) p { commit = self.buffer.ui:get_commit_under_cursor() } @@ -1367,6 +1432,7 @@ M.n_tag_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_stash_popup = function(self) return popups.open("stash", function(p) local stash = self.buffer.ui:get_yankable_under_cursor() @@ -1375,6 +1441,7 @@ M.n_stash_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_diff_popup = function(self) return popups.open("diff", function(p) local section = self.buffer.ui:get_selection().section @@ -1387,6 +1454,7 @@ M.n_diff_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_ignore_popup = function(self) return popups.open("ignore", function(p) local path = self.buffer.ui:get_hunk_or_filename_under_cursor() @@ -1398,6 +1466,7 @@ M.n_ignore_popup = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_help_popup = function(self) return popups.open("help", function(p) -- Since any other popup can be launched from help, build an ENV for any of them. @@ -1445,26 +1514,31 @@ M.n_help_popup = function(self) end ---@param _self StatusBuffer +---@return fun(): nil M.n_remote_popup = function(_self) return popups.open("remote") end ---@param _self StatusBuffer +---@return fun(): nil M.n_fetch_popup = function(_self) return popups.open("fetch") end ---@param _self StatusBuffer +---@return fun(): nil M.n_pull_popup = function(_self) return popups.open("pull") end ---@param _self StatusBuffer +---@return fun(): nil M.n_log_popup = function(_self) return popups.open("log") end ---@param self StatusBuffer +---@return fun(): nil M.n_margin_popup = function(self) return popups.open("margin", function(p) p { buffer = self } @@ -1472,29 +1546,41 @@ M.n_margin_popup = function(self) end ---@param _self StatusBuffer +---@return fun(): nil M.n_worktree_popup = function(_self) return popups.open("worktree") end ----@param _self StatusBuffer -M.n_open_tree = function(_self) +---@param self StatusBuffer +---@return fun(): nil +M.n_open_tree = function(self) return a.void(function() - local template = "https://${host}/${owner}/${repository}/tree/${branch_name}" - - local upstream = git.branch.upstream_remote() - if not upstream then + if not vim.ui.open then + notification.warn("Requires Neovim >= 0.10") return end - local url = git.remote.get_url(upstream)[1] - local format_values = git.remote.parse(url) - format_values["branch_name"] = git.branch.current() + local commit = self.buffer.ui:get_commit_under_cursor() + local branch = git.branch.current() + local url - vim.ui.open(util.format(template, format_values)) + if commit then + url = git.remote.commit_url(commit) + elseif branch then + url = git.remote.tree_url(branch) + end + + if url then + notification.info(("Opening %q in your browser."):format(url)) + vim.ui.open(url) + else + notification.warn("Couldn't determine commit URL to open") + end end) end ---@param self StatusBuffer|nil +---@return fun(): nil M.n_command = function(self) local process = require("neogit.process") local runner = require("neogit.runner") @@ -1536,6 +1622,7 @@ M.n_command = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_next_section = function(self) return function() local section = self.buffer.ui:get_current_section() @@ -1549,6 +1636,7 @@ M.n_next_section = function(self) end ---@param self StatusBuffer +---@return fun(): nil M.n_prev_section = function(self) return function() local section = self.buffer.ui:get_current_section() diff --git a/lua/neogit/config.lua b/lua/neogit/config.lua index 67d79e6cd..fe8abd948 100644 --- a/lua/neogit/config.lua +++ b/lua/neogit/config.lua @@ -329,6 +329,11 @@ end ---@field commit_editor_I? { [string]: NeogitConfigMappingsCommitEditor_I } A dictionary that uses Commit editor commands to set a single keybind ---@field refs_view? { [string]: NeogitConfigMappingsRefsView } A dictionary that uses Refs view editor commands to set a single keybind +---@class NeogitConfigGitService +---@field pull_request? string +---@field commit? string +---@field tree? string + ---@class NeogitConfig Neogit configuration settings ---@field filewatcher? NeogitFilewatcherConfig Values for filewatcher ---@field graph_style? NeogitGraphStyle Style for graph @@ -338,7 +343,7 @@ end ---@field disable_context_highlighting? boolean Disable context highlights based on cursor position ---@field disable_signs? boolean Special signs to draw for sections etc. in Neogit ---@field prompt_force_push? boolean Offer to force push when branches diverge ----@field git_services? table Templartes to use when opening a pull request for a branch +---@field git_services? NeogitConfigGitService[] Templates to use when opening a pull request for a branch, or commit ---@field fetch_after_checkout? boolean Perform a fetch if the newly checked out branch has an upstream or pushRemote set ---@field telescope_sorter? function The sorter telescope will use ---@field process_spinner? boolean Hide/Show the process spinner @@ -397,10 +402,26 @@ function M.get_default_values() return nil end, git_services = { - ["github.com"] = "https://github.com/${owner}/${repository}/compare/${branch_name}?expand=1", - ["bitbucket.org"] = "https://bitbucket.org/${owner}/${repository}/pull-requests/new?source=${branch_name}&t=1", - ["gitlab.com"] = "https://gitlab.com/${owner}/${repository}/merge_requests/new?merge_request[source_branch]=${branch_name}", - ["azure.com"] = "https://dev.azure.com/${owner}/_git/${repository}/pullrequestcreate?sourceRef=${branch_name}&targetRef=${target}", + ["github.com"] = { + pull_request = "https://github.com/${owner}/${repository}/compare/${branch_name}?expand=1", + commit = "https://github.com/${owner}/${repository}/commit/${oid}", + tree = "https://${host}/${owner}/${repository}/tree/${branch_name}", + }, + ["bitbucket.org"] = { + pull_request = "https://bitbucket.org/${owner}/${repository}/pull-requests/new?source=${branch_name}&t=1", + commit = "https://bitbucket.org/${owner}/${repository}/commits/${oid}", + tree = "https://bitbucket.org/${owner}/${repository}/branch/${branch_name}", + }, + ["gitlab.com"] = { + pull_request = "https://gitlab.com/${owner}/${repository}/merge_requests/new?merge_request[source_branch]=${branch_name}", + commit = "https://gitlab.com/${owner}/${repository}/-/commit/${oid}", + tree = "https://gitlab.com/${owner}/${repository}/-/tree/${branch_name}?ref_type=heads", + }, + ["azure.com"] = { + pull_request = "https://dev.azure.com/${owner}/_git/${repository}/pullrequestcreate?sourceRef=${branch_name}&targetRef=${target}", + commit = "", + tree = "", + }, }, highlight = {}, disable_insert_on_commit = "auto", @@ -1220,6 +1241,15 @@ function M.validate_config() validate_kind(config.popup.kind, "popup.kind") end + if validate_type(config.git_services, "git_services", "table") then + for k, v in pairs(config.git_services) do + validate_type(v, "git_services." .. k, "table") + validate_type(v.pull_request, "git_services." .. k .. ".pull_request", "string") + validate_type(v.commit, "git_services." .. k .. ".commit", "string") + validate_type(v.tree, "git_services." .. k .. ".tree", "string") + end + end + validate_integrations() validate_sections() validate_ignored_settings() @@ -1252,8 +1282,14 @@ function M.setup(opts) end if opts.use_default_keymaps == false then - M.values.mappings = - { status = {}, popup = {}, finder = {}, commit_editor = {}, rebase_editor = {}, refs_view = {} } + M.values.mappings = { + status = {}, + popup = {}, + finder = {}, + commit_editor = {}, + rebase_editor = {}, + refs_view = {}, + } else -- Clear our any "false" user mappings from defaults for section, maps in pairs(opts.mappings or {}) do diff --git a/lua/neogit/lib/git/remote.lua b/lua/neogit/lib/git/remote.lua index 31bc633d1..2d28ebcc8 100644 --- a/lua/neogit/lib/git/remote.lua +++ b/lua/neogit/lib/git/remote.lua @@ -134,4 +134,58 @@ function M.parse(url) } end +---@param oid string object-id for commit +---@return string|nil +function M.commit_url(oid) + local upstream = git.branch.upstream_remote() + if not upstream then + return + end + + local template + local url = M.get_url(upstream)[1] + + for s, v in pairs(require("neogit.config").values.git_services) do + if url:match(util.pattern_escape(s)) then + template = v.commit + break + end + end + + if template and template ~= "" then + local format_values = M.parse(url) + format_values["oid"] = oid + local uri = util.format(template, format_values) + + return uri + end +end + +---@param branch string +---@return string|nil +function M.tree_url(branch) + local upstream = git.branch.upstream_remote() + if not upstream then + return + end + + local template + local url = M.get_url(upstream)[1] + + for s, v in pairs(require("neogit.config").values.git_services) do + if url:match(util.pattern_escape(s)) then + template = v.tree + break + end + end + + if template and template ~= "" then + local format_values = M.parse(url) + format_values["branch_name"] = branch + local uri = util.format(template, format_values) + + return uri + end +end + return M diff --git a/lua/neogit/lib/ui/init.lua b/lua/neogit/lib/ui/init.lua index f2d6bfb1f..225cc5af5 100644 --- a/lua/neogit/lib/ui/init.lua +++ b/lua/neogit/lib/ui/init.lua @@ -40,6 +40,7 @@ function Ui.new(buf) return setmetatable({ buf = buf, layout = {} }, Ui) end +---@return Component|nil function Ui._find_component(components, f, options) for _, c in ipairs(components) do if c.tag == "col" or c.tag == "row" then @@ -64,24 +65,6 @@ function Ui:find_component(f, options) return Ui._find_component(self.layout, f, options or {}) end -function Ui._find_components(components, f, result, options) - for _, c in ipairs(components) do - if c.tag == "col" or c.tag == "row" then - Ui._find_components(c.children, f, result, options) - end - - if f(c) then - table.insert(result, c) - end - end -end - -function Ui:find_components(f, options) - local result = {} - Ui._find_components(self.layout, f, result, options or {}) - return result -end - ---@param fn? fun(c: Component): boolean ---@return Component|nil function Ui:get_component_under_cursor(fn) diff --git a/lua/neogit/popups/branch/actions.lua b/lua/neogit/popups/branch/actions.lua index cc7159122..d733e396b 100644 --- a/lua/neogit/popups/branch/actions.lua +++ b/lua/neogit/popups/branch/actions.lua @@ -378,7 +378,7 @@ function M.open_pull_request() for s, v in pairs(config.values.git_services) do if url:match(util.pattern_escape(s)) then service = s - template = v + template = v.pull_request break end end @@ -409,7 +409,7 @@ function M.open_pull_request() notification.info(("Opening %q in your browser."):format(uri)) vim.ui.open(uri) else - notification.warn("Requires Neovim 0.10") + notification.warn("Requires Neovim >= 0.10") end else notification.warn("Pull request URL template not found for this branch's upstream") diff --git a/spec/buffers/commit_buffer_spec.rb b/spec/buffers/commit_buffer_spec.rb new file mode 100644 index 000000000..0ada40be2 --- /dev/null +++ b/spec/buffers/commit_buffer_spec.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "Commit Buffer", :git, :nvim do + before do + nvim.keys("ll") + end + + it "can close the view with " do + nvim.keys("") + expect(nvim.filetype).to eq("NeogitLogView") + end + + it "can close the view with q" do + nvim.keys("q") + expect(nvim.filetype).to eq("NeogitLogView") + end + + it "can yank OID" do + nvim.keys("Y") + expect(nvim.screen.last.strip).to match(/\A[a-f0-9]{40}\z/) + end + + it "can open the bisect popup" do + nvim.keys("B") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the branch popup" do + nvim.keys("b") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the cherry pick popup" do + nvim.keys("A") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the commit popup" do + nvim.keys("c") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the diff popup" do + nvim.keys("d") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the pull popup" do + nvim.keys("p") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the fetch popup" do + nvim.keys("f") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the ignore popup" do + nvim.keys("i") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the log popup" do + nvim.keys("l") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the remote popup" do + nvim.keys("M") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the merge popup" do + nvim.keys("m") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the push popup" do + nvim.keys("P") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the rebase popup" do + nvim.keys("r") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the tag popup" do + nvim.keys("t") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the revert popup" do + nvim.keys("v") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the worktree popup" do + nvim.keys("w") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the reset popup" do + nvim.keys("X") + expect(nvim.filetype).to eq("NeogitPopup") + end + + it "can open the stash popup" do + nvim.keys("Z") + expect(nvim.filetype).to eq("NeogitPopup") + end +end