Skip to content
This repository has been archived by the owner on Aug 31, 2022. It is now read-only.

Commit

Permalink
feat: handle multiple zettel IDs on 1 line
Browse files Browse the repository at this point in the history
  • Loading branch information
Damien Rajon committed Jul 1, 2021
1 parent 3dbbf29 commit 14b1efd
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 93 deletions.
2 changes: 2 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
test:
@nvim --headless -c "PlenaryBustedDirectory tests/nerveux {minimal_init = 'tests/minimal_init.vim'}"
140 changes: 89 additions & 51 deletions lua/nerveux/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -46,55 +46,13 @@ end

function nerveux.add_virtual_title_current_line(buf, ln, line, is_overlay)
if type(line) ~= "string" then return end
local id = u.match_link(line)
if id == nil then return end
local start_col, end_col = u.find_link(line)
local is_folgezettel = string.sub(line, end_col, end_col) == "#"
local hl = (function()
if is_folgezettel then
return config.virtual_title_hl_folge
else
return config.virtual_title_hl
end
end)()

query_id(id, function(json)
if type(json) == "userdata" then return end
if json == nil then return end
if json.error then return end

local title = (function()
local is_at_eol = #line == end_col

if is_overlay then
do
local end_col_offset = end_col - 1
local start_col_offset = start_col - 2
return
u.rpad(json.Title, end_col_offset - start_col_offset, is_at_eol)
end
else
return json.Title
end
end)()
local all_links = u.get_all_link_indices(line)

local extmark_opts = {end_col = end_col, virt_text = {{title, hl}}}

if is_overlay then
extmark_opts.virt_text_pos = "overlay"
extmark_opts.virt_text_hide = true
end

-- This is needed because there seems to be a bug in
-- `virt_text_pos="overlay" when the virtual text starts at column 0`
local start_col_patched = (function()
if start_col == 1 then
return 2
else
return start_col
end
end)()
local all_titles = {}
local nb_resolved = 0

-- Final callback, once all the IDs have be queried
local function on_done(all)
-- This is called on `BufWrite` so this gets executed on `:wq` but has
-- an async call to `neuron`.
-- When the async calls back, the buffer is no longer visible so an
Expand All @@ -103,11 +61,91 @@ function nerveux.add_virtual_title_current_line(buf, ln, line, is_overlay)
-- This check helps with this issue.
-- We need to add `+ 0` here to cast buf as number, otherwise the call to
-- bufwinnr always returns -1 because the string is not valid
if vim.fn.bufwinnr(buf + 0) ~= -1 then
vim.api.nvim_buf_set_extmark(buf, ns, ln - 1, start_col_patched - 1,
extmark_opts)
if vim.fn.bufwinnr(buf + 0) == -1 then return end

if is_overlay then
for _, v in ipairs(all) do
local start_col_patched, _, extmark_opts = unpack(v)
vim.api.nvim_buf_set_extmark(buf, ns, ln - 1, start_col_patched - 1,
extmark_opts)
end
else
local all_only_titles = {}
for _, v in ipairs(all) do
local _, title = unpack(v)
table.insert(all_only_titles, title)
end
-- We want to display all the titles as virutal text
-- at the end of the line
local extmarkopts = {
end_col = 0,
virt_text = {
{table.concat(all_only_titles, ","), config.virtual_title_hl}
}
}

vim.api.nvim_buf_set_extmark(buf, ns, ln - 1, 1, extmarkopts)
end
end)
end

for k, v in ipairs(all_links) do
table.insert(all_titles, "")
local start_col, end_col, id, is_folgezettel = unpack(v)
local hl = (function()
if is_folgezettel then
return config.virtual_title_hl_folge
else
return config.virtual_title_hl
end
end)()

query_id(id, function(json)
nb_resolved = nb_resolved + 1
if type(json) == "userdata" then return end
if json == nil then return end
if json.error then return end

local title = (function()
local is_at_eol = #line == end_col

if is_overlay then
do
return u.rpad(json.Title, end_col + 1 - start_col, is_at_eol)
end
else
return json.Title
end
end)()

local extmark_opts = nil

if is_overlay then
extmark_opts = {
virt_text_pos = "overlay",
virt_text_hide = true,
end_col = end_col,
virt_text = {{title, hl}}
}
end

-- This is needed because there seems to be a bug in
-- `virt_text_pos="overlay" when the virtual text starts at column 0`
local start_col_patched = (function()
if start_col == 1 then
return 2
else
return start_col
end
end)()

all_titles[k] = {start_col_patched, title, extmark_opts}

-- All the links have been queried, call the final callback to actually
-- display the titles
if nb_resolved == #all_links then on_done(all_titles) end
end)
end

end

local function setup_autocmds()
Expand Down
91 changes: 49 additions & 42 deletions lua/nerveux/utils.lua
Original file line number Diff line number Diff line change
@@ -1,74 +1,81 @@
local utils = {}
local Job = require "plenary.job"

local DOUBLE_LINK_RE = "%[%[([ A-Za-z0-9|]+)%]%]#?"
local DOUBLE_LINK_RE = "%[%[(%w+)|?[%g%s]-%]%]#?"
local DOUBLE_LINK_RE_NOCAP = "%[%[[%g%s]-%]%]#?"

---@param s string
function utils.match_link(s)
return s:match(DOUBLE_LINK_RE)
end
function utils.match_link(s) return s:match(DOUBLE_LINK_RE) end

--- Returns a table containing all the IDs and they positions and if
-- they are folgezettels
function utils.get_all_link_indices(line)
local function get_all_ids_from_line(line_)
return line_:gmatch(DOUBLE_LINK_RE_NOCAP)
end

local function remove_link_decorations(match_)
return match_:gsub(DOUBLE_LINK_RE, "%1")
end

function utils.find_link(s)
return s:find(DOUBLE_LINK_RE)
local matches = get_all_ids_from_line(line)
local start = 1
local indices = {}
for match in matches do
local start_ix, end_ix = line:find(match, start, true)
local is_folgezettel = string.sub(line, end_ix, end_ix) == "#"
local id = remove_link_decorations(match)

table.insert(indices, {start_ix, end_ix, id, is_folgezettel })
start = end_ix
end

return indices
end

function utils.find_link(s) return s:find(DOUBLE_LINK_RE) end

--- Stolen from https://github.com/blitmap/lua-snippets/blob/master/string-pad.lua
function utils.lpad(s, l, is_eol)
local short_or_eq = #s <= l
local ss = (is_eol or short_or_eq) and s or (string.sub(s, 0, l) .. "")
local res = string.rep(" ", l - #ss) .. s
local short_or_eq = #s <= l
local ss = (is_eol or short_or_eq) and s or (string.sub(s, 0, l) .. "")
local res = string.rep(" ", l - #ss) .. s

return res
return res
end

--- Stolen from https://github.com/blitmap/lua-snippets/blob/master/string-pad.lua
function utils.pad(s, l, is_eol)
local res1 = utils.rpad(s, (l / 2) + #s, is_eol) -- pad to half-length + the length of s
local res2 = utils.lpad(res1, l, is_eol) -- right-pad our left-padded string to the full length
local res1 = utils.rpad(s, (l / 2) + #s, is_eol) -- pad to half-length + the length of s
local res2 = utils.lpad(res1, l, is_eol) -- right-pad our left-padded string to the full length

return res2
return res2
end

--- Stolen from https://github.com/blitmap/lua-snippets/blob/master/string-pad.lua
function utils.rpad(s, l, is_eol)
local short_or_eq = #s <= l
local ss = (is_eol or short_or_eq) and s or (string.sub(s, 1, l - 1) .. "")
local res = ss .. string.rep(" ", l - #ss)
local short_or_eq = #s <= l
local ss = (is_eol or short_or_eq) and s or (string.sub(s, 1, l - 1) .. "")
local res = ss .. string.rep(" ", l - #ss)

return res
return res
end

function utils.map(tbl, f)
local t = {}
for k, v in pairs(tbl) do
t[k] = f(v)
end
return t
local t = {}
for k, v in pairs(tbl) do t[k] = f(v) end
return t
end

utils.is_process_running = function(process_name, cb)
local j =
Job:new(
{
command = "pgrep",
args = {"-c", process_name}
}
)
j:start()
j:after_failure(
function()
return cb(nil, false)
end
)
j:after_success(
function(_, ret_code)
return cb(nil, tonumber(ret_code) == 0)
end
)
local j = Job:new({command = "pgrep", args = {"-c", process_name}})
j:start()
j:after_failure(function() return cb(nil, false) end)
j:after_success(
function(_, ret_code) return cb(nil, tonumber(ret_code) == 0) end)
end

function utils.get_zettel_id_from_fname()
return vim.fn.fnamemodify(vim.api.nvim_buf_get_name(0), ":t:r")
return vim.fn.fnamemodify(vim.api.nvim_buf_get_name(0), ":t:r")
end

return utils
4 changes: 4 additions & 0 deletions tests/minimal_init.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
set rtp+=.
runtime plugin/plenary.vim

nnoremap ,,x :luafile %<CR>
55 changes: 55 additions & 0 deletions tests/nerveux/link_extraction_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
local u = require "nerveux.utils"

describe("When extracting links", function()

it("should work on the simplest links", function()
do
local line = "[[12345]]"
assert.same({{1, 9, "12345", false}}, u.get_all_link_indices(line))
end

do
local line = "[[1234567890abcdefghijklmnopqrstuvwxyzABCDDE]]"
assert.same(
{{1, 46, "1234567890abcdefghijklmnopqrstuvwxyzABCDDE", false}},
u.get_all_link_indices(line))
end
end)

it("should work when a the link is in the middle of the line", function()
local line = "ok ca marche [[12345]] salut merci"
assert.same({{14, 22, "12345", false}}, u.get_all_link_indices(line))
end)

it("should work with multiple links on the same line ", function()
local line = "ok ca marche [[12345]] salut merci [[abcdef123]]"
assert.same({{14, 22, "12345", false}, {36, 48, "abcdef123", false}},
u.get_all_link_indices(line))
end)

it("should work with folgezettels", function()
local line = "ok ca marche [[12345]]#"
assert.same({{14, 23, "12345", true}}, u.get_all_link_indices(line))
end)

it("should work with a simple aliases", function()
do
local line = "ok ca marche [[12345|alias]]#"
assert.same({{14, #line, "12345", true}}, u.get_all_link_indices(line))
end

do
local line =
"ok ca marche [[12345zxcv|alias with spaces wow ! SPACES!@#%]]#"
assert.same({{14, #line, "12345zxcv", true}}, u.get_all_link_indices(line))
end
end)

it("should work with aliases and folgezettels with aliases", function()
local line =
"[[1232sd|this is an alias]] hi this is [[2547ad|HEy!!! ok]]# a link right there"
assert.same({{1, 27, "1232sd", false}, {40, 60, "2547ad", true}},
u.get_all_link_indices(line))
end)

end)

0 comments on commit 14b1efd

Please sign in to comment.