Skip to content

Commit

Permalink
54 allow selecting handler if there are multiple handlers matching (#55)
Browse files Browse the repository at this point in the history
* Allow selecting if multiple handlers match

* Fix tests and issue with ordering, add names to handlers

* Update README

* Fix linting

* allow selection if word is selected in visual mode
  • Loading branch information
chrishrb authored Mar 21, 2024
1 parent 0665db8 commit ea4cc71
Show file tree
Hide file tree
Showing 13 changed files with 121 additions and 44 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* if there is no url found under the cursor, the word/selection is automatically searched on the web
* supports user defined handlers to extend the functionality
* support for macOS, Linux and Windows
* more to come (jira issues, ..)
* if multiple patterns match you can simply select the desired URL from the menu

## ⚡️ Requirements

Expand Down Expand Up @@ -52,6 +52,7 @@ require("lazy").setup({
package_json = true, -- open dependencies from package.json
search = true, -- search the web/selection on the web if nothing else is found
jira = { -- custom handler to open Jira tickets (these have higher precedence than builtin handlers)
name = "jira", -- set name of handler
handle = function(mode, line, _)
local ticket = require("gx.helper").find(line, mode, "(%u+-%d+)")
if ticket and #ticket < 20 then
Expand All @@ -60,6 +61,7 @@ require("lazy").setup({
end,
},
rust = { -- custom handler to open rust's cargo packages
name = "rust", -- set name of handler
filetype = { "toml" }, -- you can also set the required filetype for this handler
filename = "Cargo.toml", -- or the necessary filename
handle = function(mode, line, _)
Expand All @@ -74,6 +76,7 @@ require("lazy").setup({
handler_options = {
search_engine = "google", -- you can select between google, bing, duckduckgo, and ecosia
search_engine = "https://search.brave.com/search?q=", -- or you can pass in a custom search engine
select_for_search = false, -- if your cursor is e.g. on a link, the pattern for the link AND for the word will always match. This disables this behaviour for default so that the link is opened without the select option for the word AND link
},
} end,
},
Expand Down
24 changes: 20 additions & 4 deletions lua/gx/handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ local function resolve_handlers(handlers)
add_handler(resolved, github_handler, handlers.github and exists.github == nil)
add_handler(resolved, commit_handler, handlers.commit and exists.commit == nil)
add_handler(resolved, markdown_handler, handlers.markdown and exists.markdown == nil)
add_handler(resolved, url_handler, handlers.url and exists.url == nil)
add_handler(resolved, cve_handler, handlers.cve and exists.cve == nil)
add_handler(resolved, url_handler, handlers.url and exists.url == nil)
add_handler(resolved, search_handler, handlers.search and exists.search == nil)
-- ###

Expand All @@ -54,17 +54,33 @@ end
---@param mode string
---@param line string
---@param configured_handlers { [string]: (boolean | GxHandler)[] }
---@return string | nil
---@return { [number]: GxSelection }
function M.get_url(mode, line, configured_handlers, handler_options)
local detected_urls_set = {}
local detected_urls = {}
local handlers = resolve_handlers(configured_handlers)

for _, handler in ipairs(handlers) do
local url = handler.handle(mode, line, handler_options)

if url then
return url
-- only use search handler if no other pattern matches or word is selected in visual mode
if
url
and (
handler.name ~= "search"
or #detected_urls == 0
or mode ~= "n"
or handler_options.select_for_search == true
)
then
if detected_urls_set[url] == nil then
detected_urls[#detected_urls + 1] = { ["name"] = handler.name, ["url"] = url }
end
detected_urls_set[url] = true
end
end

return detected_urls
end

return M
1 change: 1 addition & 0 deletions lua/gx/handlers/brewfile.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ local helper = require("gx.helper")

local M = {
-- only Brewfile
name = "brewfile",
filetype = nil,
filename = "Brewfile",
}
Expand Down
1 change: 1 addition & 0 deletions lua/gx/handlers/commit.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ local helper = require("gx.helper")

local M = {
-- every filetype and filename
name = "commit",
filetype = nil,
filename = nil,
}
Expand Down
1 change: 1 addition & 0 deletions lua/gx/handlers/cve.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ local helper = require("gx.helper")

local M = {
-- every filetype and filename
name = "cve",
filetype = nil,
filename = nil,
}
Expand Down
1 change: 1 addition & 0 deletions lua/gx/handlers/github.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ local helper = require("gx.helper")

local M = {
-- every filetype and filename
name = "github",
filetype = nil,
filename = nil,
}
Expand Down
1 change: 1 addition & 0 deletions lua/gx/handlers/markdown.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ local helper = require("gx.helper")

local M = {
-- every filetype and filename
name = "markdown",
filetype = { "markdown" },
filename = nil,
}
Expand Down
1 change: 1 addition & 0 deletions lua/gx/handlers/package_json.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ local helper = require("gx.helper")

local M = {
-- only package.json
name = "package_json",
filetype = { "json" },
filename = "package.json",
}
Expand Down
1 change: 1 addition & 0 deletions lua/gx/handlers/plugin.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ local helper = require("gx.helper")

local M = {
-- every filename but only lua
name = "nvim-plugin",
filetype = { "lua", "vim" },
filename = nil,
}
Expand Down
1 change: 1 addition & 0 deletions lua/gx/handlers/search.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ local helper = require("gx.helper")

local M = {
-- every filetype and filename
name = "search",
filetype = nil,
filename = nil,
}
Expand Down
3 changes: 2 additions & 1 deletion lua/gx/handlers/url.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ local helper = require("gx.helper")

local M = {
-- every filetype
name = "url",
filetype = nil,
filename = nil,
}
Expand All @@ -13,7 +14,7 @@ function M.handle(mode, line, _)

-- match url without http(s)
if not url then
pattern = "([a-zA-Z%d_/%-%.~@\\+#]+%.[a-zA-Z%d_/%%%-%.~@\\+#=?&:]+)"
pattern = "([a-zA-Z%d_/%-%.~@\\+#]+%.[a-zA-Z_/%%%-%.~@\\+#=?&:]+)"
url = helper.find(line, mode, pattern)
if url then
return "https://" .. url
Expand Down
40 changes: 32 additions & 8 deletions lua/gx/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ local M = {}

---@class GxHandlerOptions
---@field search_engine string
---@field select_for_search string

---@class GxHandler
---@field name string
---@field filetypes string[] | nil
---@field filename string | nil
---@field handle fun(mode: string, line: string, handler_options: GxHandlerOptions | nil)
Expand All @@ -18,6 +20,10 @@ local M = {}
---@field handlers (boolean | GxHandler)[]
---@field handler_options GxHandlerOptions | nil

---@class GxSelection
---@field name string | nil
---@field url string

-- search for url with handler
function M.open(mode, line)
if not line then
Expand All @@ -28,18 +34,35 @@ function M.open(mode, line)
-- cut if in visual mode
line = helper.cut_with_visual_mode(mode, line)

local url =
local urls =
require("gx.handler").get_url(mode, line, M.options.handlers, M.options.handler_options)

if not url then
if #urls == 0 then
return
elseif #urls == 1 then
return require("gx.shell").execute_with_error(
M.options.open_browser_app,
M.options.open_browser_args,
urls[1].url
)
else
vim.ui.select(urls, {
prompt = "Multiple patterns match. Select:",
format_item = function(item)
return item.url .. " (" .. item.name .. ")"
end,
}, function(selected)
if not selected then
return
end

return require("gx.shell").execute_with_error(
M.options.open_browser_app,
M.options.open_browser_args,
selected.url
)
end)
end

return require("gx.shell").execute_with_error(
M.options.open_browser_app,
M.options.open_browser_args,
url
)
end

-- get the app for opening the webbrowser
Expand Down Expand Up @@ -74,6 +97,7 @@ local function with_defaults(options)
handlers = options.handlers or {},
handler_options = {
search_engine = options.handler_options.search_engine or "google",
select_for_search = options.handler_options.select_for_search or false,
},
}
end
Expand Down
85 changes: 55 additions & 30 deletions test/spec/gx/handler_spec.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
local helper = require("gx.helper")
local handler = require("gx.handler")
local stub = require("luassert.stub")
local assert = require("luassert")

describe("test handler", function()
local activated_handlers
Expand All @@ -23,13 +24,16 @@ describe("test handler", function()
-- mock filetype
vim.bo.filetype = "lua"

assert.equals("https://github.com", handler.get_url("v", "github.com", activated_handlers))
assert.equals(
"https://github.com",
assert.same(
{ { ["name"] = "url", ["url"] = "https://github.com" } },
handler.get_url("v", "github.com", activated_handlers)
)
assert.same(
{ { ["name"] = "url", ["url"] = "https://github.com" } },
handler.get_url("v", "https://github.com", activated_handlers)
)
assert.is.Nil(handler.get_url("v", '"example_user/example_plugin"', activated_handlers))
assert.is.Nil(handler.get_url("v", "Fixes #22", activated_handlers))
assert.same({}, handler.get_url("v", '"example_user/example_plugin"', activated_handlers))
assert.same({}, handler.get_url("v", "Fixes #22", activated_handlers))
end)

it("plugin handler on", function()
Expand All @@ -38,13 +42,16 @@ describe("test handler", function()
-- mock filetype
vim.bo.filetype = "lua"

assert.equals("https://github.com", handler.get_url("v", "github.com", activated_handlers))
assert.equals(
"https://github.com",
assert.same(
{ { ["name"] = "url", ["url"] = "https://github.com" } },
handler.get_url("v", "github.com", activated_handlers)
)
assert.same(
{ { ["name"] = "url", ["url"] = "https://github.com" } },
handler.get_url("v", "https://github.com", activated_handlers)
)
assert.equals(
"https://github.com/example_user/example_plugin",
assert.same(
{ { ["name"] = "nvim-plugin", ["url"] = "https://github.com/example_user/example_plugin" } },
handler.get_url("v", '"example_user/example_plugin"', activated_handlers)
)
end)
Expand All @@ -56,13 +63,16 @@ describe("test handler", function()
-- mock filetype
vim.bo.filetype = "vim"

assert.equals("https://github.com", handler.get_url("v", "github.com", activated_handlers))
assert.equals(
"https://github.com",
assert.same(
{ { ["name"] = "url", ["url"] = "https://github.com" } },
handler.get_url("v", "github.com", activated_handlers)
)
assert.same(
{ { ["name"] = "url", ["url"] = "https://github.com" } },
handler.get_url("v", "https://github.com", activated_handlers)
)
assert.equals(
"https://github.com/example_user/example_plugin",
assert.same(
{ { ["name"] = "nvim-plugin", ["url"] = "https://github.com/example_user/example_plugin" } },
handler.get_url("v", '"example_user/example_plugin"', activated_handlers)
)
end)
Expand All @@ -73,12 +83,15 @@ describe("test handler", function()
-- mock filetype
vim.bo.filetype = "java"

assert.equals("https://github.com", handler.get_url("v", "github.com", activated_handlers))
assert.equals(
"https://github.com",
assert.same(
{ { ["name"] = "url", ["url"] = "https://github.com" } },
handler.get_url("v", "github.com", activated_handlers)
)
assert.same(
{ { ["name"] = "url", ["url"] = "https://github.com" } },
handler.get_url("v", "https://github.com", activated_handlers)
)
assert.is.Nil(handler.get_url("v", '"example_user/example_plugin"', activated_handlers))
assert.same({}, handler.get_url("v", '"example_user/example_plugin"', activated_handlers))
end)

it("github handler on", function()
Expand All @@ -87,13 +100,16 @@ describe("test handler", function()
-- mock filetype
vim.bo.filetype = "lua"

assert.equals("https://github.com", handler.get_url("v", "github.com", activated_handlers))
assert.equals(
"https://github.com",
assert.same(
{ { ["name"] = "url", ["url"] = "https://github.com" } },
handler.get_url("v", "github.com", activated_handlers)
)
assert.same(
{ { ["name"] = "url", ["url"] = "https://github.com" } },
handler.get_url("v", "https://github.com", activated_handlers)
)
assert.equals(
"https://github.com/chrishrb/gx.nvim/issues/22",
assert.same(
{ { ["name"] = "github", ["url"] = "https://github.com/chrishrb/gx.nvim/issues/22" } },
handler.get_url("v", "Fixes #22", activated_handlers)
)
end)
Expand All @@ -107,23 +123,29 @@ describe("test handler", function()
stub(helper, "get_filename")
helper.get_filename.on_call_with().returns("package.json")

assert.equals(
"https://www.npmjs.com/package/@rushstack/eslint-patch",
handler.get_url("v", '"@rushstack/eslint-patch": "^1.2.0",', activated_handlers)
)
assert.same({
{
["name"] = "package_json",
["url"] = "https://www.npmjs.com/package/@rushstack/eslint-patch",
},
}, handler.get_url("v", '"@rushstack/eslint-patch": "^1.2.0",', activated_handlers))

helper.get_filename:revert()
end)

it("user defined handler has precedence", function()
activated_handlers.commit = true
activated_handlers.custom = {
name = "custom",
handle = function()
return "https://from.user.handler"
end,
}

assert.equals("https://from.user.handler", handler.get_url("v", "1a2b3c4", activated_handlers))
assert.same({
{ ["name"] = "custom", ["url"] = "https://from.user.handler" },
{ ["name"] = "commit", ["url"] = "https://github.com/chrishrb/gx.nvim/commit/1a2b3c4" },
}, handler.get_url("v", "1a2b3c4", activated_handlers))
end)

it("user defined handler instead of builtin handler", function()
Expand All @@ -134,6 +156,9 @@ describe("test handler", function()
end,
}

assert.equals("https://from.user.handler", handler.get_url("v", "1a2b3c4", activated_handlers))
assert.same(
{ { ["url"] = "https://from.user.handler" } },
handler.get_url("v", "1a2b3c4", activated_handlers)
)
end)
end)

0 comments on commit ea4cc71

Please sign in to comment.