Skip to content

Commit

Permalink
feat(markup): Allow markup in link descriptions
Browse files Browse the repository at this point in the history
  • Loading branch information
kristijanhusak committed Jan 28, 2025
1 parent 4e75c55 commit f7c669a
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 78 deletions.
2 changes: 2 additions & 0 deletions lua/orgmode/colors/highlighter/markup/_meta.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
---@field node TSNode
---@field range OrgMarkupRange
---@field self_contained? boolean
---@field metadata? table<string, any>

---@class OrgMarkupHighlight
---@field from OrgMarkupRange
---@field to OrgMarkupRange
---@field char string
---@field metadata? table<string, any>

---@class OrgMarkupPreparedHighlight
---@field start_line number
Expand Down
5 changes: 3 additions & 2 deletions lua/orgmode/colors/highlighter/markup/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ function OrgMarkup:get_node_highlights(root_node, source, line)
char = item.char,
from = item.self_contained and item.range or from.range,
to = item.range,
metadata = item.metadata,
})

if last_seek and last_seek.type == item.type then
Expand Down Expand Up @@ -171,9 +172,9 @@ function OrgMarkup:get_prepared_headline_highlights(headline)
for type, highlight in pairs(highlights) do
vim.list_extend(
result,
self.parsers[type]:prepare_highlights(highlight, function(markup_highlight)
self.parsers[type]:prepare_highlights(highlight, function(start_col, end_col)
local text = headline.file:get_node_text(headline:node())
return text:sub(markup_highlight.from.start_col + 1, markup_highlight.to.end_col)
return text:sub(start_col, end_col)
end)
)
end
Expand Down
259 changes: 200 additions & 59 deletions lua/orgmode/colors/highlighter/markup/link.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---@class OrgLinkHighlighter : OrgMarkupHighlighter
---@field private markup OrgMarkupHighlighter
---@field private has_extmark_url_support boolean
---@field private last_start_node_id? string
local OrgLink = {
valid_capture_names = {
['link.start'] = true,
Expand Down Expand Up @@ -44,17 +45,42 @@ end
function OrgLink:_parse_start_node(node)
local node_type = node:type()
local next_sibling = node:next_sibling()
local prev_sibling = node:prev_sibling()

-- Start of link
if next_sibling and next_sibling:type() == '[' then
local id = table.concat({ 'link', node_type }, '_')
local id = table.concat({ 'link', '[' }, '_')
local seek_id = table.concat({ 'link', ']' }, '_')
self.last_start_node_id = node:id()
return {
type = 'link',
id = id,
char = node_type,
seek_id = seek_id,
nestable = false,
range = self.markup:node_to_range(node),
metadata = {
type = 'link_start',
},
node = node,
}
end

-- Start of link alias
if prev_sibling and prev_sibling:type() == ']' then
local id = table.concat({ 'link', '[' }, '_')
local seek_id = table.concat({ 'link', ']' }, '_')
return {
type = 'link',
id = id,
char = node_type,
seek_id = seek_id,
nestable = true,
range = self.markup:node_to_range(node),
metadata = {
type = 'link_alias_start',
start_node_id = self.last_start_node_id,
},
node = node,
}
end
Expand All @@ -68,8 +94,11 @@ end
function OrgLink:_parse_end_node(node)
local node_type = node:type()
local prev_sibling = node:prev_sibling()
if prev_sibling and prev_sibling:type() == ']' then
local id = table.concat({ 'link', node_type }, '_')
local next_sibling = node:next_sibling()

-- End of link, start of alias
if next_sibling and next_sibling:type() == '[' then
local id = table.concat({ 'link', ']' }, '_')
local seek_id = table.concat({ 'link', '[' }, '_')
return {
type = 'link',
Expand All @@ -79,9 +108,34 @@ function OrgLink:_parse_end_node(node)
range = self.markup:node_to_range(node),
nestable = false,
node = node,
metadata = {
type = 'link_end_alias_start',
start_node_id = self.last_start_node_id,
},
}
end

-- End of link
if prev_sibling and prev_sibling:type() == ']' then
local id = table.concat({ 'link', ']' }, '_')
local seek_id = table.concat({ 'link', '[' }, '_')
local result = {
type = 'link',
id = id,
char = node_type,
seek_id = seek_id,
range = self.markup:node_to_range(node),
nestable = false,
metadata = {
type = 'link_end',
},
node = node,
}
result.metadata.start_node_id = self.last_start_node_id
self.last_start_node_id = nil
return result
end

return false
end

Expand All @@ -97,17 +151,50 @@ function OrgLink:is_valid_end_node(entry)
return entry.type == 'link' and entry.id == 'link_]'
end

function OrgLink:_get_url(bufnr, line, start_col, end_col)
if not self.has_extmark_url_support then
return nil
end

return vim.api.nvim_buf_get_text(bufnr, line, start_col, line, end_col, {})[1]
end

---@param highlights OrgMarkupHighlight[]
---@param bufnr number
function OrgLink:highlight(highlights, bufnr)
local namespace = self.markup.highlighter.namespace
local ephemeral = self.markup:use_ephemeral()

for _, entry in ipairs(highlights) do
local link =
vim.api.nvim_buf_get_text(bufnr, entry.from.line, entry.from.start_col, entry.from.line, entry.to.end_col, {})[1]
local alias = link:find('%]%[') or 1
local link_end = link:find('%]%[') or (link:len() - 1)
for i, entry in ipairs(highlights) do
local prev_entry = highlights[i - 1]
local next_entry = highlights[i + 1]
if not entry.metadata.start_node_id then
goto continue
end

-- Alias without the valid end link
if
entry.metadata.type == 'link_end_alias_start'
and (
not next_entry
or next_entry.metadata.type ~= 'link_end'
or entry.metadata.start_node_id ~= next_entry.metadata.start_node_id
)
then
goto continue
end

-- End node without the valid alias
if
entry.metadata.type == 'link_end'
and (
prev_entry
and prev_entry.metadata.type == 'link_end_alias_start'
and prev_entry.metadata.start_node_id ~= entry.metadata.start_node_id
)
then
goto continue
end

local link_opts = {
ephemeral = ephemeral,
Expand All @@ -116,43 +203,83 @@ function OrgLink:highlight(highlights, bufnr)
priority = 110,
}

if self.has_extmark_url_support then
link_opts.url = alias > 1 and link:sub(3, alias - 1) or link:sub(3, -3)
if entry.metadata.type == 'link_end_alias_start' then
link_opts.url = self:_get_url(bufnr, entry.from.line, entry.from.start_col + 2, entry.to.end_col - 1)
link_opts.spell = false
entry.url = link_opts.url
-- Conceal the whole target (marked with << and >>)
-- <<[[https://neovim.io][>>Neovim]]
vim.api.nvim_buf_set_extmark(bufnr, namespace, entry.from.line, entry.from.start_col, {
ephemeral = ephemeral,
end_col = entry.to.end_col + 1,
conceal = '',
})
end

vim.api.nvim_buf_set_extmark(bufnr, namespace, entry.from.line, entry.from.start_col, link_opts)

vim.api.nvim_buf_set_extmark(bufnr, namespace, entry.from.line, entry.from.start_col, {
ephemeral = ephemeral,
end_col = entry.from.start_col + 1 + alias,
conceal = '',
})

vim.api.nvim_buf_set_extmark(bufnr, namespace, entry.from.line, entry.from.start_col + 2, {
ephemeral = ephemeral,
end_col = entry.from.start_col - 1 + link_end,
spell = false,
})
if entry.metadata.type == 'link_end' then
if prev_entry and prev_entry.metadata.type == 'link_end_alias_start' then
link_opts.url = prev_entry.url
else
link_opts.url = self:_get_url(bufnr, entry.from.line, entry.from.start_col + 2, entry.to.end_col - 2)
-- Conceal the start marker (marked with << and >>)
-- <<[[>>https://neovim.io][Neovim]]
vim.api.nvim_buf_set_extmark(bufnr, namespace, entry.from.line, entry.from.start_col, {
ephemeral = ephemeral,
end_col = entry.from.start_col + 2,
conceal = '',
})
end
-- Conceal the end marker (marked with << and >>)
-- [[https://neovim.io][Neovim<<]]>>
vim.api.nvim_buf_set_extmark(bufnr, namespace, entry.from.line, entry.to.end_col - 2, {
ephemeral = ephemeral,
end_col = entry.to.end_col,
conceal = '',
})
end

vim.api.nvim_buf_set_extmark(bufnr, namespace, entry.from.line, entry.to.end_col - 2, {
ephemeral = ephemeral,
end_col = entry.to.end_col,
conceal = '',
})
vim.api.nvim_buf_set_extmark(bufnr, namespace, entry.from.line, entry.from.start_col, link_opts)
::continue::
end
end

---@param highlights OrgMarkupHighlight[]
---@param source_getter_fn fun(highlight: OrgMarkupHighlight): string
---@param source_getter_fn fun(start_col: number, end_col: number): string
---@return OrgMarkupPreparedHighlight[]
function OrgLink:prepare_highlights(highlights, source_getter_fn)
local ephemeral = self.markup:use_ephemeral()
local extmarks = {}

for _, entry in ipairs(highlights) do
local link = source_getter_fn(entry)
local alias = link:find('%]%[') or 1
local link_end = link:find('%]%[') or (link:len() - 1)
for i, entry in ipairs(highlights) do
local prev_entry = highlights[i - 1]
local next_entry = highlights[i + 1]
if not entry.metadata.start_node_id then
goto continue
end

-- Alias without the valid end link
if
entry.metadata.type == 'link_end_alias_start'
and (
not next_entry
or next_entry.metadata.type ~= 'link_end'
or entry.metadata.start_node_id ~= next_entry.metadata.start_node_id
)
then
goto continue
end

-- End node without the valid alias
if
entry.metadata.type == 'link_end'
and (
prev_entry
and prev_entry.metadata.type == 'link_end_alias_start'
and prev_entry.metadata.start_node_id ~= entry.metadata.start_node_id
)
then
goto continue
end

local link_opts = {
ephemeral = ephemeral,
Expand All @@ -161,8 +288,45 @@ function OrgLink:prepare_highlights(highlights, source_getter_fn)
priority = 110,
}

if self.has_extmark_url_support then
link_opts.url = alias > 1 and link:sub(3, alias - 1) or link:sub(3, -3)
if entry.metadata.type == 'link_end_alias_start' then
link_opts.url = source_getter_fn(entry.from.end_col + 2, entry.to.end_col - 1)
link_opts.spell = false
entry.url = link_opts.url
-- Conceal the whole target (marked with << and >>)
-- <<[[https://neovim.io][>>Neovim]]
table.insert(extmarks, {
start_line = entry.from.line,
start_col = entry.from.start_col,
end_col = entry.to.end_col + 1,
ephemeral = ephemeral,
conceal = '',
})
end

if entry.metadata.type == 'link_end' then
if prev_entry and prev_entry.metadata.type == 'link_end_alias_start' then
link_opts.url = prev_entry.url
else
link_opts.url = source_getter_fn(entry.from.end_col + 2, entry.to.end_col - 2)
-- Conceal the start marker (marked with << and >>)
-- <<[[>>https://neovim.io][Neovim]]
table.insert(extmarks, {
start_line = entry.from.line,
start_col = entry.from.start_col,
end_col = entry.from.start_col + 2,
ephemeral = ephemeral,
conceal = '',
})
end
-- Conceal the end marker (marked with << and >>)
-- [[https://neovim.io][Neovim<<]]>>
table.insert(extmarks, {
start_line = entry.from.line,
start_col = entry.to.end_col - 2,
end_col = entry.to.end_col,
ephemeral = ephemeral,
conceal = '',
})
end

table.insert(extmarks, {
Expand All @@ -174,30 +338,7 @@ function OrgLink:prepare_highlights(highlights, source_getter_fn)
priority = link_opts.priority,
url = link_opts.url,
})

table.insert(extmarks, {
start_line = entry.from.line,
start_col = entry.from.start_col,
end_col = entry.from.start_col + 1 + alias,
ephemeral = ephemeral,
conceal = '',
})

table.insert(extmarks, {
start_line = entry.from.line,
start_col = entry.from.start_col + 2,
end_col = entry.from.start_col - 1 + link_end,
ephemeral = ephemeral,
spell = false,
})

table.insert(extmarks, {
start_line = entry.from.line,
start_col = entry.to.end_col - 2,
end_col = entry.to.end_col,
ephemeral = ephemeral,
conceal = '',
})
::continue::
end

return extmarks
Expand Down
Loading

0 comments on commit f7c669a

Please sign in to comment.