1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-23 10:30:05 +08:00
SpaceVim/bundle/vim-zettelkasten/lua/zettelkasten.lua
2024-12-11 00:30:08 +08:00

355 lines
8.4 KiB
Lua

--=============================================================================
-- zettelkasten.lua --- zk plugin
-- Copyright (c) 2016-2023 Wang Shidong & Contributors
-- Author: Wang Shidong < wsdjeg@outlook.com >
-- URL: https://spacevim.org
-- License: GPLv3
--=============================================================================
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)
-- here the note sometimes do not have title, then it is nil
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.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(opt)
if config.zettel_dir == '' then
log.notify("'notes_path' option is required for note browsing.", log_levels.WARN, {})
return {}
end
local filter_tags = {}
for _, tag in ipairs(opt.tags) do
filter_tags[tag] = true
end
local all_notes = browser.get_notes()
local lines = {}
for _, note in ipairs(all_notes) do
local has_tag
if #opt.tags == 0 then
has_tag = true
else
for _, tag in ipairs(note.tags) do
if filter_tags[tag.name] then
has_tag = true
break
end
end
end
if has_tag then
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
end
return formatter.format(lines, config.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.zknew(opt) -- {{{
vim.cmd([[new | setlocal filetype=markdown]])
if config.zettel_dir ~= '' then
if vim.fn.isdirectory(config.zettel_dir) == 0 then
vim.fn.mkdir(vim.fn.expand(config.zettel_dir), 'p', '0700')
end
vim.cmd('lcd ' .. 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
-- }}}
function M.setup(opts)
opts = opts or {}
opts.notes_path = opts.notes_path or ''
config._set(opts)
end
return M