From 1a8f51b8bcd8581090e1fbca21874cbe53f12d87 Mon Sep 17 00:00:00 2001 From: epwalsh Date: Thu, 9 Nov 2023 15:55:29 -0800 Subject: [PATCH] Fix #223 and make calling finders more robust --- lua/obsidian/client.lua | 39 ++++++++---- lua/obsidian/command.lua | 134 ++++++++++++++++++++++++--------------- lua/obsidian/util.lua | 68 -------------------- 3 files changed, 109 insertions(+), 132 deletions(-) diff --git a/lua/obsidian/client.lua b/lua/obsidian/client.lua index e1cc3bbcb..9cf096e70 100644 --- a/lua/obsidian/client.lua +++ b/lua/obsidian/client.lua @@ -435,25 +435,38 @@ Client.resolve_note = function(self, query) return nil end -Client._run_with_finder_backend = function(self, command_name, implementations) - local finders_order = { "telescope.nvim", "fzf-lua", "fzf.vim" } +Client._run_with_finder_backend = function(self, implementations) if self.opts.finder then - for idx, finder in ipairs(finders_order) do - if finder == self.opts.finder then - table.remove(finders_order, idx) - break + if implementations[self.opts.finder] ~= nil then + local ok, res = pcall(implementations[self.opts.finder]) + if not ok then + echo.err("error running finder '" .. self.opts.finder .. "':\n" .. tostring(res), self.opts.log_level) + return + elseif res == false then + echo.err( + "unable to load finder '" .. self.opts.finder .. "'. Are you sure it's installed?", + self.opts.log_level + ) + return + else + return res end + else + echo.err("invalid finder '" .. self.opts.finder .. "' in config", self.opts.log_level) + return end - table.insert(finders_order, 1, self.opts.finder) end - local success, err = pcall(util.run_first_supported, command_name, finders_order, implementations) - if not success then - if type(err) == "string" then - echo.err(err, self.opts.log_level) - else - error(err) + + for _, finder in ipairs { "telescope.nvim", "fzf-lua", "fzf.vim" } do + if implementations[finder] ~= nil then + local has_finder, res = implementations[finder]() + if has_finder then + return res + end end end + + echo.err "No finders available. One of 'telescope.nvim', 'fzf-lua', or 'fzf.vim' is required." end return Client diff --git a/lua/obsidian/command.lua b/lua/obsidian/command.lua index 8512c444b..c7a6aee7b 100644 --- a/lua/obsidian/command.lua +++ b/lua/obsidian/command.lua @@ -207,13 +207,14 @@ command.search = function(client, data) local base_cmd = vim.tbl_flatten { search.SEARCH_CMD, { "--smart-case", "--column", "--line-number", "--no-heading" } } - client:_run_with_finder_backend(":ObsidianSearch", { + client:_run_with_finder_backend { ["telescope.nvim"] = function() local has_telescope, telescope = pcall(require, "telescope.builtin") if not has_telescope then - util.implementation_unavailable() + return false end + -- Search with telescope.nvim local vimgrep_arguments = vim.tbl_flatten { base_cmd, { @@ -230,44 +231,52 @@ command.search = function(client, data) else telescope.live_grep { cwd = tostring(client.dir), vimgrep_arguments = vimgrep_arguments } end + + return true end, ["fzf-lua"] = function() local has_fzf_lua, fzf_lua = pcall(require, "fzf-lua") - if not has_fzf_lua then - util.implementation_unavailable() + return false end + if data.args:len() > 0 then fzf_lua.grep { cwd = tostring(client.dir), search = data.args } else fzf_lua.live_grep { cwd = tostring(client.dir), exec_empty_query = true } end + + return true end, ["fzf.vim"] = function() - -- Fall back to trying with fzf.vim - local has_fzf, _ = pcall(function() - local grep_cmd = vim.tbl_flatten { - base_cmd, - { - "--color=always", - "--", - vim.fn.shellescape(data.args), - tostring(client.dir), - }, - } + local grep_cmd = vim.tbl_flatten { + base_cmd, + { + "--color=always", + "--", + util.quote(data.args), + tostring(client.dir), + }, + } + local ok, res = pcall(function() vim.api.nvim_call_function("fzf#vim#grep", { table.concat(grep_cmd, " "), - true, vim.api.nvim_call_function("fzf#vim#with_preview", {}), - false, }) end) - if not has_fzf then - util.implementation_unavailable() + + if not ok then + if string.find(tostring(res), "Unknown function", 1, true) ~= nil then + return false + else + error(res) + end end + + return true end, - }) + } end --- Insert a template @@ -299,13 +308,14 @@ command.template = function(client, data) return end - client:_run_with_finder_backend(":ObsidianTemplate", { + client:_run_with_finder_backend { ["telescope.nvim"] = function() -- try with telescope.nvim local has_telescope, _ = pcall(require, "telescope.builtin") if not has_telescope then - util.implementation_unavailable() + return false end + local choose_template = function() local opts = { cwd = tostring(client.templates_dir), @@ -321,14 +331,18 @@ command.template = function(client, data) } require("telescope.builtin").find_files(opts) end + choose_template() + + return true end, ["fzf-lua"] = function() -- try with fzf-lua local has_fzf_lua, fzf_lua = pcall(require, "fzf-lua") if not has_fzf_lua then - util.implementation_unavailable() + return false end + local cmd = search.build_find_cmd(".", client.opts.sort_by, client.opts.sort_reversed) fzf_lua.files { cmd = util.table_params_to_str(cmd), @@ -344,30 +358,38 @@ command.template = function(client, data) end, }, } + + return true end, ["fzf.vim"] = function() - -- try with fzf - local has_fzf, _ = pcall(function() - vim.api.nvim_create_user_command("ApplyTemplate", function(path) - -- remove escaped whitespace and extract the file name - local file_path = string.gsub(path.args, "\\ ", " ") - local template = vim.fs.basename(file_path) - insert_template(template) - vim.api.nvim_del_user_command "ApplyTemplate" - end, { nargs = 1, bang = true }) - - local cmd = - search.build_find_cmd(tostring(client.templates_dir), client.opts.sort_by, client.opts.sort_reversed) - local fzf_options = { source = util.table_params_to_str(cmd), sink = "ApplyTemplate" } + vim.api.nvim_create_user_command("ApplyTemplate", function(path) + -- remove escaped whitespace and extract the file name + local file_path = string.gsub(path.args, "\\ ", " ") + local template = vim.fs.basename(file_path) + insert_template(template) + vim.api.nvim_del_user_command "ApplyTemplate" + end, { nargs = 1, bang = true }) + + local cmd = search.build_find_cmd(tostring(client.templates_dir), client.opts.sort_by, client.opts.sort_reversed) + local fzf_options = { source = util.table_params_to_str(cmd), sink = "ApplyTemplate" } + + local ok, res = pcall(function() vim.api.nvim_call_function("fzf#run", { vim.api.nvim_call_function("fzf#wrap", { fzf_options }), }) end) - if not has_fzf then - util.implementation_unavailable() + + if not ok then + if string.find(tostring(res), "Unknown function", 1, true) ~= nil then + return false + else + error(res) + end end + + return true end, - }) + } end ---Quick switch to an obsidian note @@ -377,12 +399,11 @@ end command.quick_switch = function(client, _) local dir = tostring(client.dir) - client:_run_with_finder_backend(":ObsidianQuickSwitch", { + client:_run_with_finder_backend { ["telescope.nvim"] = function() local has_telescope, telescope = pcall(require, "telescope.builtin") - if not has_telescope then - util.implementation_unavailable() + return false end -- Search with telescope.nvim telescope.find_files { @@ -390,30 +411,41 @@ command.quick_switch = function(client, _) search_file = "*.md", find_command = search.build_find_cmd(".", client.opts.sort_by, client.opts.sort_reversed), } + + return true end, ["fzf-lua"] = function() local has_fzf_lua, fzf_lua = pcall(require, "fzf-lua") - if not has_fzf_lua then - util.implementation_unavailable() + return false end + local cmd = search.build_find_cmd(".", client.opts.sort_by, client.opts.sort_reversed) fzf_lua.files { cmd = util.table_params_to_str(cmd), cwd = tostring(client.dir) } + + return true end, ["fzf.vim"] = function() - -- Fall back to trying with fzf.vim - local has_fzf, _ = pcall(function() - local cmd = search.build_find_cmd(dir, client.opts.sort_by, client.opts.sort_reversed) - local fzf_options = { source = util.table_params_to_str(cmd), sink = "e" } + local cmd = search.build_find_cmd(dir, client.opts.sort_by, client.opts.sort_reversed) + local fzf_options = { source = util.table_params_to_str(cmd), sink = "e" } + + local ok, res = pcall(function() vim.api.nvim_call_function("fzf#run", { vim.api.nvim_call_function("fzf#wrap", { fzf_options }), }) end) - if not has_fzf then - util.implementation_unavailable() + + if not ok then + if string.find(tostring(res), "Unknown function", 1, true) ~= nil then + return false + else + error(res) + end end + + return true end, - }) + } end command.link_new = function(client, data) diff --git a/lua/obsidian/util.lua b/lua/obsidian/util.lua index 6ac40b60f..0ddb94814 100644 --- a/lua/obsidian/util.lua +++ b/lua/obsidian/util.lua @@ -412,74 +412,6 @@ util.insert_template = function(name, client, location) vim.api.nvim_win_set_cursor(0, { new_cursor_row, 0 }) end -local IMPLEMENTATION_UNAVAILABLE = { "implementation_unavailable called from outside run_first_supported" } - ----Try implementations one by one in the given order, until finding one that is supported ---- ----Implementations are given as functions. If the backend of the implementation ----is unavailable (usually because a plugin is not installed), the function ----should call the `implementation_unavailable()` function from the ----`obsidian.util` module so that the next implementation in order will be ----attempted. ---- ----If the implementation's backend is installed but for some reason the ----operation fails, the error will bubble up normally and the next ----implementation will not be attempted. ---- ----@param command_name string - name of the command, used for formatting the error message ----@param order table - list of implementation names in the order in which they should be attempted ----@param implementations table - map of implementation name to implementation function -util.run_first_supported = function(command_name, order, implementations) - local unavailable = {} - local not_supported = {} - for _, impl_name in ipairs(order) do - local impl_function = implementations[impl_name] - if impl_function then - local result = { pcall(impl_function) } - if result[1] then - return select(2, unpack(result)) - elseif result[2] == IMPLEMENTATION_UNAVAILABLE then - table.insert(unavailable, impl_name) - else - error(result[2]) - end - else - table.insert(not_supported, impl_name) - end - end - - if next(unavailable) == nil then - error(command_name .. " cannot be run with " .. table.concat(not_supported, " or ")) - end - - local error_message - if #unavailable == 1 then - error_message = unavailable[1] .. " is required for " .. command_name .. " command" - elseif #unavailable then - error_message = "Either " .. table.concat(unavailable, " or ") .. " is required for " .. command_name .. " command" - end - - if next(not_supported) ~= nil then - if #not_supported == 1 then - error_message = error_message .. ". " .. not_supported[1] .. " is not a viable option for this command" - else - error_message = error_message - .. ". " - .. table.concat(not_supported, " and ") - .. " are not viable options for this command" - end - end - - error(error_message) -end - ----Should be called inside implementation functions passed to ----`run_first_supported` when the implementation's backend is unavailable ----(usually because a plugin is not installed) -util.implementation_unavailable = function() - error(IMPLEMENTATION_UNAVAILABLE) -end - util.escape_magic_characters = function(text) return text:gsub("([%(%)%.%%%+%-%*%?%[%]%^%$])", "%%%1") end