Repository: pwntester/octo.nvim (lua)

Issue:
Feature Request: add config options to change picker mappings
<!-- Here is for bug reports and feature requests ONLY! 

If you're looking for help, please use the discussions forum.

-->

## Issue Description

Type: *feature request*

### Describe what happened (or what feature you want)

I'd like the ability to change the Telescope mappings set by this plugin. Specifically, I use `<C-b>` to scroll the preview up, and I always accidentally open issues/prs in the browser when using Octo.

### Describe what you expected to happen

The ability to change Telescope mappings, which are currently hard-coded.

### How to reproduce it (as minimally and precisely as possible)

N/A (not a bug)

### Tell us your environment

N/A (not a bug)

### Anything else we need to know?

I submitted a pull request for this issue: #448

Pull request:
<!--  Thanks for submitting a pull request! Here are some tips for you:
1. Please make sure you have read and understood the contributing guidelines: https://github.com/pwntester/octo.nvim/blob/master/CONTRIBUTING.md
2. Please make sure the PR has a corresponding issue.
-->

### Describe what this PR does / why we need it

Adds config options to change the telescope or fzf-lua mappings.

### Does this pull request fix one issue?

<!--If that, add "Fixes #xxxx" below in the next line. For example, Fixes #15. Otherwise, add "NONE" -->

Fixes #447

### Describe how you did it

I added the the config options and used them in the telescope and fzf-lua picker code. I added a util, along with spec tests, that converts vim style mappings to fzf ones.

### Describe how to verify it

1. Change `pwntester/octo.nvim` to `benelan/octo.nvim` in your Neovim plugin config
2. Add the following to your Octo config

    ```lua
    picker_config = {
      mappings = {
        open_in_browser = { lhs = "<M-b>", desc = "open issue in browser" },
        copy_url = { lhs = "<M-y>", desc = "copy url to system clipboard" },
        checkout_pr = { lhs = "<M-o>", desc = "checkout pull request" },
        merge_pr = { lhs = "<M-r>", desc = "merge pull request" },
      },
    },
    ```
3. Open an Octo telescope or fzf-lua picker, e.g. `Octo pr list`
4. Verify that the mappings are triggered with alt instead of ctrl (which is the default)

### Special notes for reviews

The `desc` property isn't used, but I added it to match the format of the other mappings in the config. Plus, Telescope may add the ability to use the `desc` for `<C-/>` some day.

Let me know if you'd like me to change anything about the config options I added, thanks for the review!

Patch:
diff --git a/README.md b/README.md
index f99204cd3..cec7dbc29 100644
--- a/README.md
+++ b/README.md
@@ -83,6 +83,14 @@ require"octo".setup({
   enable_builtin = false,                  -- shows a list of builtin actions when no action is provided
   default_remote = {"upstream", "origin"}; -- order to try remotes
   ssh_aliases = {},                        -- SSH aliases. e.g. `ssh_aliases = {["github.com-work"] = "github.com"}`
+  picker_config = {
+    mappings = {
+      open_in_browser = { lhs = "<C-b>", desc = "open issue in browser" },
+      copy_url = { lhs = "<C-y>", desc = "copy url to system clipboard" },
+      checkout_pr = { lhs = "<C-o>", desc = "checkout pull request" },
+      merge_pr = { lhs = "<C-r>", desc = "merge pull request" },
+    },
+  },
   reaction_viewer_hint_icon = "";         -- marker for user reactions
   user_icon = " ";                        -- user icon
   timeline_marker = "";                   -- timeline marker
diff --git a/lua/octo/config.lua b/lua/octo/config.lua
index 07eee423e..bcf4439c2 100644
--- a/lua/octo/config.lua
+++ b/lua/octo/config.lua
@@ -4,6 +4,12 @@ M.defaults = {
   picker = "telescope",
   picker_config = {
     use_emojis = false,
+    mappings = {
+      open_in_browser = { lhs = "<C-b>", desc = "open issue in browser" },
+      copy_url = { lhs = "<C-y>", desc = "copy url to system clipboard" },
+      checkout_pr = { lhs = "<C-o>", desc = "checkout pull request" },
+      merge_pr = { lhs = "<C-r>", desc = "merge pull request" },
+    },
   },
   default_remote = { "upstream", "origin" },
   ssh_aliases = {},
diff --git a/lua/octo/pickers/fzf-lua/pickers/fzf_actions.lua b/lua/octo/pickers/fzf-lua/pickers/fzf_actions.lua
index 8ae283117..d05ef8451 100644
--- a/lua/octo/pickers/fzf-lua/pickers/fzf_actions.lua
+++ b/lua/octo/pickers/fzf-lua/pickers/fzf_actions.lua
@@ -1,4 +1,6 @@
 local picker_utils = require "octo.pickers.fzf-lua.pickers.utils"
+local octo_config = require "octo.config"
+local utils = require "octo.utils"
 local M = {}
 
 M.common_buffer_actions = function(formatted_items)
@@ -19,11 +21,12 @@ M.common_buffer_actions = function(formatted_items)
 end
 
 M.common_open_actions = function(formatted_items)
+  local cfg = octo_config.get_config()
   return vim.tbl_extend("force", M.common_buffer_actions(formatted_items), {
-    ["ctrl-b"] = function(selected)
+    [utils.convert_vim_mapping_to_fzf(cfg.picker_config.mappings.open_in_browser.lhs)] = function(selected)
       picker_utils.open_in_browser(formatted_items[selected[1]])
     end,
-    ["ctrl-y"] = function(selected)
+    [utils.convert_vim_mapping_to_fzf(cfg.picker_config.mappings.copy_url.lhs)] = function(selected)
       picker_utils.copy_url(formatted_items[selected[1]])
     end,
   })
diff --git a/lua/octo/pickers/fzf-lua/pickers/prs.lua b/lua/octo/pickers/fzf-lua/pickers/prs.lua
index 64a976c44..ad925340a 100644
--- a/lua/octo/pickers/fzf-lua/pickers/prs.lua
+++ b/lua/octo/pickers/fzf-lua/pickers/prs.lua
@@ -79,7 +79,7 @@ return function(opts)
       ["--info"] = "default",
     },
     actions = vim.tbl_extend("force", fzf_actions.common_open_actions(formatted_pulls), {
-      ["ctrl-o"] = function(selected)
+      [utils.convert_vim_mapping_to_fzf(cfg.picker_config.mappings.checkout_pr.lhs)] = function(selected)
         local entry = formatted_pulls[selected[1]]
         checkout_pull_request(entry)
       end,
diff --git a/lua/octo/pickers/telescope/provider.lua b/lua/octo/pickers/telescope/provider.lua
index e1db58ca2..4f3830ca9 100644
--- a/lua/octo/pickers/telescope/provider.lua
+++ b/lua/octo/pickers/telescope/provider.lua
@@ -169,8 +169,8 @@ function M.issues(opts)
               action_set.select:replace(function(prompt_bufnr, type)
                 open(type)(prompt_bufnr)
               end)
-              map("i", "<c-b>", open_in_browser())
-              map("i", "<c-y>", copy_url())
+              map("i", cfg.picker_config.mappings.open_in_browser.lhs, open_in_browser())
+              map("i", cfg.picker_config.mappings.copy_url.lhs, copy_url())
               return true
             end,
           })
@@ -315,10 +315,10 @@ function M.pull_requests(opts)
               action_set.select:replace(function(prompt_bufnr, type)
                 open(type)(prompt_bufnr)
               end)
-              map("i", "<c-o>", checkout_pull_request())
-              map("i", "<c-b>", open_in_browser())
-              map("i", "<c-y>", copy_url())
-              map("i", "<c-r>", merge_pull_request())
+              map("i", cfg.picker_config.mappings.checkout_pr.lhs, checkout_pull_request())
+              map("i", cfg.picker_config.mappings.open_in_browser.lhs, open_in_browser())
+              map("i", cfg.picker_config.mappings.copy_url.lhs, copy_url())
+              map("i", cfg.picker_config.mappings.merge_pr.lhs, merge_pull_request())
               return true
             end,
           })
@@ -479,6 +479,7 @@ end
 ---
 function M.search(opts)
   opts = opts or {}
+  local cfg = octo_config.get_config()
   local requester = function()
     return function(prompt)
       if not opts.prompt and utils.is_blank(prompt) then
@@ -531,8 +532,8 @@ function M.search(opts)
         action_set.select:replace(function(prompt_bufnr, type)
           open(type)(prompt_bufnr)
         end)
-        map("i", "<c-b>", open_in_browser())
-        map("i", "<c-y>", copy_url())
+        map("i", cfg.picker_config.mappings.open_in_browser.lhs, open_in_browser())
+        map("i", cfg.picker_config.mappings.copy_url.lhs, copy_url())
         return true
       end,
     })
@@ -923,6 +924,7 @@ end
 --
 function M.repos(opts)
   opts = opts or {}
+  local cfg = octo_config.get_config()
   if not opts.login then
     if vim.g.octo_viewer then
       opts.login = vim.g.octo_viewer
@@ -967,8 +969,8 @@ function M.repos(opts)
               action_set.select:replace(function(prompt_bufnr, type)
                 open(type)(prompt_bufnr)
               end)
-              map("i", "<c-b>", open_in_browser())
-              map("i", "<c-y>", copy_url())
+              map("i", cfg.picker_config.mappings.open_in_browser.lhs, open_in_browser())
+              map("i", cfg.picker_config.mappings.copy_url.lhs, copy_url())
               return true
             end,
           })
diff --git a/lua/octo/utils.lua b/lua/octo/utils.lua
index 471198b00..843b4a271 100644
--- a/lua/octo/utils.lua
+++ b/lua/octo/utils.lua
@@ -1309,4 +1309,10 @@ function M.get_lines_from_context(calling_context)
   return line_number_start, line_number_end
 end
 
+function M.convert_vim_mapping_to_fzf(vim_mapping)
+  local fzf_mapping = string.gsub(vim_mapping, "<[cC]%-(.*)>", "ctrl-%1")
+  fzf_mapping = string.gsub(fzf_mapping, "<[amAM]%-(.*)>", "alt-%1")
+  return string.lower(fzf_mapping)
+end
+
 return M


Test patch:
diff --git a/lua/tests/plenary/utils_spec.lua b/lua/tests/plenary/utils_spec.lua
index f9961df62..145121ed1 100644
--- a/lua/tests/plenary/utils_spec.lua
+++ b/lua/tests/plenary/utils_spec.lua
@@ -25,5 +25,21 @@ describe("Utils module:", function()
       eq(this.parse_remote_url(remote_urls[5], aliases).host, "github.com")
       eq(this.parse_remote_url(remote_urls[5], aliases).repo, "pwntester/octo.nvim")
     end)
+
+    it("convert_vim_mapping_to_fzf changes vim mappings to fzf mappings", function()
+      local utils = require "octo.utils"
+      local mappings = {
+        ["<C-j>"] = "ctrl-j",
+        ["<c-k>"] = "ctrl-k",
+        ["<A-J>"] = "alt-j",
+        ["<a-k>"] = "alt-k",
+        ["<M-Tab>"] = "alt-tab",
+        ["<m-UP>"] = "alt-up",
+      }
+
+      for vim_mapping, fzf_mapping in pairs(mappings) do
+        eq(utils.convert_vim_mapping_to_fzf(vim_mapping), fzf_mapping)
+      end
+    end)
   end)
 end)