Skip to content

Commit

Permalink
Fix #223 and make calling finders more robust
Browse files Browse the repository at this point in the history
  • Loading branch information
epwalsh committed Nov 9, 2023
1 parent bda0454 commit 1a8f51b
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 132 deletions.
39 changes: 26 additions & 13 deletions lua/obsidian/client.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
134 changes: 83 additions & 51 deletions lua/obsidian/command.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand All @@ -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
Expand Down Expand Up @@ -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),
Expand All @@ -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),
Expand All @@ -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
Expand All @@ -377,43 +399,53 @@ 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 {
cwd = dir,
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)
Expand Down
68 changes: 0 additions & 68 deletions lua/obsidian/util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 1a8f51b

Please sign in to comment.