1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-23 07:20:04 +08:00

feat(zettelkasten): add zettelkasten layer

This commit is contained in:
Wang Shidong 2022-10-18 22:55:34 +08:00 committed by GitHub
parent efb59af888
commit 342c658509
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1208 additions and 0 deletions

View File

@ -0,0 +1,44 @@
"=============================================================================
" zettelkasten.vim --- zettelkasten layer for SpaceVim
" Copyright (c) 2016-2019 Wang Shidong & Contributors
" Author: Wang Shidong < wsdjeg@outlook.com >
" URL: https://spacevim.org
" License: GPLv3
"=============================================================================
function! SpaceVim#layers#zettelkasten#plugins() abort
let plugins = []
call add(plugins, [g:_spacevim_root_dir . 'bundle/vim-zettelkasten',
\ {
\ 'merged' : 0,
\ }])
return plugins
endfunction
function! SpaceVim#layers#zettelkasten#health() abort
call SpaceVim#layers#zettelkasten#plugins()
return 1
endfunction
function! SpaceVim#layers#zettelkasten#loadable() abort
return has('nvim')
endfunction
function! SpaceVim#layers#zettelkasten#config() abort
let g:_spacevim_mappings_space.m.z = {'name' : '+zettelkasten'}
call SpaceVim#mapping#space#def('nnoremap', ['m', 'z', 'n'], 'ZkNew', 'create-new-zettel-note', 1)
endfunction
function! SpaceVim#layers#zettelkasten#set_variable(var) abort
let g:zettelkasten_directory = get(a:var,
\ 'zettel_dir',
\ '')
endfunction
function! SpaceVim#layers#zettelkasten#get_options() abort
return ['zettel_dir']
endfunction

24
bundle/vim-zettelkasten/LICENSE vendored Normal file
View File

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org>

70
bundle/vim-zettelkasten/README.md vendored Normal file
View File

@ -0,0 +1,70 @@
# vim-zettelkasten
`vim-zettelkasten` is a [Zettelkasten](https://zettelkasten.de) note taking plugin.
It is based on [zettelkasten.nvim@fe174666](https://github.com/Furkanzmc/zettelkasten.nvim/tree/fe1746666e27c2fcc0e60dc2786cb9983b994759)
Using this plugin, you can:
1. Create new notes with unique IDs (`:help :ZkNew`)
2. List the places where a tag is used with `:tselect tag_name` or use Vim's own tag shortcuts for
navigation.
3. Use `i_CTRL-X_CTRL-]` to get a list of all the tags in your notes.
4. Get a completion list of note references.
5. Use `K` command to display context for a note ID.
6. Use `gf` to navigate to a reference. As long as your `:help path` option is set correctly, this
will work.
There's no separate file type for zettelkasten. Markdown file type is used to extend the
functionality to make it easier to take notes.
For the most up to date information, please do `:help zettelkasten.txt`. I won't be updating README
file for every single feature or update. You can also check out the
[wiki](https://github.com/Furkanzmc/zettelkasten.nvim/wiki) for tips and tricks on how to use
zettelkasten.nvim.
# Configuration
See `:help zettelkasten.txt` for more information.
# TODO
Potential ideas to implement in the future:
- [ ] A graph view (Possible with an external CLI program.)
- [X] A sidebar (or preview window) to display the linked notes.
- [ ] Telescope support. It's a popular plugin so it'd be useful but I don't use Telescope so
contributions for this feature is most welcome!
# Project Goals
I started the project out of a bout of excitement for having discovered the Zettelkasten note
taking system. I've been looking for better ways to take notes and this system seems to fulfill my
needs. Since I love Vim, and Zettelkasten is a text based system (Which is what I love the most
about it), I decided to create a plugin immediately.
My goal is not to turn this into a huge thing with custom pickers, and file types, and a gazillion
mapping and commands. My goal is to make use of the existing Vim options/mappings/features to
extend markdown file type so it's more convenient to navigate, discover, and write.
As you can see from its initial state, the only thing you need to really know about this plugin is
the `:ZkNew` command. Everything else can be discovered as you are flexing your usual Vim muscles
(e. `gf`, `i_CTRL-X_CTRL-]`, `CTRL-]`).
In true Vim philosophy, I also want to make it easier for people to extend this plugin to their own
needs. So, all the Lua API will be nicely designed so you can interface this plugin with others
(e.g `Telescope.nvim`) or create your own workflow easily.
Please also see `:help zettelkasten.nvim-101` and `:help zettelkasten-philosophy`.
# Related Projects
- [zk-nvim](https://github.com/mickael-menu/zk-nvim)
- [zettel.vim](https://github.com/Aarleks/zettel.vim/)
- [telekasten.nvim](https://github.com/renerocksai/telekasten.nvim)
- [marty-oehme/zettelkasten.nvim](https://github.com/marty-oehme/zettelkasten.nvim)

View File

@ -0,0 +1,256 @@
*zettelkasten.txt* vim-zettelkasten
Origin Author: Furkan Uzumcu
Maintainer: Shidong Wang <wsdjeg@outlook.com>
INTRODUCTION *vim-zettelkasten* *zettelkasten*
================================================================================
`vim-zettelkasten` is forked from `zettelkasten.nvim`. The goal of this plugin
is to make it easier to use the Zettelkasten note taking system:
https://zettelkasten.de/introduction/
It is meant to provide the following features in a way that is consistent with
Vim's own features:
1. Note ID completion
2. ID generation
3. Jumping to references
4. Jumping to tags
5. Listing tags
All relevant options are set automatically if they have not been set before.
If they were set, these are the options that you can use to get the best
experience. These options may change, make sure the check the filetype file: >
setlocal tagfunc=v:lua.zettelkasten.tagfunc
setlocal isfname+=-,:
setlocal iskeyword+=:,-
setlocal suffixesadd+=.md
setlocal keywordprg=:ZkHover
setlocal completefunc=v:lua.zettelkasten.completefunc
ZETTELKASTEN.NVIM 101 *zettelkasten.nvim-101*
================================================================================
The plugin does not introduce any new syntax for note taking. You can take
advantage of the markdown syntax with headers, tags, and links. You can refer
to a note by wrapping the note ID (i.e. the file name) with double angle
brackets.
>
This note refers to [[2022-02-27-06-05-03]] which was created using
:ZkNew.
It can also refer to an [[existing_note]]. With this approach, you won't
be able to use `:help CTRL-]` to jump to the file buy you can still use
`:help gf`
`zettelkasten.nvim` also assumes a single folder where all the notes reside.
If you need to organize things you can use `#tags` to group them or create a
note that will be used as a reference for other notes.
PHILOSOPHY *zettelkasten-philosophy*
================================================================================
`zettelkasten.nvim` embraces Vim by trying its hardest to not create new
commands or mappings. Everything this plugin provides should ideally be
useable with existing Vim options, mappings, and commands.
Where possible, Lua API should be exposed so others can extend this plugin's
functionality or create their own plugins on top of this one (e.g. telescope
pickers.).
PREVIEWING NOTES *zettelkasten-preview-notes*
================================================================================
To review a note, you can either use |K| mapping to execute |keywordprg| or
use any of the preview window tag mappings to open the note in a preview
window. Note that when the preview window commands are used (e.g |CTRL-W }|),
|preview-window| is used. If you want to customize the preview window, use
`preview_command` when setting up the plugin and rely on |K| command.
include ~
|include| commands can be used to see the references as well. You can use
`[I` to display all the lines that contain the reference under the cursor.
MAPS *zettelkasten-maps*
================================================================================
Buffer Local ~
*zettelkasten-[I*
[I Lists the notes that reference the note ID under the cursor. Available
in markdown buffers and plugin specific buffers.
COMMANDS *zettelkasten-commands*
================================================================================
Global ~
ZkNew *:ZkNew*
Creates a new buffer with a markdown file type. If {notes_path} option was
set with |zettelkasten-setup|, it'll change the working directory.
ZkHover {-preview} {-return-lines} {note_id} *:ZkHover*
Implements |keywordprg|. This command is set to |keywordprg| option if
|keywordprg| is not already set.
If `-preview` is passed to the command, a preview window will be used to
show the note contents.
If `-return-lines` can only be used when `-preview` is used. When this
argument is passed, this command will return the note content in a list
instead of using `preview_command`.
Example: >
setlocal keywordprg=:ZkHover\ -preview
setlocal keywordprg=:ZkHover\ -preview\ -return-lines
ZkBrowse *:ZkBrowse*
Just a command that runs `edit zk://browser`.
In this buffer, you can use the same short cuts that you use to navigate
and open notes as in the markdown files.
LUA *zettelkasten-lua*
================================================================================
setup({opts}) *zettelkasten-setup*
Initialize the plugin.
Parameters: ~
{opts} (optional, table)
- {notes_path} (optional, string): Defaults to an empty string. If
provided, |:ZkNew| uses the notes path to set the working
directory. If this is not set. you need to make sure that your
working directory is your notes directory. Otherwise some
features may not work.
- {preview_command} (optional, string): The command to use to
preview a note. This will be used with |:ZkHover -preview|
command. The command must take one string argument as the name
of the note file. This can be used to configure
zettelkasten.nvim so the hover previews are (for example) shown
in a hover window.
- {browseformat} (optional, string): Used to format each line when
browsing the notes. Here's the supported values:
field meaning ~
%f File name of the note.
%h Note's header.
%b Number of references to this note.
%r Number of notes this file references.
%t Tags in the note.
%d Note ID.
Default value is: `%f - %h [%r Refs] [%b B-Refs] %t`
keyword_expr({cword}, {opts}) *zettelkasten-keyword-expr*
Returns a table that contains the context for the note with the given ID
{cword}.
Parameters: ~
{cword} (string): The word under the cursor. If a note with the given
ID cannot be found, returns an empty table.
{opts} (optional, table)
- {preview_note} (optional, boolean): If set to true,
`preview_command` will be used to preview the note.
- {return_lines} (optional, boolean): Only used when
`preview_note` is set to `true`. When set, instead of using
`preview_command`, it returns the note contents from the
command.
Returns: ~
{table}
tagfunc({pattern}, {flags}, {info}) *zettelkasten-tagfunc*
Implements a |tagfunc|. This function is automatically set to the
|tagfunc| option by the plugin.
Returns: ~
{table}
set_note_id({bufnr}) *zettelkasten-set-note-id*
Prepends a note ID to the first line of the current buffer. It expects to
find a markdown header in the first line.
Parameters: ~
{bufnr} (number): Current buffer, or another note buffer.
completefunc({base}) *zettelkasten-completefunc*
Implements a |complete-function|. Given a {base} note ID or title, returns
a list of notes matching notes.
Parameters: ~
{base} (string): Note ID or title.
show_back_references({cword}, {use_loclist}) *zettelkasten-show_back_references*
Presents the list of notes that reference the note with {cword} ID.
If {use_loclist} set to true, location list will be used instead of
quickfix list.
get_back_references({note_id}) *zettelkasten-get_back_references*
Returns a list containg an item each describing a note that references
{note_id}.
Each item in the return table contains these values:
- {id} (string): The note ID that references {note_id}
- {linenr} (number): The line where the reference is found.
- {title} (string): The title of the note with {id}
- {file_name} (string): The file name of the note with {id}
get_toc({note_id}, {format}) *zettelkasten-get_toc*
Returns a table that contains a list of notes that refer to {note_id}
formatted according to {format}.
You can use this function to insert all the notes that refer to a master
note.
Parameters: ~
{note_id} (string): Note ID.
{format} (optional, string)
The default format is `- [%h](%d)`. Only these format options are
supported.
field meaning ~
%f File name of the note.
%h Note's header.
%d Note ID.
Returns: ~
{table}
Example: ~
Insert Table of Contents with a Command: >
vim.api.nvim_buf_add_user_command(0, "ZkInsertTOC", function(opts)
vim.api.nvim_buf_set_lines(
vim.api.nvim_get_current_buf(),
opts.line1,
opts.line2,
true,
require("zettelkasten").get_toc(opts.args)
)
end, {
nargs = 1,
range = true,
})
ABOUT *zettelkasten-about*
================================================================================
Grab the latest version or report a bug on GitHub:
https://github.com/Furkanzmc/zettelkasten.nvim
vim:tw=80:colorcolumn=81:et:ft=help:norl:

View File

@ -0,0 +1,4 @@
vim.cmd([[augroup zettelkasten_ft]])
vim.cmd([[au!]])
vim.cmd([[autocmd BufNewFile,BufRead zk://browser setlocal filetype=zkbrowser]])
vim.cmd([[augroup END]])

View File

@ -0,0 +1,34 @@
local zk = require("zettelkasten")
if vim.opt_local.tagfunc:get() == "" then
vim.opt_local.tagfunc = "v:lua.zettelkasten.tagfunc"
end
if vim.opt_local.completefunc:get() == "" then
vim.opt_local.completefunc = "v:lua.zettelkasten.completefunc"
end
vim.opt.isfname:append(":")
vim.opt.isfname:append("-")
vim.opt_local.iskeyword:append(":")
vim.opt_local.iskeyword:append("-")
vim.opt_local.suffixesadd:append(".md")
vim.opt_local.errorformat = "%f:%l: %m"
vim.opt_local.include = "[[\\s]]"
vim.opt_local.define = "^# \\s*"
if vim.opt_local.keywordprg:get() == "" then
vim.opt_local.keywordprg = ":ZkHover"
end
if vim.fn.mapcheck("[I", "n") == "" then
vim.api.nvim_buf_set_keymap(
0,
"n",
"[I",
'<CMD>lua require("zettelkasten").show_back_references(vim.fn.expand("<cword>"))<CR>',
{ noremap = true, silent = true, nowait = true }
)
end
require("zettelkasten").add_hover_command()

View File

@ -0,0 +1,55 @@
if vim.b.did_ftp == true then
return
end
vim.opt_local.cursorline = true
vim.opt_local.modifiable = true
vim.opt_local.buflisted = true
vim.opt_local.syntax = "zkbrowser"
vim.opt_local.buftype = "nofile"
vim.opt_local.swapfile = false
vim.opt_local.iskeyword:append(":")
vim.opt_local.iskeyword:append("-")
vim.opt_local.suffixesadd:append(".md")
vim.opt_local.errorformat = "%f:%l: %m"
if vim.opt_local.keywordprg:get() == "" then
vim.opt_local.keywordprg = ":ZkHover -preview"
end
if vim.opt_local.tagfunc:get() == "" then
vim.opt_local.tagfunc = "v:lua.zettelkasten.tagfunc"
end
require("zettelkasten").add_hover_command()
if vim.fn.mapcheck("[I", "n") == "" then
vim.api.nvim_buf_set_keymap(
0,
"n",
"[I",
'<CMD>lua require("zettelkasten").show_back_references(vim.fn.expand("<cword>"))<CR>',
{ noremap = true, silent = true, nowait = true }
)
end
local config = require("zettelkasten.config").get()
if config.notes_path ~= "" then
vim.cmd("lcd " .. config.notes_path)
end
vim.api.nvim_create_autocmd({ "BufEnter" }, {
group = vim.api.nvim_create_augroup("zettelkasten_browser_events", { clear = true }),
buffer = vim.api.nvim_get_current_buf(),
callback = function(opts)
vim.opt_local.syntax = ""
vim.api.nvim_buf_set_lines(
0,
vim.fn.line("$") - 1,
-1,
true,
require("zettelkasten").get_note_browser_content()
)
vim.opt_local.syntax = "zkbrowser"
end,
})

View File

@ -0,0 +1,314 @@
local M = {}
local api = vim.api
local fn = vim.fn
local log_levels = vim.log.levels
local log = require("zettelkasten.log")
local config = require("zettelkasten.config")
local formatter = require("zettelkasten.formatter")
local browser = require("zettelkasten.browser")
local NOTE_ID_STRFTIME_FORMAT = "%Y-%m-%d-%H-%M-%S"
local function set_qflist(lines, action, bufnr, use_loclist, what)
what = what or {}
local _, local_efm = pcall(vim.api.nvim_buf_get_option, bufnr, "errorformat")
what.efm = what.efm or local_efm
if use_loclist then
fn.setloclist(bufnr, lines, action, what)
else
fn.setqflist(lines, action, what)
end
end
local function read_note(file_path, line_count)
local file = io.open(file_path, "r")
assert(file ~= nil)
assert(file:read(0) ~= nil)
if line_count == nil then
return file:read("*all")
end
local lines = {}
while #lines < line_count do
local line = file:read("*line")
if line == nil then
break
end
table.insert(lines, line)
end
return lines
end
local function get_all_tags(lookup_tag)
if lookup_tag ~= nil and #lookup_tag > 0 then
lookup_tag = string.gsub(lookup_tag, "\\<", "")
lookup_tag = string.gsub(lookup_tag, "\\>", "")
end
local tags = browser.get_tags()
if lookup_tag ~= nil and #lookup_tag > 0 then
tags = vim.tbl_filter(function(tag)
return string.match(tag.name, lookup_tag) ~= nil
end, tags)
end
return tags
end
local function generate_note_id()
return fn.strftime(NOTE_ID_STRFTIME_FORMAT)
end
function M.completefunc(find_start, base)
if find_start == 1 and base == "" then
local pos = api.nvim_win_get_cursor(0)
local line = api.nvim_get_current_line()
local line_to_cursor = line:sub(1, pos[2])
return fn.match(line_to_cursor, "\\k*$")
end
local notes = vim.tbl_filter(function(note)
return string.match(note.title, base)
end, browser.get_notes())
local words = {}
for _, ref in ipairs(notes) do
table.insert(words, {
word = ref.id,
abbr = ref.title,
dup = 0,
empty = 0,
kind = "[zettelkasten]",
icase = 1,
})
end
return words
end
function M.set_note_id(bufnr)
local first_line = vim.api.nvim_buf_get_lines(bufnr, 0, 1, true)[1]
local zk_id = generate_note_id()
if #zk_id > 0 then
first_line, _ = string.gsub(first_line, "# ", "")
api.nvim_buf_set_lines(bufnr, 0, 1, true, { "# " .. zk_id .. " " .. first_line })
vim.cmd("file " .. zk_id .. ".md")
else
log.notify("There's already a note with the same ID.", log_levels.ERROR, {})
end
end
function M.tagfunc(pattern, flags, info)
local in_insert = string.match(flags, "i") ~= nil
local pattern_provided = pattern ~= "\\<\\k\\k" or pattern == "*"
local all_tags = {}
if pattern_provided then
all_tags = get_all_tags(pattern)
else
all_tags = get_all_tags()
end
local tags = {}
for _, tag in ipairs(all_tags) do
table.insert(tags, {
name = string.gsub(tag.name, "#", ""),
filename = tag.file_name,
cmd = tostring(tag.linenr),
kind = "zettelkasten",
})
end
if not in_insert then
local notes = browser.get_notes()
for _, note in ipairs(notes) do
if string.find(note.id, pattern, 1, true) or not pattern_provided then
table.insert(tags, {
name = note.id,
filename = note.file_name,
cmd = "1",
kind = "zettelkasten",
})
end
end
end
if #tags > 0 then
return tags
end
return nil
end
function M.keyword_expr(word, opts)
if not word then
return {}
end
opts = opts or {}
opts.preview_note = opts.preview_note or false
opts.return_lines = opts.return_lines or false
local note = browser.get_note(word)
if note == nil then
log.notify("Cannot find note.", log_levels.ERROR, {})
return {}
end
local lines = {}
if opts.preview_note and not opts.return_lines then
vim.cmd(config.get().preview_command .. " " .. note.file_name)
elseif opts.preview_note and opts.return_lines then
vim.list_extend(lines, read_note(note.file_name))
else
table.insert(lines, note.title)
end
return lines
end
function M.get_back_references(note_id)
local note = browser.get_note(note_id)
if note == nil then
return {}
end
local title_cache = {}
local get_title = function(id)
if title_cache[id] ~= nil then
return title_cache[id]
end
title_cache[id] = browser.get_note(id).title
return title_cache[id]
end
local references = {}
for _, back_reference in ipairs(note.back_references) do
table.insert(references, {
id = back_reference.id,
linenr = back_reference.linenr,
title = back_reference.title,
file_name = back_reference.file_name,
})
end
return references
end
function M.show_back_references(cword, use_loclist)
use_loclist = use_loclist or false
local references = M.get_back_references(cword)
if #references == 0 then
log.notify("No back references found.", log_levels.ERROR, {})
return
end
local lines = {}
for _, ref in ipairs(references) do
local line = {}
table.insert(line, fn.fnamemodify(ref.file_name, ":."))
table.insert(line, ":")
table.insert(line, ref.linenr)
table.insert(line, ": ")
table.insert(line, ref.title)
table.insert(lines, table.concat(line, ""))
end
set_qflist(
{},
" ",
vim.api.nvim_get_current_buf(),
use_loclist,
{ title = "[[" .. cword .. "]] References", lines = lines }
)
if use_loclist then
vim.cmd([[botright lopen | wincmd p]])
else
vim.cmd([[botright copen | wincmd p]])
end
end
function M.get_toc(note_id, format)
format = format or "- [%h](%d)"
local references = M.get_back_references(note_id)
local lines = {}
for _, note in ipairs(references) do
table.insert(lines, {
file_name = note.file_name,
id = note.id,
title = note.title,
})
end
return formatter.format(lines, format)
end
function M.get_note_browser_content()
if config.get().notes_path == "" then
log.notify("'notes_path' option is required for note browsing.", log_levels.WARN, {})
return {}
end
local all_notes = browser.get_notes()
local lines = {}
for _, note in ipairs(all_notes) do
table.insert(lines, {
file_name = note.file_name,
id = note.id,
references = note.references,
back_references = note.back_references,
tags = note.tags,
title = note.title,
})
end
return formatter.format(lines, config.get().browseformat)
end
function M.add_hover_command()
if fn.exists(":ZkHover") == 2 then
return
end
vim.cmd(
[[command -buffer -nargs=1 ZkHover :lua require"zettelkasten"._internal_execute_hover_cmd(<q-args>)]]
)
end
function M._internal_execute_hover_cmd(args)
if args ~= nil and type(args) == "string" then
args = vim.split(args, " ", true)
else
args = {}
end
local cword = ""
if #args == 1 then
cword = fn.expand("<cword>")
else
cword = args[#args]
end
local lines = M.keyword_expr(cword, {
preview_note = vim.tbl_contains(args, "-preview"),
return_lines = vim.tbl_contains(args, "-return-lines"),
})
if #lines > 0 then
log.notify(table.concat(lines, "\n"), log_levels.INFO, {})
end
end
function M.setup(opts)
opts = opts or {}
opts.notes_path = opts.notes_path or ""
config._set(opts)
end
return M

View File

@ -0,0 +1,215 @@
local M = {}
local fn = vim.fn
local config = require("zettelkasten.config")
local ZK_FULL_TITLE_PATTERN = "# %d+-%d+-%d+-%d+-%d+-%d+ .+"
local ZK_ID_PATTERN = "%d+-%d+-%d+-%d+-%d+-%d+"
local ZK_FILE_NAME_PATTERN = "%d+-%d+-%d+-%d+-%d+-%d+.md"
local s_note_cache_with_file_path = {}
local s_note_cache_with_id = {}
local function get_files(folder)
local files = fn.split(fn.globpath(folder, "*.md"), "\\n")
files = vim.tbl_filter(function(file)
return string.match(file, ZK_FILE_NAME_PATTERN) ~= nil
end, files)
return files
end
local function extract_id_and_title(line)
local zk_id = string.match(line, ZK_FULL_TITLE_PATTERN)
if zk_id == nil then
return nil
end
zk_id = string.gsub(zk_id, "# ", "")
local note_id = string.match(zk_id, ZK_ID_PATTERN)
local title = vim.trim(string.gsub(zk_id, ZK_ID_PATTERN, ""))
return { id = note_id, title = string.gsub(title, "\r", "") }
end
local function extract_references(line, linenr)
assert(line ~= nil)
local references = {}
for ref in string.gmatch(line, "(%[%[" .. ZK_ID_PATTERN .. "%]%])") do
ref = string.gsub(ref, "%[%[", "")
ref = string.gsub(ref, "%]%]", "")
table.insert(references, { id = ref, linenr = linenr })
end
return references
end
local function extract_back_references(notes, note_id)
assert(note_id ~= nil)
local back_references = {}
for _, note in ipairs(notes) do
if note.id == note_id then
goto continue
end
local references = note.references
for _, ref in ipairs(references) do
if ref.id == note_id then
table.insert(back_references, {
id = note.id,
title = note.title,
file_name = note.file_name,
linenr = ref.linenr,
})
break
end
end
::continue::
end
return back_references
end
local function extract_tags(line, linenr)
assert(line ~= nil)
local tags = {}
for tag in string.gmatch(line, "(%#%a[%w-]+)") do
local start_pos, _ = string.find(line, tag, 1, true)
local previous_char = string.sub(line, start_pos - 1, start_pos - 1)
if previous_char == "" or previous_char == " " then
table.insert(tags, { linenr = linenr, name = tag })
end
end
return tags
end
local function get_note_information(file_path)
local last_modified = fn.strftime("%Y-%m-%d.%H:%M:%S", fn.getftime(file_path))
if
s_note_cache_with_file_path[file_path] ~= nil
and s_note_cache_with_file_path[file_path].last_modified == last_modified
then
return s_note_cache_with_file_path[file_path]
end
local file = io.open(file_path, "r")
if file:read(0) == nil then
return nil
end
local info = {
file_name = file_path,
last_modified = last_modified,
tags = {},
references = {},
back_references = {},
}
local linenr = 0
while true do
linenr = linenr + 1
local line = file:read("*line")
if line == nil then
break
end
if line == "" then
goto continue
end
if info.id == nil then
local id_title = extract_id_and_title(line)
if id_title then
info = vim.tbl_extend("error", info, id_title)
goto continue
end
end
local refs = extract_references(line, linenr)
if refs then
vim.list_extend(info.references, refs)
end
local tags = extract_tags(line, linenr)
if tags then
vim.list_extend(info.tags, tags)
end
::continue::
end
s_note_cache_with_file_path[file_path] = info
s_note_cache_with_id[info.id] = info
return info
end
function M.get_note(id)
if s_note_cache_with_id[id] ~= nil then
return s_note_cache_with_id[id]
end
local _ = M.get_notes()
if s_note_cache_with_id[id] ~= nil then
return s_note_cache_with_id[id]
end
return nil
end
function M.get_notes()
local folder = config.get().notes_path
local files = get_files(folder)
local all_notes = {}
for _, file in ipairs(files) do
table.insert(all_notes, get_note_information(file))
end
for _, note in ipairs(all_notes) do
if note.id == nil then
goto continue
end
local back_references = extract_back_references(all_notes, note.id)
if back_references then
-- When notes are cached, `back_references` field will have the references from before.
-- Since the files that refer to this one might have changed, we'll overwrite it here.
-- extract_back_references() already re-processes the references.
note.back_references = back_references
end
::continue::
end
return all_notes
end
function M.get_tags()
local notes = M.get_notes()
local tags = {}
for _, note in ipairs(notes) do
if #note.tags == 0 then
goto continue
end
for _, tag in ipairs(note.tags) do
table.insert(tags, {
linenr = tag.linenr,
name = tag.name,
file_name = note.file_name,
})
end
::continue::
end
return tags
end
return M

View File

@ -0,0 +1,31 @@
--=============================================================================
-- config.lua --- the config module for zettelkasten
-- Copyright (c) 2016-2022 Wang Shidong & Contributors
-- Author: Wang Shidong < wsdjeg@outlook.com >
-- URL: https://spacevim.org
-- License: GPLv3
--=============================================================================
local M = {}
if vim.g.zettelkasten_directory and vim.g.zettelkasten_directory ~= '' then
M.zettel_dir = vim.g.zettelkasten_directory
else
M.zettel_dir = '~/.zettelkasten/'
end
local s_config = {
notes_path = "",
preview_command = "pedit",
browseformat = "%f - %h [%r Refs] [%b B-Refs] %t",
}
M.get = function()
return s_config
end
M._set = function(new_config)
s_config = vim.tbl_extend("force", s_config, new_config)
end
return M

View File

@ -0,0 +1,54 @@
local M = {}
local s_formatters = {
["%r"] = function(line)
return #line.references
end,
["%b"] = function(line)
return #line.back_references
end,
["%f"] = function(line)
return vim.fn.fnamemodify(line.file_name, ":t")
end,
["%h"] = function(line)
return line.title
end,
["%d"] = function(line)
return line.id
end,
["%t"] = function(line)
local tags = {}
for _, tag in ipairs(line.tags) do
if vim.tbl_contains(tags, tag.name) == false then
table.insert(tags, tag.name)
end
end
return table.concat(tags, " ")
end,
}
local function get_format_keys(format)
local matches = {}
for w in string.gmatch(format, "%%%a") do
table.insert(matches, w)
end
return matches
end
function M.format(lines, format)
local formatted_lines = {}
local modifiers = get_format_keys(format)
for _, line in ipairs(lines) do
local cmps = format
for _, modifier in ipairs(modifiers) do
cmps = string.gsub(cmps, "%" .. modifier, s_formatters[modifier](line))
end
table.insert(formatted_lines, cmps)
end
return formatted_lines
end
return M

View File

@ -0,0 +1,23 @@
local M = {}
local log_levels = vim.log.levels
local s_log_level = log_levels.INFO
local logger = require("spacevim.logger").derive("zettel")
function M.set_level(level)
s_log_level = level
end
function M.notify(msg, level, opts)
if level >= s_log_level then
local tag = opts.tag or "[zettelkasten]"
vim.notify(tag .. " " .. msg, level, opts)
end
end
function M.info(msg) -- {{{
logger.info(msg)
end
-- }}}
return M

View File

@ -0,0 +1,31 @@
local api = vim.api
local s_config = require("zettelkasten.config")
if vim.fn.exists(":ZkNew") == 0 then
vim.cmd([[command ZkNew :lua _G.zettelkasten.zknew()]])
end
if vim.fn.exists(":ZkBrowse") == 0 then
vim.cmd([[command ZkBrowse :lua _G.zettelkasten.zkbrowse()]])
end
_G.zettelkasten = {
tagfunc = require("zettelkasten").tagfunc,
completefunc = require("zettelkasten").completefunc,
zknew = function()
vim.cmd([[new | setlocal filetype=markdown]])
if s_config.zettel_dir ~= "" then
if vim.fn.isdirectory(s_config.zettel_dir) == 0 then
vim.fn.mkdir(vim.fn.expand(s_config.zettel_dir), 'p', '0700')
end
vim.cmd("lcd " .. s_config.zettel_dir)
end
vim.cmd("normal ggI# New Note")
require("zettelkasten").set_note_id(vim.api.nvim_get_current_buf())
vim.cmd("normal $")
end,
zkbrowse = function()
vim.cmd("edit zk://browser")
end,
}

2
bundle/vim-zettelkasten/stylua.toml vendored Normal file
View File

@ -0,0 +1,2 @@
indent_type = "Spaces"
column_width = 100

View File

@ -0,0 +1,13 @@
if "zkbrowser" !=# get(b:, "current_syntax", "zkbrowser")
finish
endif
syntax match ZkFileName '[0-9]\+-[0-9]\+-[0-9]\+-[0-9]\+-[0-9]\+-[0-9]\+\.md'
syntax match ZkRefCount '\[[0-9]\+ .*\]'
syntax match ZkTag '#\<\k\+\>'
highlight default link ZkFileName Label
highlight default link ZkRefCount Link
highlight default link ZkTag Tag
let b:current_syntax = "zkbrowser"

View File

@ -0,0 +1,38 @@
---
title: "SpaceVim zettelkasten layer"
description: "This layers adds extensive support for zettelkasten"
---
# [Available Layers](../) >> zettelkasten
<!-- vim-markdown-toc GFM -->
- [Description](#description)
- [Install](#install)
- [Layer options](#layer-options)
- [Key bindings](#key-bindings)
<!-- vim-markdown-toc -->
## Description
This layer adds support for zettelkasten in neovim.
## Install
To use this configuration layer, update your custom configuration file with:
```toml
[[layers]]
name = "zettelkasten"
```
## Layer options
- `zettel_dir`: set the default zettelkasten directory
## Key bindings
| Key bindings | description |
| ------------ | --------------- |
| `SPC m z n` | create new note |