major refactoring, custom commands, configurable picker (#14)

* new way of defining user commands

* add FZF picker

* support vim.ui.select as picker

* refactor

* update readme

* Improved architecture, according to https://github.com/mickael-menu/zk-nvim/pull/14#pullrequestreview-840097886

* fix lsp not attaching buffers

* make "select" the default picker

* update README

* simplify picker api, make multi_select=true the default

* rename action->cb

* amend

* rename path->notebook_path and move into options arg

* fix

* Update README

* easier command syntax

* use new command api

* clean up commands

* easier command syntax

* doc

* update README

* add more code documentation

* update README

* remove lspconfig dependency

* add autogenerated vimdoc (md2vim)

* fix command viml syntax

* better command syntax

* correctly (not) handle character encoding when getting selected text

* README fixes

* Remove `zk.edit_from_tags`

* fixes, also remove `:Telescope zk tags`

* update vimdoc

Co-authored-by: Mickaël Menu <mickael.menu@gmail.com>
pull/15/head
Karim Abou Zeid 2 years ago committed by GitHub
parent d5681f0f49
commit b0ce17b020
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,50 +1,42 @@
# zk-nvim
Neovim extension for [zk](https://github.com/mickael-menu/zk).
Neovim extension for the [`zk`](https://github.com/mickael-menu/zk) plain text note-taking assistant.
## Install
## Installation
Using [packer.nvim](https://github.com/wbthomason/packer.nvim)
```lua
use {
"mickael-menu/zk-nvim",
requires = { "neovim/nvim-lspconfig" }
}
This plugin requires Neovim v0.6.0 or later.
-- Telescope is optional
use {
'nvim-telescope/telescope.nvim',
requires = { {'nvim-lua/plenary.nvim'} }
}
Via [packer.nvim](https://github.com/wbthomason/packer.nvim)
```lua
use("mickael-menu/zk-nvim")
```
Using [vim-plug](https://github.com/junegunn/vim-plug)
Via [vim-plug](https://github.com/junegunn/vim-plug)
```viml
Plug "mickael-menu/zk-nvim"
Plug "neovim/nvim-lspconfig"
Plug 'nvim-telescope/telescope.nvim' " optional
Plug 'nvim-lua/plenary.nvim' " optional, dependency for Telescope
```
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
> :warning: This plugin will setup and start the LSP server for you, do *not* call `require("lspconfig").zk.setup()`.
```lua
require("zk").setup()
require("telescope").load_extension("zk")
```
> :warning: This plugin will setup and start the LSP server for you, do *not* call `require("lspconfig").zk.setup()`.
**Default configuration**
**The default configuration**
```lua
require("zk").setup({
-- create user commands such as :ZkNew
create_user_commands = true,
-- can be "telescope", "fzf" or "select" (`vim.ui.select`)
-- it's recommended to use "telescope" or "fzf"
picker = "select",
lsp = {
-- `config` is passed to `vim.lsp.start_client(config)`
config = {
cmd = { "zk", "lsp" },
name = "zk",
-- init_options = ...
-- on_attach = ...
-- etc, see `:h vim.lsp.start_client()`
},
@ -58,6 +50,9 @@ require("zk").setup({
})
```
Note that the `setup` function will not add any key mappings for you.
If you want to add key mappings, see the [example mappings](#example-mappings).
### Notebook Directory Discovery
When you run a notebook command, this plugin will look for a notebook in the following places and order:
1. the current buffer path (i.e. the file you are currently editing),
@ -70,143 +65,239 @@ It is worth noting that for some notebook commands you can explicitly specify a
An explicitly provided path will always take precedence and override the automatic notebook discovery.
However, this is always optional, and usually not necessary.
## Commands
## Getting Started
After you have installed the plugin and added the setup code to your config, you are good to go. If you are not familiar with `zk`, we recommend you to also read the [`zk` docs](https://github.com/mickael-menu/zk/tree/main/docs).
When using the default config, the `zk` LSP client will automatically attach itself to buffers inside your notebook and provide capabilities like completion, hover and go-to-definition; see https://github.com/mickael-menu/zk/issues/22 for a full list of what is supported.
Try out different [commands](#built-in-commands) such as `:ZkNotes` or `:ZkNew`, see what they can do, and learn as you go.
## Built-in Commands
### VimL
```vim
" Indexes the notebook
" params
" (optional) additional options, see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zkindex
:ZkIndex [<options>]
:ZkIndex [{options}]
```
" Creates a new note
```vim
" Creates and edits a new note
" params
" (optional) additional options, see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zknew
:ZkNew [<options>]
:ZkNew [{options}]
```
```vim
" Creates a new note and uses the last visual selection as the title while replacing the selection with a link to the new note
" params
" (optional) additional options, see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zknew
:ZkNewLink [<options>]
:'<,'>ZkNewFromTitleSelection [{options}]
```
" Opens a Telescope picker
```vim
" Creates a new note and uses the last visual selection as the content while replacing the selection with a link to the new note
" params
" (optional) additional options, see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zknew
:'<,'>ZkNewFromContentSelection [{options}]
```
```vim
" cd into the notebook root
" params
" (optional) additional options
:ZkCd [{options}]
```
```vim
" Opens a notes picker
" params
" (optional) additional options, see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
:ZkList [<options>]
:ZkNotes [{options}]
```
" Opens a Telescope picker
```vim
" Opens a notes picker for the backlinks of the current buffer
" params
" (optional) additional options, see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
:ZkBacklinks [{options}]
```
```vim
" Opens a notes picker for the outbound links of the current buffer
" params
" (optional) additional options, see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
:ZkLinks [{options}]
```
```vim
" Opens a notes picker, filters for notes that match the text in the last visual selection
" params
" (optional) additional options, see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
:'<,'>ZkMatch [{options}]
```
```vim
" Opens a notes picker, filters for notes with the selected tags
" params
" (optional) additional options, see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zktaglist
:ZkTagList [<options>]
:ZkTags [{options}]
```
where `options` can be any valid *Lua* expression that evaluates to a table.
The `options` parameter can be any valid *Lua* expression that evaluates to a table.
For a list of available options, refer to the [`zk` docs](https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#custom-commands).
In addition, `options.notebook_path` can be used to explicitly specify a notebook by providing a path to any file or directory within the notebook; see [Notebook Directory Discovery](#notebook-directory-discovery).
*Examples:*
```vim
:ZkNew { dir = "daily", date = "yesterday" }
:ZkList { createdAfter = "3 days ago", tags = { "work" } }
:'<,'>ZkNewLink " this will use your last visual mode selection. Note that you *must* call this command with the '<,'> range.
:ZkNotes { createdAfter = "3 days ago", tags = { "work" } }
:'<,'>ZkNewFromTitleSelection " this will use your last visual mode selection. Note that you *must* call this command with the '<,'> range.
:ZkCd
```
### Lua
```lua
---Indexes the notebook
--
---@param path? string path to explicitly specify the notebook
---@param options table additional options
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zkindex
require("zk").index(path, options)
---
**Via Lua**
---Creates and opens a new note
--
---@param path? string path to explicitly specify the notebook
---@param options table additional options
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zknew
require("zk").new(path, options)
You can access the underlying Lua function of a commands, with `require("zk.commands").get`.
---Creates a new note and uses the last visual selection as the title while replacing the selection with a link to the new note
--
---@param path? string path to explicitly specify the notebook
---@param options table additional options
require("zk").new_link(path, options)
*Examples:*
```lua
require("zk.commands").get("ZkNew")({ dir = "daily" })
require("zk.commands").get("ZkNotes")({ createdAfter = "3 days ago", tags = { "work" } })
require("zk.commands").get("ZkNewFromTitleSelection")() -- this will use your last visual mode selection
```
---Opens a Telescope picker
--
---@param path? string path to explicitly specify the notebook
---@param options table additional options
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
require("zk").list(path, options)
## Custom Commands
---Opens a Telescope picker
--
---@param path? string path to explicitly specify the notebook
---@param options table additional options
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zktaglist
require("zk").tag.list(path, options)
```lua
---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 fn function
---@param opts? table {needs_selection} makes sure the command is called with a range
---@see vim.api.nvim_add_user_command
require("zk.commands").add(name, fn, opts)
```
*Examples:*
*Example 1:*
Let us add a custom `:ZkOrphans` command that will list all notes that are orphans, i.e. not referenced by any other note.
```lua
require("zk").new(nil, { dir = "daily" })
require("zk").list(nil, { createdAfter = "3 days ago", tags = { "work" } })
require("zk").new_link() -- this will use your last visual mode selection
local zk = require("zk")
local commands = require("zk.commands")
commands.add("ZkOrphans", function(options)
options = vim.tbl_extend("force", { orphan = true }, options or {})
zk.edit(options, { title = "Zk Orphans" })
end)
```
This adds the `:ZkOrphans [{options}]` vim user command, which accepts an `options` Lua table as an argument.
We can execute it like this `:ZkOrphans { tags = "work" }` for example.
As you can see, the `path` is optional, and can usually be omitted; see [Notebook Directory Discovery](#notebook-directory-discovery).
> Note: The `zk.edit` function is from the [high-level API](#high-level-api), which also contains other functions that might be useful for your custom commands.
### Telescope
```vim
:Telescope zk notes
:Telescope zk orphans
:Telescope zk backlinks
:Telescope zk links
:Telescope zk related
:Telescope zk tags
*Example 2:*
Chances are that this will not be our only custom command following this pattern.
So let's also add a `:ZkRecents` command and make the pattern a bit more reusable.
```lua
local zk = require("zk")
local commands = require("zk.commands")
local function make_edit_fn(defaults, picker_options)
return function(options)
options = vim.tbl_extend("force", defaults, options or {})
zk.edit(options, picker_options)
end
end
commands.add("ZkOrphans", make_edit_fn({ orphan = true }, { title = "Zk Orphans" }))
commands.add("ZkRecents", make_edit_fn({ createdAfter = "2 weeks ago" }, { title = "Zk Recents" }))
```
or via Lua
## High-level API
The high-level API is inspired by the commands provided by the `zk` CLI tool; see `zk --help`.
It's mainly used for the implementation of built-in and custom commands.
```lua
require('telescope').extensions.zk.notes()
require('telescope').extensions.zk.orphans()
require('telescope').extensions.zk.backlinks()
require('telescope').extensions.zk.links()
require('telescope').extensions.zk.related()
require('telescope').extensions.zk.tags()
---Cd into the notebook root
--
---@param options? table
require("zk").cd(options)
```
The Telescope pickers also allow you to explicitly specify a notebook like so `:Telescope zk notes path=/foo/bar` or so `require('telescope').extensions.zk.notes({ path = '/foo/bar'})`.
However, specifying a `path` is optional, and is usually not necessary; see [Notebook Directory Discovery](#notebook-directory-discovery).
```lua
---Creates and edits a new note
--
---@param options? table additional options
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zknew
require("zk").new(options)
```
You can even pass the same additional options to the Telescope pickers as described in [list and tag list commands](#commands).
```lua
---Indexes the notebook
--
---@param options? table additional options
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zkindex
require("zk").index(options)
```
*Example VimL:*
```vim
:Telescope zk notes createdAfter=3\ days\ ago
```lua
---Opens a notes picker, and calls the callback with the selection
--
---@param options? table additional options
---@param picker_options? table options for the picker
---@param cb function
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
---@see zk.ui.pick_notes
require("zk").pick_notes(options, picker_options, cb)
```
*Example Lua:*
```lua
require('telescope').extensions.zk.notes({ createdAfter = "3 days ago", tags = { "work" } })
---Opens a tags picker, and calls the callback with the selection
--
---@param options? table additional options
---@param picker_options? table options for the picker
---@param cb function
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zktaglist
---@see zk.ui.pick_tags
require("zk").pick_tags(options, picker_options, cb)
```
As you can see, the VimL API is a bit constrained. Whitespace must be escaped and lists and dictionaries are not supported.
It is therefore recommended to use the `:ZkList` and `:ZkTagList` [commands](#commands) instead.
```lua
---Opens a notes picker, and edits the selected notes
--
---@param options? table additional options
---@param picker_options? table options for the picker
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
---@see zk.ui.pick_notes
require("zk").edit(options, picker_options)
```
## API
The functions in the API module give you maximum flexibility and provide only a thin Lua friendly layer around zk's API.
You can use it to write your own specialized functions for interacting with zk.
The functions in the API module give you maximum flexibility and provide only a thin Lua friendly layer around `zk`'s LSP API.
You can use it to write your own specialized functions for interacting with `zk`.
```lua
-- https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zkindex
-- path and options are optional
---@param path? string path to explicitly specify the notebook
---@param options? table additional options
---@param cb function callback function
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zkindex
require("zk").api.index(path, options, function(stats)
-- do something with the stats
end)
```
```lua
-- https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zknew
-- path and options are optional
---@param path? string path to explicitly specify the notebook
---@param options? table additional options
---@param cb function callback function
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zknew
require("zk").api.new(path, options, function(res)
file_path = res.path
-- do something with the new file path
@ -214,75 +305,117 @@ end)
```
```lua
-- https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
-- path is optional, options.select is required
-- options = { select = { "title", "absPath", "rawContent" }, sort = { "created" } }
---@param path? string path to explicitly specify the notebook
---@param options table additional options
---@param cb function callback function
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
require("zk").api.list(path, options, function(notes)
-- do something with the notes
end)
```
```lua
-- https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zktaglist
-- path and options are optional
---@param path? string path to explicitly specify the notebook
---@param options? table additional options
---@param cb function callback function
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zktaglist
require("zk").api.tag.list(path, options, function(tags)
-- do something with the tags
end)
```
## Pickers
Used by the [high-level API](#high-level-api) to display the results of the [API](#api).
```lua
---Opens a notes picker
--
---@param notes list
---@param options? table containing {picker}, {title}, {multi_select} keys
---@param cb function
require("zk.ui").pick_notes(notes, options, cb)
```
```lua
---Opens a tags picker
--
---@param tags list
---@param options? table containing {picker}, {title}, {multi_select} keys
---@param cb function
require("zk.ui").pick_tags(tags, options, cb)
```
```lua
---To be used in zk.api.list as the `selection` in the additional options table
--
---@param options table the same options that are use for pick_notes
---@return table api selection
require("zk.ui").get_pick_notes_list_api_selection(options)
```
## Example Mappings
```lua
vim.api.nvim_set_keymap("n", "<Leader>zc", "<cmd>ZkNew<CR>", { noremap = true })
vim.api.nvim_set_keymap("x", "<Leader>zc", ":'<'>ZkNewFromTitleSelection<CR>", { noremap = true })
vim.api.nvim_set_keymap("n", "<Leader>zn", "<cmd>ZkNotes<CR>", { noremap = true })
vim.api.nvim_set_keymap("n", "<Leader>zb", "<cmd>ZkBacklinks<CR>", { noremap = true })
vim.api.nvim_set_keymap("n", "<Leader>zl", "<cmd>ZkLinks<CR>", { noremap = true })
vim.api.nvim_set_keymap("n", "<Leader>zt", "<cmd>ZkTags<CR>", { noremap = true })
```
# Miscellaneous
## Syntax Highlighting Tips
These code snippets only work if you use Neovim's built-in Markdown syntax highlighting.
*Proper syntax highlighting for Wikilinks.* [[This is a wiki link]].
```vim
autocmd Filetype markdown syn region markdownWikiLink matchgroup=markdownLinkDelimiter start="\[\[" end="\]\]" contains=markdownUrl keepend oneline concealends
```
*Conceal support for normal Markdown links.* Overwrite the syntax regions like so
```vim
autocmd Filetype markdown syn region markdownLinkText matchgroup=markdownLinkTextDelimiter start="!\=\[\%(\%(\_[^][]\|\[\_[^][]*\]\)*]\%( \=[[(]\)\)\@=" end="\]\%( \=[[(]\)\@=" nextgroup=markdownLink,markdownId skipwhite contains=@markdownInline,markdownLineStart concealends
autocmd Filetype markdown syn region markdownLink matchgroup=markdownLinkDelimiter start="(" end=")" contains=markdownUrl keepend contained conceal
```
## nvim-lsp-installer
> Not recommended, instead install the [`zk`](https://github.com/mickael-menu/zk) CLI tool and make it available in your `$PATH`.
If you insist to use nvim-lsp-installer for `zk`, the following code snippet should guide you on how to setup the `zk` server when installed via nvim-lsp-installer.
```lua
require("nvim-lsp-installer").on_server_ready(function(server)
local opts = {
-- customize your options as usual
--
-- on_attach = ...
-- etc, see `:h vim.lsp.start_client()`
}
if server.name == "zk" then
require("zk").setup({
lsp = {
config = vim.tbl_extend("force", server:get_default_options(), opts),
},
})
else
server:setup(opts)
end
end)
```
-- Create notes / links
vim.api.nvim_set_keymap(
"n",
"<Leader>zc",
"<cmd>lua require('zk').new()<CR>",
{ noremap = true }
)
vim.api.nvim_set_keymap(
"x",
"<Leader>zc",
"<esc><cmd>lua require('zk').new_link()<CR>",
{ noremap = true }
)
-- Show Telescope pickers
vim.api.nvim_set_keymap(
"n",
"<Leader>zn",
"<cmd>lua require('telescope').extensions.zk.notes()<CR>",
{ noremap = true }
)
vim.api.nvim_set_keymap(
"n",
"<Leader>zo",
"<cmd>lua require('telescope').extensions.zk.orphans()<CR>",
{ noremap = true }
)
vim.api.nvim_set_keymap(
"n",
"<Leader>zb",
"<cmd>lua require('telescope').extensions.zk.backlinks()<CR>",
{ noremap = true }
)
vim.api.nvim_set_keymap(
"n",
"<Leader>zl",
"<cmd>lua require('telescope').extensions.zk.links()<CR>",
{ noremap = true }
)
vim.api.nvim_set_keymap(
"n",
"<Leader>zt",
"<cmd>lua require('telescope').extensions.zk.tags()<CR>",
{ noremap = true }
)
## Telescope Plugin
> Not recommended, instead just use the [:ZkNotes command](#built-in-commands).
It's possible (but unnecessary) to also load the notes picker as a telescope plugin.
```lua
require("telescope").load_extension("zk")
```
```vim
:Telescope zk notes
:Telescope zk notes createdAfter=3\ days\ ago
```

@ -0,0 +1,433 @@
zk.txt *zk.txt* Neovim extension for the `zk` plain text note-taking assistant.
================================================================================
CONTENTS *zk-contents*
1. zk-nvim............................................................|zk-zk-nvim|
1.1. Installation............................................|zk-installation|
1.2. Setup..........................................................|zk-setup|
1.2.1. Notebook Directory Discovery......|zk-notebook_directory_discovery|
1.3. Getting Started......................................|zk-getting_started|
1.4. Built-in Commands..................................|zk-built-in_commands|
1.5. Custom Commands......................................|zk-custom_commands|
1.6. High-level API........................................|zk-high-level_api|
1.7. API..............................................................|zk-api|
1.8. Pickers......................................................|zk-pickers|
1.9. Example Mappings....................................|zk-example_mappings|
2. Miscellaneous................................................|zk-miscellaneous|
2.1. Syntax Highlighting Tips....................|zk-syntax_highlighting_tips|
2.2. nvim-lsp-installer................................|zk-nvim-lsp-installer|
2.3. Telescope Plugin....................................|zk-telescope_plugin|
================================================================================
ZK-NVIM *zk-zk-nvim*
Neovim extension for the `zk` (https://github.com/mickael-menu/zk) plain text note-taking assistant.
--------------------------------------------------------------------------------
INSTALLATION *zk-installation*
This plugin requires Neovim v0.6.0 or later.
Via packer.nvim (https://github.com/wbthomason/packer.nvim)
>
use("mickael-menu/zk-nvim")
<
Via vim-plug (https://github.com/junegunn/vim-plug)
>
Plug "mickael-menu/zk-nvim"
<
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 *zk-setup*
>
:warning: This plugin will setup and start the LSP server for you, do not call `require("lspconfig").zk.setup()`.
<
>
require("zk").setup()
<
The default configuration
>
require("zk").setup({
-- can be "telescope", "fzf" or "select" (`vim.ui.select`)
-- it's recommended to use "telescope" or "fzf"
picker = "select",
lsp = {
-- `config` is passed to `vim.lsp.start_client(config)`
config = {
cmd = { "zk", "lsp" },
name = "zk",
-- on_attach = ...
-- etc, see `:h vim.lsp.start_client()`
},
-- automatically attach buffers in a zk notebook that match the given filetypes
auto_attach = {
enabled = true,
filetypes = { "markdown" },
},
},
})
<
Note that the `setup` function will not add any key mappings for you.
If you want to add key mappings, see the example mappings (#example-mappings).
NOTEBOOK DIRECTORY DISCOVERY *zk-notebook_directory_discovery*
When you run a notebook command, this plugin will look for a notebook in the following places and order:
1. the current buffer path (i.e. the file you are currently editing),
2. the current working directory,
3. the `$ZK_NOTEBOOK_DIR` environment variable.
We recommend you to export the `$ZK_NOTEBOOK_DIR` environment variable, so that a notebook can always be found.
It is worth noting that for some notebook commands you can explicitly specify a notebook by providing a path to any file or directory within the notebook.
An explicitly provided path will always take precedence and override the automatic notebook discovery.
However, this is always optional, and usually not necessary.
--------------------------------------------------------------------------------
GETTING STARTED *zk-getting_started*
After you have installed the plugin and added the setup code to your config, you are good to go. If you are not familiar with `zk`, we recommend you to also read the `zk` docs (https://github.com/mickael-menu/zk/tree/main/docs).
When using the default config, the `zk` LSP client will automatically attach itself to buffers inside your notebook and provide capabilities like completion, hover and go-to-definition; see https://github.com/mickael-menu/zk/issues/22 for a full list of what is supported.
Try out different commands (#built-in-commands) such as `:ZkNotes` or `:ZkNew`, see what they can do, and learn as you go.
--------------------------------------------------------------------------------
BUILT-IN COMMANDS *zk-built-in_commands*
>
" Indexes the notebook
" params
" (optional) additional options, see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zkindex
:ZkIndex [{options}]
<
>
" Creates and edits a new note
" params
" (optional) additional options, see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zknew
:ZkNew [{options}]
<
>
" Creates a new note and uses the last visual selection as the title while replacing the selection with a link to the new note
" params
" (optional) additional options, see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zknew
:'<,'>ZkNewFromTitleSelection [{options}]
<
>
" Creates a new note and uses the last visual selection as the content while replacing the selection with a link to the new note
" params
" (optional) additional options, see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zknew
:'<,'>ZkNewFromContentSelection [{options}]
<
>
" cd into the notebook root
" params
" (optional) additional options
:ZkCd [{options}]
<
>
" Opens a notes picker
" params
" (optional) additional options, see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
:ZkNotes [{options}]
<
>
" Opens a notes picker for the backlinks of the current buffer
" params
" (optional) additional options, see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
:ZkBacklinks [{options}]
<
>
" Opens a notes picker for the outbound links of the current buffer
" params
" (optional) additional options, see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
:ZkLinks [{options}]
<
>
" Opens a notes picker, filters for notes that match the text in the last visual selection
" params
" (optional) additional options, see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
:'<,'>ZkMatch [{options}]
<
>
" Opens a notes picker, filters for notes with the selected tags
" params
" (optional) additional options, see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zktaglist
:ZkTags [{options}]
<
The `options` parameter can be any valid Lua expression that evaluates to a table.
For a list of available options, refer to the `zk` docs (https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#custom-commands).
In addition, `options.notebook_path` can be used to explicitly specify a notebook by providing a path to any file or directory within the notebook; see Notebook Directory Discovery (#notebook-directory-discovery).
Examples:
>
:ZkNew { dir = "daily", date = "yesterday" }
:ZkNotes { createdAfter = "3 days ago", tags = { "work" } }
:'<,'>ZkNewFromTitleSelection " this will use your last visual mode selection. Note that you *must* call this command with the '<,'> range.
:ZkCd
<
--------------------------------------------------------------------------------
Via Lua
You can access the underlying Lua function of a commands, with `require("zk.commands").get`.
Examples:
>
require("zk.commands").get("ZkNew")({ dir = "daily" })
require("zk.commands").get("ZkNotes")({ createdAfter = "3 days ago", tags = { "work" } })
require("zk.commands").get("ZkNewFromTitleSelection")() -- this will use your last visual mode selection
<
--------------------------------------------------------------------------------
CUSTOM COMMANDS *zk-custom_commands*
>
---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 fn function
---@param opts? table {needs_selection} makes sure the command is called with a range
---@see vim.api.nvim_add_user_command
require("zk.commands").add(name, fn, opts)
<
Example 1:
Let us add a custom `:ZkOrphans` command that will list all notes that are orphans, i.e. not referenced by any other note.
>
local zk = require("zk")
local commands = require("zk.commands")
commands.add("ZkOrphans", function(options)
options = vim.tbl_extend("force", { orphan = true }, options or {})
zk.edit(options, { title = "Zk Orphans" })
end)
<
This adds the `:ZkOrphans [{options}]` vim user command, which accepts an `options` Lua table as an argument.
We can execute it like this `:ZkOrphans { tags = "work" }` for example.
>
Note: The `zk.edit` function is from the high-level API (#high-level-api), which also contains other functions that might be useful for your custom commands.
<
Example 2:
Chances are that this will not be our only custom command following this pattern.
So let's also add a `:ZkRecents` command and make the pattern a bit more reusable.
>
local zk = require("zk")
local commands = require("zk.commands")
local function make_edit_fn(defaults, picker_options)
return function(options)
options = vim.tbl_extend("force", defaults, options or {})
zk.edit(options, picker_options)
end
end
commands.add("ZkOrphans", make_edit_fn({ orphan = true }, { title = "Zk Orphans" }))
commands.add("ZkRecents", make_edit_fn({ createdAfter = "2 weeks ago" }, { title = "Zk Recents" }))
<
--------------------------------------------------------------------------------
HIGH-LEVEL API *zk-high-level_api*
The high-level API is inspired by the commands provided by the `zk` CLI tool; see `zk --help`.
It's mainly used for the implementation of built-in and custom commands.
>
---Cd into the notebook root
--
---@param options? table
require("zk").cd(options)
<
>
---Creates and edits a new note
--
---@param options? table additional options
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zknew
require("zk").new(options)
<
>
---Indexes the notebook
--
---@param options? table additional options
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zkindex
require("zk").index(options)
<
>
---Opens a notes picker, and calls the callback with the selection
--
---@param options? table additional options
---@param picker_options? table options for the picker
---@param cb function
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
---@see zk.ui.pick_notes
require("zk").pick_notes(options, picker_options, cb)
<
>
---Opens a tags picker, and calls the callback with the selection
--
---@param options? table additional options
---@param picker_options? table options for the picker
---@param cb function
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zktaglist
---@see zk.ui.pick_tags
require("zk").pick_tags(options, picker_options, cb)
<
>
---Opens a notes picker, and edits the selected notes
--
---@param options? table additional options
---@param picker_options? table options for the picker
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
---@see zk.ui.pick_notes
require("zk").edit(options, picker_options)
<
--------------------------------------------------------------------------------
API *zk-api*
The functions in the API module give you maximum flexibility and provide only a thin Lua friendly layer around `zk`'s LSP API.
You can use it to write your own specialized functions for interacting with `zk`.
>
---@param path? string path to explicitly specify the notebook
---@param options? table additional options
---@param cb function callback function
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zkindex
require("zk").api.index(path, options, function(stats)
-- do something with the stats
end)
<
>
---@param path? string path to explicitly specify the notebook
---@param options? table additional options
---@param cb function callback function
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zknew
require("zk").api.new(path, options, function(res)
file_path = res.path
-- do something with the new file path
end)
<
>
---@param path? string path to explicitly specify the notebook
---@param options table additional options
---@param cb function callback function
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
require("zk").api.list(path, options, function(notes)
-- do something with the notes
end)
<
>
---@param path? string path to explicitly specify the notebook
---@param options? table additional options
---@param cb function callback function
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zktaglist
require("zk").api.tag.list(path, options, function(tags)
-- do something with the tags
end)
<
--------------------------------------------------------------------------------
PICKERS *zk-pickers*
Used by the high-level API (#high-level-api) to display the results of the API (#api).
>
---Opens a notes picker
--
---@param notes list
---@param options? table containing {picker}, {title}, {multi_select} keys
---@param cb function
require("zk.ui").pick_notes(notes, options, cb)
<
>
---Opens a tags picker
--
---@param tags list
---@param options? table containing {picker}, {title}, {multi_select} keys
---@param cb function
require("zk.ui").pick_tags(tags, options, cb)
<
>
---To be used in zk.api.list as the `selection` in the additional options table
--
---@param options table the same options that are use for pick_notes
---@return table api selection
require("zk.ui").get_pick_notes_list_api_selection(options)
<
--------------------------------------------------------------------------------
EXAMPLE MAPPINGS *zk-example_mappings*
>
vim.api.nvim_set_keymap("n", "<Leader>zc", "<cmd>ZkNew<CR>", { noremap = true })
vim.api.nvim_set_keymap("x", "<Leader>zc", ":'<'>ZkNewFromTitleSelection<CR>", { noremap = true })
vim.api.nvim_set_keymap("n", "<Leader>zn", "<cmd>ZkNotes<CR>", { noremap = true })
vim.api.nvim_set_keymap("n", "<Leader>zb", "<cmd>ZkBacklinks<CR>", { noremap = true })
vim.api.nvim_set_keymap("n", "<Leader>zl", "<cmd>ZkLinks<CR>", { noremap = true })
vim.api.nvim_set_keymap("n", "<Leader>zt", "<cmd>ZkTags<CR>", { noremap = true })
<
================================================================================
MISCELLANEOUS *zk-miscellaneous*
--------------------------------------------------------------------------------
SYNTAX HIGHLIGHTING TIPS *zk-syntax_highlighting_tips*
These code snippets only work if you use Neovim's built-in Markdown syntax highlighting.
Proper syntax highlighting for Wikilinks. [[This is a wiki link]].
>
autocmd Filetype markdown syn region markdownWikiLink matchgroup=markdownLinkDelimiter start="\[\[" end="\]\]" contains=markdownUrl keepend oneline concealends
<
Conceal support for normal Markdown links. Overwrite the syntax regions like so
>
autocmd Filetype markdown syn region markdownLinkText matchgroup=markdownLinkTextDelimiter start="!\=\[\%(\%(\_[^][]\|\[\_[^][]*\]\)*]\%( \=[[(]\)\)\@=" end="\]\%( \=[[(]\)\@=" nextgroup=markdownLink,markdownId skipwhite contains=@markdownInline,markdownLineStart concealends
autocmd Filetype markdown syn region markdownLink matchgroup=markdownLinkDelimiter start="(" end=")" contains=markdownUrl keepend contained conceal
<
--------------------------------------------------------------------------------
NVIM-LSP-INSTALLER *zk-nvim-lsp-installer*
>
Not recommended, instead install the `zk` (https://github.com/mickael-menu/zk) CLI tool and make it available in your `$PATH`.
<
If you insist to use nvim-lsp-installer for `zk`, the following code snippet should guide you on how to setup the `zk` server when installed via nvim-lsp-installer.
>
require("nvim-lsp-installer").on_server_ready(function(server)
local opts = {
-- customize your options as usual
--
-- on_attach = ...
-- etc, see `:h vim.lsp.start_client()`
}
if server.name == "zk" then
require("zk").setup({
lsp = {
config = vim.tbl_extend("force", server:get_default_options(), opts),
},
})
else
server:setup(opts)
end
end)
<
--------------------------------------------------------------------------------
TELESCOPE PLUGIN *zk-telescope_plugin*
>
Not recommended, instead just use the :ZkNotes command (#built-in-commands).
<
It's possible (but unnecessary) to also load the notes picker as a telescope plugin.
>
require("telescope").load_extension("zk")
<
>
:Telescope zk notes
:Telescope zk notes createdAfter=3\ days\ ago
<

@ -1,68 +1,13 @@
local util = require("telescope.zk.util")
local zk = require("zk")
---@param opts table additional options
---@param opts? table additional options for zk, telescope options, all optional and in one table
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
local function show_notes(opts)
opts = vim.tbl_extend("keep", opts or {}, { prompt_title = "Zk Notes" })
zk.api.list(opts.path, util.wrap_note_options(opts), function(notes)
util.show_note_picker(opts, notes)
end)
end
---@param opts table additional options
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
local function show_orphans(opts)
opts = vim.tbl_extend("keep", opts or {}, { prompt_title = "Zk Orphans" })
opts = vim.tbl_deep_extend("force", opts, { orphan = true })
show_notes(opts)
end
---@param opts table additional options
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
local function show_backlinks(opts)
opts = vim.tbl_extend("keep", opts or {}, { prompt_title = "Zk Backlinks" })
opts = vim.tbl_deep_extend("force", opts, { linkTo = { vim.api.nvim_buf_get_name(0) } })
show_notes(opts)
end
---@param opts table additional options
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
local function show_links(opts)
opts = vim.tbl_extend("keep", opts or {}, { prompt_title = "Zk Links" })
opts = vim.tbl_deep_extend("force", opts, { linkedBy = { vim.api.nvim_buf_get_name(0) } })
show_notes(opts)
end
---@param opts table additional options
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
local function show_related(opts)
opts = vim.tbl_extend("keep", opts or {}, { prompt_title = "Zk Related" })
opts = vim.tbl_deep_extend("force", opts, { related = { vim.api.nvim_buf_get_name(0) } })
show_notes(opts)
end
---@param opts table additional options
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zktaglist
local function show_tags(opts)
opts = vim.tbl_extend("keep", opts or {}, { prompt_title = "Zk Tags" })
zk.api.tag.list(opts.path, util.wrap_tag_options({}), function(tags)
util.show_tag_picker(opts, tags, function(selected_tags)
zk.api.list(opts.path, util.wrap_note_options({ tags = selected_tags }), function(notes)
opts.prompt_title = "Zk Notes for tag(s) " .. vim.inspect(selected_tags)
util.show_note_picker(opts, notes)
end)
end)
end)
zk.edit(opts, { picker = "telescope", telescope = opts })
end
return require("telescope").register_extension({
exports = {
notes = show_notes,
orphans = show_orphans,
backlinks = show_backlinks,
links = show_links,
related = show_related,
tags = show_tags,
},
})

@ -1,115 +1,136 @@
local util = require("zk.util")
local lsp = require("zk.lsp")
local config = require("zk.config")
local ui = require("zk.ui")
local api = require("zk.api")
local util = require("zk.util")
local M = {}
M.api = require("zk.api")
local function setup_lsp_auto_attach()
--- NOTE: modified version of code in nvim-lspconfig
local trigger
local filetypes = config.options.lsp.auto_attach.filetypes
if filetypes then
trigger = "FileType " .. table.concat(filetypes, ",")
else
trigger = "BufReadPost *"
end
vim.api.nvim_command(string.format("autocmd %s lua require'zk'._lsp_buf_auto_add(0)", trigger))
end
M.lsp = require("zk.lsp")
---Automatically called via an |autocmd| if lsp.auto_attach is enabled.
--
---@param bufnr number
function M._lsp_buf_auto_add(bufnr)
if vim.api.nvim_buf_get_option(bufnr, "buftype") == "nofile" then
return
end
if not util.notebook_root(vim.api.nvim_buf_get_name(bufnr)) then
return
end
lsp.buf_add(bufnr)
end
---The entry point of the plugin
--
---@param options? table user configuration options
function M.setup(options)
config.options = vim.tbl_deep_extend("force", config.defaults, options or {})
if config.options.lsp.auto_attach.enabled then
util.setup_lsp_auto_attach()
setup_lsp_auto_attach()
end
if config.options.create_user_commands then
vim.cmd([[
" Core Commands
command! -nargs=? -complete=lua ZkIndex lua require('zk').index(nil, assert(loadstring('return ' .. <q-args>))())
command! -nargs=? -complete=lua ZkNew lua require('zk').new(nil, assert(loadstring('return ' .. <q-args>))())
command! -nargs=? -complete=lua ZkList lua require('zk').list(nil, assert(loadstring('return ' .. <q-args>))())
command! -nargs=? -complete=lua ZkTagList lua require('zk').tag.list(nil, assert(loadstring('return ' .. <q-args>))())
" Convenience Commands
command! -range -nargs=? -complete=lua ZkNewLink lua assert(<range> == 2, "ZkNewLink must be called with '<,'> range. Try :'<'>ZkNewLink"); require('zk').new_link(nil, assert(loadstring('return ' .. <q-args>))())
]])
-- The definition of :ZkNewLink is kind of a hack.
-- The lua function that is called by ZkNewLink will always use the '<,'> marks to get the selection.
-- The only way that this command can be called that makes semantical sense is :'<,'>ZkNewLink.
end
require("zk.commands.builtin")
end
-- Core Commands
---Indexes the notebook
---Cd into the notebook root
--
---@param path? string path to explicitly specify the notebook
---@param options table additional options
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zkindex
function M.index(path, options)
M.api.index(path, options, function(stats)
vim.notify(vim.inspect(stats))
end)
---@param options? table
function M.cd(options)
options = options or {}
local notebook_path = options.notebook_path or util.resolve_notebook_path(0)
local root = util.notebook_root(notebook_path)
if root then
vim.cmd("cd " .. root)
end
end
---Creates a new note
---Creates and edits a new note
--
---@param path? string path to explicitly specify the notebook
---@param options table additional options
---@param options? table additional options
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zknew
function M.new(path, options)
-- neovim does not yet support window/showDocument, therefore we handle options.edit locally
if options and options.edit then
options.edit = nil -- nil means true in this context
end
M.api.new(path, options, function(res)
function M.new(options)
options = options or {}
api.new(options.notebook_path, options, function(res)
if options and options.edit == false then
return
end
-- neovim does not yet support window/showDocument, therefore we handle options.edit locally
vim.cmd("edit " .. res.path)
end)
end
---Opens a Telescope picker
---Indexes the notebook
--
---@param path? string path to explicitly specify the notebook
---@param options table additional options
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
function M.list(path, options)
-- NOTE: this does not have to be telescope specific.
-- In the future consider exposing something like config.options.picker = 'telescope'|'fzf'|'builtin'.
-- Obviously the same applies to the `M.tag.list` function.
if path then
options = options or {}
options.path = path
end
-- `h: telescope.command`
require("telescope._extensions.zk").exports.notes(options)
---@param options? table additional options
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zkindex
function M.index(options)
options = options or {}
api.index(options.notebook_path, options, function(stats)
vim.notify(vim.inspect(stats))
end)
end
M.tag = {}
---Opens a notes picker, and calls the callback with the selection
--
---@param options? table additional options
---@param picker_options? table options for the picker
---@param cb function
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
---@see zk.ui.pick_notes
function M.pick_notes(options, picker_options, cb)
options = vim.tbl_extend(
"force",
{ select = ui.get_pick_notes_list_api_selection(picker_options), sort = { "created" } },
options or {}
)
api.list(options.notebook_path, options, function(notes)
ui.pick_notes(notes, picker_options, cb)
end)
end
---Opens a Telescope picker
---Opens a tags picker, and calls the callback with the selection
--
---@param path? string path to explicitly specify the notebook
---@param options table additional options
---@param options? table additional options
---@param picker_options? table options for the picker
---@param cb function
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zktaglist
function M.tag.list(path, options)
if path then
options = options or {}
options.path = path
end
require("telescope._extensions.zk").exports.tags(options)
---@see zk.ui.pick_tags
function M.pick_tags(options, picker_options, cb)
options = vim.tbl_extend("force", { sort = { "note-count" } }, options or {})
api.tag.list(options.notebook_path, options, function(tags)
ui.pick_tags(tags, picker_options, cb)
end)
end
-- Convenience Commands
---Creates a new note and uses the last visual selection as the title while replacing the selection with a link to the new note
---Opens a notes picker, and edits the selected notes
--
---@param path? string path to explicitly specify the notebook
---@param options table additional options
function M.new_link(path, options)
local location = util.make_lsp_location()
local selected_text = util.get_text_in_range(location.range)
if not selected_text then
vim.notify("Selection not set", vim.log.levels.ERROR)
return
end
M.new(path, vim.tbl_extend("keep", options or {}, { insertLinkAtLocation = location, title = selected_text }))
---@param options? table additional options
---@param picker_options? table options for the picker
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zklist
---@see zk.ui.pick_notes
function M.edit(options, picker_options)
M.pick_notes(options, picker_options, function(notes)
if picker_options.multi_select == false then
notes = { notes }
end
for _, note in ipairs(notes) do
vim.cmd("e " .. note.absPath)
end
end)
end
return M

@ -3,34 +3,6 @@ local util = require("zk.util")
local M = {}
---Try to resolve the notebook directory by checking the following locations in that order
---1. current buffer path
---2. current working directory
---3. `$ZK_NOTEBOOK_DIR` environment variable
--
---@param bufnr number?
local function resolve_notebook_dir(bufnr)
local path = vim.api.nvim_buf_get_name(bufnr)
local cwd = vim.fn.getcwd(0)
-- if the buffer has no name (i.e. it is empty), set the current working directory as it's path
if path == "" then
path = cwd
end
if not util.notebook_root(path) then
if not util.notebook_root(cwd) then
-- if neither the buffer nor the cwd belong to a notebook, use $ZK_NOTEBOOK_DIR as fallback if available
if vim.env.ZK_NOTEBOOK_DIR then
path = vim.env.ZK_NOTEBOOK_DIR
end
else
-- the buffer doesn't belong to a notebook, but the cwd does!
path = cwd
end
end
-- at this point, the buffer either belongs to a notebook, or everything else failed
return path
end
---Executes the given command via LSP
--
---@param cmd string
@ -47,7 +19,7 @@ local function execute_command(cmd, path, options, cb)
lsp.client().request("workspace/executeCommand", {
command = "zk." .. cmd,
arguments = {
path or resolve_notebook_dir(bufnr),
path or util.resolve_notebook_path(bufnr),
options,
},
}, function(err, res)
@ -59,7 +31,7 @@ local function execute_command(cmd, path, options, cb)
end
---@param path? string path to explicitly specify the notebook
---@param options table additional options
---@param options? table additional options
---@param cb function callback function
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zkindex
function M.index(path, options, cb)
@ -67,7 +39,7 @@ function M.index(path, options, cb)
end
---@param path? string path to explicitly specify the notebook
---@param options table additional options
---@param options? table additional options
---@param cb function callback function
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zknew
function M.new(path, options, cb)
@ -85,7 +57,7 @@ end
M.tag = {}
---@param path? string path to explicitly specify the notebook
---@param options table additional options
---@param options? table additional options
---@param cb function callback function
---@see https://github.com/mickael-menu/zk/blob/main/docs/editors-integration.md#zktaglist
function M.tag.list(path, options, cb)

@ -0,0 +1,53 @@
local zk = require("zk")
local util = require("zk.util")
local commands = require("zk.commands")
commands.add("ZkIndex", zk.index)
commands.add("ZkNew", zk.new)
commands.add("ZkNewFromTitleSelection", function(options)
local location = util.get_lsp_location_from_selection()
local selected_text = util.get_text_in_range(location.range)
assert(selected_text ~= nil, "No selected text")
zk.new(vim.tbl_extend("force", { insertLinkAtLocation = location, title = selected_text }, options or {}))
end, { needs_selection = true })
commands.add("ZkNewFromContentSelection", function(options)
local location = util.get_lsp_location_from_selection()
local selected_text = util.get_text_in_range(location.range)
assert(selected_text ~= nil, "No selected text")
zk.new(vim.tbl_extend("force", { insertLinkAtLocation = location, content = selected_text }, options or {}))
end, { needs_selection = true })
commands.add("ZkCd", zk.cd)
commands.add("ZkNotes", function(options)
zk.edit(options, { title = "Zk Notes" })
end)
commands.add("ZkBacklinks", function(options)
options = vim.tbl_extend("force", { linkTo = { vim.api.nvim_buf_get_name(0) } }, options or {})
zk.edit(options, { title = "Zk Backlinks" })
end)
commands.add("ZkLinks", function(options)
options = vim.tbl_extend("force", { linkedBy = { vim.api.nvim_buf_get_name(0) } }, options or {})
zk.edit(options, { title = "Zk Links" })
end)
commands.add("ZkMatch", function(options)
local selected_text = util.get_selected_text()
assert(selected_text ~= nil, "No selected text")
options = vim.tbl_extend("force", { match = selected_text }, options or {})
zk.edit(options, { title = "Zk Notes matching " .. vim.inspect(selected_text) })
end, { needs_selection = true })
commands.add("ZkTags", function(options)
zk.pick_tags(options, { title = "Zk Tags" }, function(tags)
tags = vim.tbl_map(function(v)
return v.name
end, tags)
zk.edit({ tags = tags }, { title = "Zk Notes for tag(s) " .. vim.inspect(tags) })
end)
end)

@ -0,0 +1,67 @@
local M = {}
local name_fn_map = {}
-- NOTE: remove this once `vim.api.nvim_add_user_command` is officially released
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 fn function
---@param opts? table {needs_selection} makes sure the command is called with a range
---@see vim.api.nvim_add_user_command
function M.add(name, fn, opts)
opts = opts or {}
nvim_add_user_command(name, function(params) -- vim.api.nvim_add_user_command
if opts.needs_selection then
assert(
params.range == 2,
"Command needs a selection and must be called with '<,'> range. Try making a selection first."
)
end
fn(loadstring("return " .. params.args)())
end, { nargs = "?", force = true, range = opts.needs_selection, complete = "lua" })
name_fn_map[name] = fn
end
function M.get(name)
return name_fn_map[name]
end
---Wrapper around `vim.api.nvim_del_user_command`
---@param name string
---@see vim.api.nvim_add_user_command
function M.del(name)
name_fn_map[name] = nil
nvim_del_user_command(name) -- vim.api.nvim_del_user_command
end
return M

@ -1,7 +1,7 @@
local M = {}
M.defaults = {
create_user_commands = true,
picker = "select",
lsp = {
config = {
cmd = { "zk", "lsp" },
@ -14,6 +14,6 @@ M.defaults = {
},
}
M.options = M.defaults
M.options = M.defaults -- not necessary, but better code completion
return M

@ -0,0 +1,84 @@
local M = {}
local delimiter = "\x01 "
-- we want can't do vim.fn["fzf#wrap"] because the sink/sinklist funcrefs
-- are reset to vim.NIL when they are converted to Lua
vim.cmd([[
function! _fzf_wrap_and_run(...)
call fzf#run(call('fzf#wrap', a:000))
endfunction
]])
M.note_picker_list_api_selection = { "title", "absPath" }
function M.show_note_picker(notes, options, cb)
options = options or {}
vim.fn._fzf_wrap_and_run({
source = vim.tbl_map(function(v)
return table.concat({ v.absPath, v.title }, delimiter)
end, notes),
options = vim.list_extend({
"--delimiter=" .. delimiter,
"--tiebreak=index",
"--with-nth=2",
"--exact",
"--tabstop=4",
[[--preview=command -v bat 1>/dev/null 2>&1 && bat -p --color always {1} || cat {1}]],
"--preview-window=wrap",
options.title and "--header=" .. options.title or nil,
options.multi_select and "--multi" or nil,
}, options.fzf_options or {}),
sinklist = function(lines)
local notes_by_path = {}
for _, note in ipairs(notes) do
notes_by_path[note.absPath] = note
end
local selected_notes = vim.tbl_map(function(line)
local path = string.match(line, "([^" .. delimiter .. "]+)")
return notes_by_path[path]
end, lines)
if options.multi_select then
cb(selected_notes)
else
cb(selected_notes[1])
end
end,
})
end
function M.show_tag_picker(tags, options, cb)
options = options or {}
vim.fn._fzf_wrap_and_run({
source = vim.tbl_map(function(v)
return table.concat({ string.format("\x1b[31m%-4d\x1b[0m", v.note_count), v.name }, delimiter)
end, tags),
options = vim.list_extend({
"--delimiter=" .. delimiter,
"--tiebreak=index",
"--nth=2",
"--exact",
"--tabstop=4",
"--ansi",
options.title and "--header=" .. options.title or nil,
options.multi_select and "--multi" or nil,
}, options.fzf or {}),
sinklist = function(lines)
local tags_by_name = {}
for _, tag in ipairs(tags) do
tags_by_name[tag.name] = tag
end
local selected_tags = vim.tbl_map(function(line)
local name = string.match(line, "([^" .. delimiter .. "]+)", 2)
return tags_by_name[name]
end, lines)
if options.multi_select then
cb(selected_tags)
else
cb(selected_tags[1])
end
end,
})
end
return M

@ -0,0 +1,47 @@
local M = {}
M.note_picker_list_api_selection = { "title", "absPath" }
function M.show_note_picker(notes, options, cb)
options = options or {}
local select_options = vim.tbl_extend("force", {
prompt = options.title,
format_item = function(item)
return item.title
end,
}, options.select or {})
vim.ui.select(notes, select_options, function(item)
if not item then
-- user aborted
return
end
if options.multi_select then
cb({ item })
else
cb(item)
end
end)
end
function M.show_tag_picker(tags, options, cb)
options = options or {}
local select_options = vim.tbl_extend("force", {
prompt = "Zk Tags",
format_item = function(item)
return item.name
end,
}, options.select or {})
vim.ui.select(tags, select_options, function(item)
if not item then
-- user aborted
return
end
if options.multi_select then
cb({ item })
else
cb(item)
end
end)
end
return M

@ -10,17 +10,7 @@ local previewers = require("telescope.previewers")
local M = {}
function M.wrap_note_options(opts)
return vim.tbl_deep_extend(
"force",
{ select = { "title", "absPath", "rawContent" }, sort = { "created" } },
opts or {}
)
end
function M.wrap_tag_options(opts)
return vim.tbl_deep_extend("force", { sort = { "note-count" } }, opts or {})
end
M.note_picker_list_api_selection = { "title", "absPath", "rawContent" }
function M.create_note_entry_maker(_)
return function(note)
@ -65,40 +55,64 @@ function M.make_note_previewer()
})
end
function M.show_note_picker(opts, notes)
opts = opts or {}
pickers.new(opts, {
function M.show_note_picker(notes, options, cb)
options = options or {}
local telescope_options = vim.tbl_extend("force", { prompt_title = options.title }, options.telescope or {})
pickers.new(telescope_options, {
finder = finders.new_table({
results = notes,
entry_maker = M.create_note_entry_maker(opts),
entry_maker = M.create_note_entry_maker(options),
}),
sorter = conf.file_sorter(opts),
sorter = conf.file_sorter(options),
previewer = M.make_note_previewer(),
attach_mappings = function(prompt_bufnr)
actions.select_default:replace(function()
if options.multi_select then
local selection = {}
action_utils.map_selections(prompt_bufnr, function(entry, _)
table.insert(selection, entry.value)
end)
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)
return true
end,
}):find()
end
function M.show_tag_picker(opts, tags, cb)
opts = opts or {}
pickers.new(opts, {
function M.show_tag_picker(tags, options, cb)
options = options or {}
local telescope_options = vim.tbl_extend("force", { prompt_title = options.title }, options.telescope or {})
pickers.new(telescope_options, {
finder = finders.new_table({
results = tags,
entry_maker = M.create_tag_entry_maker(opts),
entry_maker = M.create_tag_entry_maker(options),
}),
sorter = conf.generic_sorter(opts),
sorter = conf.generic_sorter(options),
attach_mappings = function(prompt_bufnr, _)
actions.select_default:replace(function()
local selection = {}
action_utils.map_selections(prompt_bufnr, function(entry, _)
table.insert(selection, entry.value.name)
end)
if vim.tbl_isempty(selection) then
selection = { action_state.get_selected_entry().value.name }
if options.multi_select then
local selection = {}
action_utils.map_selections(prompt_bufnr, function(entry, _)
table.insert(selection, entry.value)
end)
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
actions.close(prompt_bufnr)
cb(selection)
end)
return true
end,

@ -0,0 +1,107 @@
-- NOTE: everything in this module is copied from nvim-lspconfig (https://github.com/neovim/nvim-lspconfig)
-- NOTE: we need this util until the code from lspconfig is merged into core
local vim = vim
local validate = vim.validate
local uv = vim.loop
local M = {}
-- Some path utilities
M.path = (function()
local is_windows = uv.os_uname().version:match 'Windows'
local function exists(filename)
local stat = uv.fs_stat(filename)
return stat and stat.type or false
end
local function is_fs_root(path)
if is_windows then
return path:match '^%a:$'
else
return path == '/'
end
end
local function dirname(path)
local strip_dir_pat = '/([^/]+)$'
local strip_sep_pat = '/$'
if not path or #path == 0 then
return
end
local result = path:gsub(strip_sep_pat, ''):gsub(strip_dir_pat, '')
if #result == 0 then
if is_windows then
return path:sub(1, 2):upper()
else
return '/'
end
end
return result
end
local function path_join(...)
return table.concat(vim.tbl_flatten { ... }, '/')
end
-- Iterate the path until we find the rootdir.
local function iterate_parents(path)
local function it(_, v)
if v and not is_fs_root(v) then
v = dirname(v)
else
return
end
if v and uv.fs_realpath(v) then
return v, path
else
return
end
end
return it, path, path
end
return {
exists = exists,
join = path_join,
iterate_parents = iterate_parents,
}
end)()
function M.search_ancestors(startpath, func)
validate { func = { func, 'f' } }
if func(startpath) then
return startpath
end
local guard = 100
for path in M.path.iterate_parents(startpath) do
-- Prevent infinite recursion if our algorithm breaks
guard = guard - 1
if guard == 0 then
return
end
if func(path) then
return path
end
end
end
function M.root_pattern(...)
local patterns = vim.tbl_flatten { ... }
local function matcher(path)
for _, pattern in ipairs(patterns) do
for _, p in ipairs(vim.fn.glob(M.path.join(path, pattern), true, true)) do
if M.path.exists(p) then
return path
end
end
end
end
return function(startpath)
return M.search_ancestors(startpath, matcher)
end
end
return M

@ -0,0 +1,42 @@
local config = require("zk.config")
local M = {}
---Opens a notes picker
--
---@param notes list
---@param options? table containing {picker}, {title}, {multi_select} keys
---@param cb function
function M.pick_notes(notes, options, cb)
options = vim.tbl_extend(
"force",
{ title = "Zk Notes", picker = config.options.picker, multi_select = true },
options or {}
)
require("zk.pickers." .. options.picker).show_note_picker(notes, options, cb)
end
---Opens a tags picker
--
---@param tags list
---@param options? table containing {picker}, {title}, {multi_select} keys
---@param cb function
function M.pick_tags(tags, options, cb)
options = vim.tbl_extend(
"force",
{ title = "Zk Tags", picker = config.options.picker, multi_select = true },
options or {}
)
require("zk.pickers." .. options.picker).show_tag_picker(tags, options, cb)
end
---To be used in zk.api.list as the `selection` in the additional options table
--
---@param options table the same options that are use for pick_notes
---@return table api selection
function M.get_pick_notes_list_api_selection(options)
options = vim.tbl_extend("force", { picker = config.options.picker }, options or {})
return require("zk.pickers." .. options.picker).note_picker_list_api_selection
end
return M

@ -1,50 +1,60 @@
local lsp = require("zk.lsp")
local config = require("zk.config")
local M = {}
function M.setup_lsp_auto_attach()
--- NOTE: modified version of code in nvim-lspconfig
local trigger
local filetypes = config.options.lsp.auto_attach.filetypes
if filetypes then
trigger = "FileType " .. table.concat(filetypes, ",")
else
trigger = "BufReadPost *"
end
vim.api.nvim_command(string.format("autocmd %s lua require'zk.util'.lsp_buf_auto_add(0)", trigger))
end
---Checks whether the given path belongs to a notebook
---@param path string
---Finds the root directory of the notebook of the given path
--
---@param notebook_path string
---@return string? root
function M.notebook_root(path)
return require("lspconfig.util").root_pattern(".zk")(path)
function M.notebook_root(notebook_path)
return require("zk.root_pattern_util").root_pattern(".zk")(notebook_path)
end
---Automatically called via an |autocmd| if lsp.auto_attach is enabled.
---@param bufnr number
function M.lsp_buf_auto_add(bufnr)
if vim.api.nvim_buf_get_option(bufnr, "buftype") == "nofile" then
return
---Try to resolve a notebook path by checking the following locations in that order
---1. current buffer path
---2. current working directory
---3. `$ZK_NOTEBOOK_DIR` environment variable
---
---Note that the path will not necessarily be the notebook root.
--
---@param bufnr number?
---@return string? path inside a notebook
function M.resolve_notebook_path(bufnr)
local path = vim.api.nvim_buf_get_name(bufnr)
local cwd = vim.fn.getcwd(0)
-- if the buffer has no name (i.e. it is empty), set the current working directory as it's path
if path == "" then
path = cwd
end
if not M.notebook_root(vim.api.nvim_buf_get_name(bufnr)) then
return
if not M.notebook_root(path) then
if not M.notebook_root(cwd) then
-- if neither the buffer nor the cwd belong to a notebook, use $ZK_NOTEBOOK_DIR as fallback if available
if vim.env.ZK_NOTEBOOK_DIR then
path = vim.env.ZK_NOTEBOOK_DIR
end
else
-- the buffer doesn't belong to a notebook, but the cwd does!
path = cwd
end
end
lsp.buf_add(bufnr)
-- at this point, the buffer either belongs to a notebook, or everything else failed
return path
end
function M.make_lsp_location()
---Makes an LSP location object from the last selection in the current buffer.
--
---@return table LSP location object
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#location
function M.get_lsp_location_from_selection()
local params = vim.lsp.util.make_given_range_params()
params.uri = params.textDocument.uri
params.textDocument = nil
return params
end
--- needed until https://github.com/neovim/neovim/pull/13896 is merged
---@param range table LSP range object
---Gets the text in the given range of the current buffer.
---Needed until https://github.com/neovim/neovim/pull/13896 is merged.
--
---@param range table contains {start} and {end} tables with {line} and {character} values
---@return string? text in range
function M.get_text_in_range(range)
local A = range["start"]
local B = range["end"]
@ -58,4 +68,27 @@ function M.get_text_in_range(range)
return table.concat(lines, "\n")
end
---Gets the most recently selected text of the current buffer.
---That is the text between the '<,'> marks.
---Note that these marks are only updated *after* leaving the visual mode.
--
---@return string? selected text
function M.get_selected_text()
-- code adjusted from `vim.lsp.util.make_given_range_params`
-- we don't want to use character encoding offsets here
local A = vim.api.nvim_buf_get_mark(0, "<")
local B = vim.api.nvim_buf_get_mark(0, ">")
-- convert to 0-index
A[1] = A[1] - 1
B[1] = B[1] - 1
if vim.o.selection ~= "exclusive" then
B[2] = B[2] + 1
end
return M.get_text_in_range({
start = { line = A[1], character = A[2] },
["end"] = { line = B[1], character = B[2] },
})
end
return M

Loading…
Cancel
Save