From ea4cc715326a8bd060a450c24c3c9831cdee2f59 Mon Sep 17 00:00:00 2001 From: Christoph <52382992+chrishrb@users.noreply.github.com> Date: Thu, 21 Mar 2024 17:31:45 +0100 Subject: [PATCH] 54 allow selecting handler if there are multiple handlers matching (#55) * 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 --- README.md | 5 +- lua/gx/handler.lua | 24 +++++++-- lua/gx/handlers/brewfile.lua | 1 + lua/gx/handlers/commit.lua | 1 + lua/gx/handlers/cve.lua | 1 + lua/gx/handlers/github.lua | 1 + lua/gx/handlers/markdown.lua | 1 + lua/gx/handlers/package_json.lua | 1 + lua/gx/handlers/plugin.lua | 1 + lua/gx/handlers/search.lua | 1 + lua/gx/handlers/url.lua | 3 +- lua/gx/init.lua | 40 ++++++++++++--- test/spec/gx/handler_spec.lua | 85 +++++++++++++++++++++----------- 13 files changed, 121 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index dcf54f5..331c3d5 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -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, _) @@ -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, }, diff --git a/lua/gx/handler.lua b/lua/gx/handler.lua index 2181b53..e40d799 100644 --- a/lua/gx/handler.lua +++ b/lua/gx/handler.lua @@ -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) -- ### @@ -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 diff --git a/lua/gx/handlers/brewfile.lua b/lua/gx/handlers/brewfile.lua index 1931cab..d14d7c4 100644 --- a/lua/gx/handlers/brewfile.lua +++ b/lua/gx/handlers/brewfile.lua @@ -2,6 +2,7 @@ local helper = require("gx.helper") local M = { -- only Brewfile + name = "brewfile", filetype = nil, filename = "Brewfile", } diff --git a/lua/gx/handlers/commit.lua b/lua/gx/handlers/commit.lua index fb6b97a..d7c27f3 100644 --- a/lua/gx/handlers/commit.lua +++ b/lua/gx/handlers/commit.lua @@ -2,6 +2,7 @@ local helper = require("gx.helper") local M = { -- every filetype and filename + name = "commit", filetype = nil, filename = nil, } diff --git a/lua/gx/handlers/cve.lua b/lua/gx/handlers/cve.lua index 4ef07e5..582391d 100644 --- a/lua/gx/handlers/cve.lua +++ b/lua/gx/handlers/cve.lua @@ -2,6 +2,7 @@ local helper = require("gx.helper") local M = { -- every filetype and filename + name = "cve", filetype = nil, filename = nil, } diff --git a/lua/gx/handlers/github.lua b/lua/gx/handlers/github.lua index 8be6423..dae3f8e 100644 --- a/lua/gx/handlers/github.lua +++ b/lua/gx/handlers/github.lua @@ -2,6 +2,7 @@ local helper = require("gx.helper") local M = { -- every filetype and filename + name = "github", filetype = nil, filename = nil, } diff --git a/lua/gx/handlers/markdown.lua b/lua/gx/handlers/markdown.lua index f8cdf6a..7c4cc7f 100644 --- a/lua/gx/handlers/markdown.lua +++ b/lua/gx/handlers/markdown.lua @@ -2,6 +2,7 @@ local helper = require("gx.helper") local M = { -- every filetype and filename + name = "markdown", filetype = { "markdown" }, filename = nil, } diff --git a/lua/gx/handlers/package_json.lua b/lua/gx/handlers/package_json.lua index 3f35fba..5f0ff87 100644 --- a/lua/gx/handlers/package_json.lua +++ b/lua/gx/handlers/package_json.lua @@ -2,6 +2,7 @@ local helper = require("gx.helper") local M = { -- only package.json + name = "package_json", filetype = { "json" }, filename = "package.json", } diff --git a/lua/gx/handlers/plugin.lua b/lua/gx/handlers/plugin.lua index ec41343..0020ad6 100644 --- a/lua/gx/handlers/plugin.lua +++ b/lua/gx/handlers/plugin.lua @@ -2,6 +2,7 @@ local helper = require("gx.helper") local M = { -- every filename but only lua + name = "nvim-plugin", filetype = { "lua", "vim" }, filename = nil, } diff --git a/lua/gx/handlers/search.lua b/lua/gx/handlers/search.lua index 97b4459..30462ed 100644 --- a/lua/gx/handlers/search.lua +++ b/lua/gx/handlers/search.lua @@ -2,6 +2,7 @@ local helper = require("gx.helper") local M = { -- every filetype and filename + name = "search", filetype = nil, filename = nil, } diff --git a/lua/gx/handlers/url.lua b/lua/gx/handlers/url.lua index fa257ce..7664729 100644 --- a/lua/gx/handlers/url.lua +++ b/lua/gx/handlers/url.lua @@ -2,6 +2,7 @@ local helper = require("gx.helper") local M = { -- every filetype + name = "url", filetype = nil, filename = nil, } @@ -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 diff --git a/lua/gx/init.lua b/lua/gx/init.lua index 0fedf68..703b708 100644 --- a/lua/gx/init.lua +++ b/lua/gx/init.lua @@ -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) @@ -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 @@ -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 @@ -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 diff --git a/test/spec/gx/handler_spec.lua b/test/spec/gx/handler_spec.lua index 9582388..fb2fa5f 100644 --- a/test/spec/gx/handler_spec.lua +++ b/test/spec/gx/handler_spec.lua @@ -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 @@ -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() @@ -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) @@ -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) @@ -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() @@ -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) @@ -107,10 +123,12 @@ 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) @@ -118,12 +136,16 @@ describe("test handler", function() 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() @@ -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)