Compare commits

...

2 Commits

@ -3,8 +3,9 @@ Neovim extension for the [`zk`](https://github.com/mickael-menu/zk) plain text n
## Requirements ## Requirements
* Neovim >= 0.6.0 | `zk-nvim` | `zk` | Neovim |
* `zk` >= 0.9.0 |-----------|--------|--------|
| latest | 0.13.0 | 0.8.0 |
## Installation ## Installation
@ -18,6 +19,18 @@ Via [vim-plug](https://github.com/junegunn/vim-plug)
Plug 'mickael-menu/zk-nvim' Plug 'mickael-menu/zk-nvim'
``` ```
Via [lazy.nvim](https://github.com/folke/lazy.nvim)
```lua
{
"mickael-menu/zk-nvim",
config = function()
require("zk").setup({
-- See Setup section below
})
end
}
```
To get the best experience, it's recommended to also install either [Telescope](https://github.com/nvim-telescope/telescope.nvim) or [fzf](https://github.com/junegunn/fzf). To get the best experience, it's recommended to also install either [Telescope](https://github.com/nvim-telescope/telescope.nvim) or [fzf](https://github.com/junegunn/fzf).
## Setup ## Setup

@ -67,8 +67,8 @@ function M.new(options)
api.new(options.notebook_path, options, function(err, res) api.new(options.notebook_path, options, function(err, res)
assert(not err, tostring(err)) assert(not err, tostring(err))
if options and options.dryRun ~= true and options.edit ~= false then if options and options.dryRun ~= true and options.edit ~= false then
-- neovim does not yet support window/showDocument, therefore we handle options.edit locally -- neovim does not yet support window/showDocument, therefore we handle options.edit locally
vim.cmd("edit " .. res.path) vim.cmd("edit " .. res.path)
end end
end) end)
end end
@ -80,7 +80,9 @@ end
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zkindex ---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zkindex
function M.index(options, cb) function M.index(options, cb)
options = options or {} options = options or {}
cb = cb or function(stats) vim.notify(vim.inspect(stats)) end cb = cb or function(stats)
vim.notify(vim.inspect(stats))
end
api.index(options.notebook_path, options, function(err, stats) api.index(options.notebook_path, options, function(err, stats)
assert(not err, tostring(err)) assert(not err, tostring(err))
cb(stats) cb(stats)

@ -80,7 +80,7 @@ local function insert_link(selected, opts)
if not selected then if not selected then
location = util.get_lsp_location_from_caret() location = util.get_lsp_location_from_caret()
else else
if opts['matchSelected'] then if opts["matchSelected"] then
opts = vim.tbl_extend("force", { match = { selected_text } }, opts or {}) opts = vim.tbl_extend("force", { match = { selected_text } }, opts or {})
end end
end end
@ -102,9 +102,12 @@ local function insert_link(selected, opts)
end) end)
end end
commands.add('ZkInsertLink', function(opts) insert_link(false, opts) end, { title = 'Insert Zk link' }) commands.add("ZkInsertLink", function(opts)
commands.add('ZkInsertLinkAtSelection', function(opts) insert_link(true, opts) end, insert_link(false, opts)
{ title = 'Insert Zk link', needs_selection = true }) end, { title = "Insert Zk link" })
commands.add("ZkInsertLinkAtSelection", function(opts)
insert_link(true, opts)
end, { title = "Insert Zk link", needs_selection = true })
commands.add("ZkMatch", function(options) commands.add("ZkMatch", function(options)
local selected_text = util.get_text_in_range(util.get_selected_range()) local selected_text = util.get_text_in_range(util.get_selected_range())

@ -2,45 +2,14 @@ local M = {}
local name_fn_map = {} local name_fn_map = {}
-- NOTE: remove this once `vim.api.nvim_add_user_command` is officially released ---A thin wrapper around `vim.api.nvim_create_user_command` which parses the `params.args` of the command as a Lua table and passes it on to `fn`.
M._name_command_map = {}
-- NOTE: remove this helper once `vim.api.nvim_add_user_command` is officially released
local function nvim_add_user_command(name, command, opts)
if vim.api.nvim_add_user_command then
vim.api.nvim_add_user_command(name, command, opts)
else
assert(type(command) == "function", "Not supported in this version of Neovim.")
M._name_command_map[name] = command
vim.cmd(table.concat({
"command" .. (opts.force and "!" or ""),
opts.range and "-range" or "",
opts.nargs and ("-nargs=" .. opts.nargs) or "",
opts.complete and ("-complete=" .. opts.complete) or "",
name,
string.format("lua require('zk.commands')._name_command_map['%s']({ args = <q-args>, range = <range> })", name),
}, " "))
end
end
-- NOTE: remove this helper once `vim.api.nvim_del_user_command` is officially released
local function nvim_del_user_command(name)
if vim.api.nvim_add_user_command then
vim.api.nvim_del_user_command(name)
else
M._name_command_map[name] = nil
vim.cmd("delcommand " .. name)
end
end
---A thin wrapper around `vim.api.nvim_add_user_command` which parses the `params.args` of the command as a Lua table and passes it on to `fn`.
---@param name string ---@param name string
---@param fn function ---@param fn function
---@param opts? table {needs_selection} makes sure the command is called with a range ---@param opts? table {needs_selection} makes sure the command is called with a range
---@see vim.api.nvim_add_user_command ---@see vim.api.nvim_create_user_command
function M.add(name, fn, opts) function M.add(name, fn, opts)
opts = opts or {} opts = opts or {}
nvim_add_user_command(name, function(params) -- vim.api.nvim_add_user_command vim.api.nvim_create_user_command(name, function(params) -- vim.api.nvim_add_user_command
if opts.needs_selection then if opts.needs_selection then
assert( assert(
params.range == 2, params.range == 2,
@ -58,10 +27,10 @@ end
---Wrapper around `vim.api.nvim_del_user_command` ---Wrapper around `vim.api.nvim_del_user_command`
---@param name string ---@param name string
---@see vim.api.nvim_add_user_command ---@see vim.api.nvim_del_user_command
function M.del(name) function M.del(name)
name_fn_map[name] = nil name_fn_map[name] = nil
nvim_del_user_command(name) -- vim.api.nvim_del_user_command vim.api.nvim_del_user_command(name)
end end
return M return M

@ -11,21 +11,20 @@ function M.external_client()
client_name = "zk" client_name = "zk"
end end
local active_clients = vim.lsp.get_active_clients({name=client_name}) local active_clients = vim.lsp.get_active_clients({ name = client_name })
if active_clients == {} then if active_clients == {} then
return nil return nil
end end
-- return first lsp server that is actually in use -- return first lsp server that is actually in use
for _,v in ipairs(active_clients) do for _, v in ipairs(active_clients) do
if v.attached_buffers ~= {} then if v.attached_buffers ~= {} then
return v.id return v.id
end
end end
end
end end
---Starts an LSP client if necessary ---Starts an LSP client if necessary
function M.start() function M.start()
if not client_id then if not client_id then

@ -4,7 +4,6 @@ local conf = require("telescope.config").values
local actions = require("telescope.actions") local actions = require("telescope.actions")
local action_state = require("telescope.actions.state") local action_state = require("telescope.actions.state")
local action_utils = require("telescope.actions.utils") local action_utils = require("telescope.actions.utils")
local putils = require("telescope.previewers.utils")
local entry_display = require("telescope.pickers.entry_display") local entry_display = require("telescope.pickers.entry_display")
local previewers = require("telescope.previewers") local previewers = require("telescope.previewers")
@ -50,11 +49,10 @@ end
function M.make_note_previewer() function M.make_note_previewer()
return previewers.new_buffer_previewer({ return previewers.new_buffer_previewer({
define_preview = function(self, entry) define_preview = function(self, entry)
conf.buffer_previewer_maker( conf.buffer_previewer_maker(entry.value.absPath, self.state.bufnr, {
entry.value.absPath, bufname = entry.value.title or entry.value.path,
self.state.bufnr, winid = self.state.winid,
{ bufname = entry.value.title or entry.value.path } })
)
end, end,
}) })
end end
@ -63,64 +61,68 @@ function M.show_note_picker(notes, options, cb)
options = options or {} options = options or {}
local telescope_options = vim.tbl_extend("force", { prompt_title = options.title }, options.telescope or {}) local telescope_options = vim.tbl_extend("force", { prompt_title = options.title }, options.telescope or {})
pickers.new(telescope_options, { pickers
finder = finders.new_table({ .new(telescope_options, {
results = notes, finder = finders.new_table({
entry_maker = M.create_note_entry_maker(options), results = notes,
}), entry_maker = M.create_note_entry_maker(options),
sorter = conf.file_sorter(options), }),
previewer = M.make_note_previewer(), sorter = conf.file_sorter(options),
attach_mappings = function(prompt_bufnr) previewer = M.make_note_previewer(),
actions.select_default:replace(function() attach_mappings = function(prompt_bufnr)
if options.multi_select then actions.select_default:replace(function()
local selection = {} if options.multi_select then
action_utils.map_selections(prompt_bufnr, function(entry, _) local selection = {}
table.insert(selection, entry.value) action_utils.map_selections(prompt_bufnr, function(entry, _)
end) table.insert(selection, entry.value)
if vim.tbl_isempty(selection) then end)
selection = { action_state.get_selected_entry().value } if vim.tbl_isempty(selection) then
selection = { action_state.get_selected_entry().value }
end
actions.close(prompt_bufnr)
cb(selection)
else
actions.close(prompt_bufnr)
cb(action_state.get_selected_entry().value)
end end
actions.close(prompt_bufnr) end)
cb(selection) return true
else end,
actions.close(prompt_bufnr) })
cb(action_state.get_selected_entry().value) :find()
end
end)
return true
end,
}):find()
end end
function M.show_tag_picker(tags, options, cb) function M.show_tag_picker(tags, options, cb)
options = options or {} options = options or {}
local telescope_options = vim.tbl_extend("force", { prompt_title = options.title }, options.telescope or {}) local telescope_options = vim.tbl_extend("force", { prompt_title = options.title }, options.telescope or {})
pickers.new(telescope_options, { pickers
finder = finders.new_table({ .new(telescope_options, {
results = tags, finder = finders.new_table({
entry_maker = M.create_tag_entry_maker(options), results = tags,
}), entry_maker = M.create_tag_entry_maker(options),
sorter = conf.generic_sorter(options), }),
attach_mappings = function(prompt_bufnr, _) sorter = conf.generic_sorter(options),
actions.select_default:replace(function() attach_mappings = function(prompt_bufnr, _)
if options.multi_select then actions.select_default:replace(function()
local selection = {} if options.multi_select then
action_utils.map_selections(prompt_bufnr, function(entry, _) local selection = {}
table.insert(selection, entry.value) action_utils.map_selections(prompt_bufnr, function(entry, _)
end) table.insert(selection, entry.value)
if vim.tbl_isempty(selection) then end)
selection = { action_state.get_selected_entry().value } if vim.tbl_isempty(selection) then
selection = { action_state.get_selected_entry().value }
end
actions.close(prompt_bufnr)
cb(selection)
else
cb(action_state.get_selected_entry().value)
end end
actions.close(prompt_bufnr) end)
cb(selection) return true
else end,
cb(action_state.get_selected_entry().value) })
end :find()
end)
return true
end,
}):find()
end end
return M return M

@ -9,7 +9,7 @@ local M = {}
-- Some path utilities -- Some path utilities
M.path = (function() M.path = (function()
local is_windows = uv.os_uname().version:match 'Windows' local is_windows = uv.os_uname().version:match("Windows")
local function exists(filename) local function exists(filename)
local stat = uv.fs_stat(filename) local stat = uv.fs_stat(filename)
@ -18,31 +18,31 @@ M.path = (function()
local function is_fs_root(path) local function is_fs_root(path)
if is_windows then if is_windows then
return path:match '^%a:$' return path:match("^%a:$")
else else
return path == '/' return path == "/"
end end
end end
local function dirname(path) local function dirname(path)
local strip_dir_pat = '/([^/]+)$' local strip_dir_pat = "/([^/]+)$"
local strip_sep_pat = '/$' local strip_sep_pat = "/$"
if not path or #path == 0 then if not path or #path == 0 then
return return
end end
local result = path:gsub(strip_sep_pat, ''):gsub(strip_dir_pat, '') local result = path:gsub(strip_sep_pat, ""):gsub(strip_dir_pat, "")
if #result == 0 then if #result == 0 then
if is_windows then if is_windows then
return path:sub(1, 2):upper() return path:sub(1, 2):upper()
else else
return '/' return "/"
end end
end end
return result return result
end end
local function path_join(...) local function path_join(...)
return table.concat(vim.tbl_flatten { ... }, '/') return table.concat(vim.tbl_flatten({ ... }), "/")
end end
-- Iterate the path until we find the rootdir. -- Iterate the path until we find the rootdir.
@ -70,7 +70,7 @@ M.path = (function()
end)() end)()
function M.search_ancestors(startpath, func) function M.search_ancestors(startpath, func)
validate { func = { func, 'f' } } validate({ func = { func, "f" } })
if func(startpath) then if func(startpath) then
return startpath return startpath
end end
@ -89,7 +89,7 @@ function M.search_ancestors(startpath, func)
end end
function M.root_pattern(...) function M.root_pattern(...)
local patterns = vim.tbl_flatten { ... } local patterns = vim.tbl_flatten({ ... })
local function matcher(path) local function matcher(path)
for _, pattern in ipairs(patterns) do for _, pattern in ipairs(patterns) do
for _, p in ipairs(vim.fn.glob(M.path.join(path, pattern), true, true)) do for _, p in ipairs(vim.fn.glob(M.path.join(path, pattern), true, true)) do

@ -8,11 +8,8 @@ local M = {}
---@param options? table containing {picker}, {title}, {multi_select} keys ---@param options? table containing {picker}, {title}, {multi_select} keys
---@param cb function ---@param cb function
function M.pick_notes(notes, options, cb) function M.pick_notes(notes, options, cb)
options = vim.tbl_extend( options =
"force", vim.tbl_extend("force", { title = "Zk Notes", picker = config.options.picker, multi_select = true }, options or {})
{ title = "Zk Notes", picker = config.options.picker, multi_select = true },
options or {}
)
require("zk.pickers." .. options.picker).show_note_picker(notes, options, cb) require("zk.pickers." .. options.picker).show_note_picker(notes, options, cb)
end end
@ -22,11 +19,8 @@ end
---@param options? table containing {picker}, {title}, {multi_select} keys ---@param options? table containing {picker}, {title}, {multi_select} keys
---@param cb function ---@param cb function
function M.pick_tags(tags, options, cb) function M.pick_tags(tags, options, cb)
options = vim.tbl_extend( options =
"force", vim.tbl_extend("force", { title = "Zk Tags", picker = config.options.picker, multi_select = true }, options or {})
{ title = "Zk Tags", picker = config.options.picker, multi_select = true },
options or {}
)
require("zk.pickers." .. options.picker).show_tag_picker(tags, options, cb) require("zk.pickers." .. options.picker).show_tag_picker(tags, options, cb)
end end

@ -47,7 +47,7 @@ function M.get_lsp_location_from_selection()
local params = vim.lsp.util.make_given_range_params() local params = vim.lsp.util.make_given_range_params()
return { return {
uri = params.textDocument.uri, uri = params.textDocument.uri,
range = M.get_selected_range() -- workaround for neovim 0.6.1 bug (https://github.com/mickael-menu/zk-nvim/issues/19) range = M.get_selected_range(), -- workaround for neovim 0.6.1 bug (https://github.com/mickael-menu/zk-nvim/issues/19)
} }
end end
@ -62,18 +62,17 @@ end
local function fix_cursor_location(location) local function fix_cursor_location(location)
-- Cursor LSP position is a little weird. -- Cursor LSP position is a little weird.
-- It inserts one line down. Seems like an off by one error somewhere -- It inserts one line down. Seems like an off by one error somewhere
local pos = location['range']['start'] local pos = location["range"]["start"]
pos['line'] = pos['line'] - 1 pos["line"] = pos["line"] - 1
pos['character'] = pos['character'] + 1 pos["character"] = pos["character"] + 1
location['range']['start'] = pos location["range"]["start"] = pos
location['range']['end'] = pos location["range"]["end"] = pos
return location return location
end end
---Makes an LSP location object from the caret position in the current buffer. ---Makes an LSP location object from the caret position in the current buffer.
-- --
---@return table LSP location object ---@return table LSP location object
@ -87,8 +86,8 @@ function M.get_lsp_location_from_caret()
uri = params.textDocument.uri, uri = params.textDocument.uri,
range = { range = {
start = position, start = position,
["end"] = position ["end"] = position,
} },
}) })
end end
@ -105,7 +104,7 @@ function M.get_text_in_range(range)
if vim.tbl_isempty(lines) then if vim.tbl_isempty(lines) then
return nil return nil
end end
local MAX_STRING_SUB_INDEX = 2^31 - 1 -- LuaJIT only supports 32bit integers for `string.sub` (in block selection B.character is 2^31) local MAX_STRING_SUB_INDEX = 2 ^ 31 - 1 -- LuaJIT only supports 32bit integers for `string.sub` (in block selection B.character is 2^31)
lines[#lines] = string.sub(lines[#lines], 1, math.min(B.character, MAX_STRING_SUB_INDEX)) lines[#lines] = string.sub(lines[#lines], 1, math.min(B.character, MAX_STRING_SUB_INDEX))
lines[1] = string.sub(lines[1], math.min(A.character + 1, MAX_STRING_SUB_INDEX)) lines[1] = string.sub(lines[1], math.min(A.character + 1, MAX_STRING_SUB_INDEX))
return table.concat(lines, "\n") return table.concat(lines, "\n")

Loading…
Cancel
Save