Skip to content

Commit

Permalink
feat: Highlight dates with tree-sitter
Browse files Browse the repository at this point in the history
  • Loading branch information
kristijanhusak committed Feb 16, 2024
1 parent 59b1329 commit 6fb42c4
Show file tree
Hide file tree
Showing 10 changed files with 323 additions and 104 deletions.
7 changes: 4 additions & 3 deletions lua/orgmode/colors/highlighter/markup/_meta.lua
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
---@meta
---@alias OrgMarkupRange { line: number, start_col: number, end_col: number }

---@alias OrgMarkupParserType 'emphasis' | 'link' | 'latex'
---@alias OrgMarkupParserType 'emphasis' | 'link' | 'latex' | 'date'

---@class OrgMarkupNode
---@field type OrgMarkupParserType
---@field char string
---@field seek_char string
---@field id string
---@field seek_id string
---@field nestable boolean
---@field node TSNode
---@field range OrgMarkupRange
Expand All @@ -15,7 +16,7 @@
---@class OrgMarkupHighlight
---@field from OrgMarkupRange
---@field to OrgMarkupRange
---@field type string
---@field char string

---@class OrgMarkupHighlighter
---@field parse_node fun(self: OrgMarkupHighlighter, node: TSNode): OrgMarkupNode | false
Expand Down
156 changes: 156 additions & 0 deletions lua/orgmode/colors/highlighter/markup/dates.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
---@class OrgDatesHighlighter : OrgMarkupHighlighter
---@field private markup OrgMarkupHighlighter
local OrgDates = {}

---@param opts { markup: OrgMarkupHighlighter }
function OrgDates:new(opts)
local data = {
markup = opts.markup,
}
setmetatable(data, self)
self.__index = self
return data
end

---@param node TSNode
---@return OrgMarkupNode | false
function OrgDates:parse_node(node)
local type = node:type()
if type == '[' or type == '<' then
return self:_parse_start_node(node)
end

if type == ']' or type == '>' then
return self:_parse_end_node(node)
end

return false
end

---@private
---@param node TSNode
---@return OrgMarkupNode | false
function OrgDates:_parse_start_node(node)
local node_type = node:type()
local prev_sibling = node:prev_sibling()
-- Ignore links
if prev_sibling and (node_type == '[' and prev_sibling:type() == '[') then
return false
end
local expected_next_siblings = {
{
type = 'num',
length = 4,
},
{
type = '-',
length = 1,
},
{
type = 'num',
length = 2,
},
{
type = '-',
length = 1,
},
{
type = 'num',
length = 2,
},
}
local next_sibling = node:next_sibling()

for _, sibling in ipairs(expected_next_siblings) do
if not next_sibling or next_sibling:type() ~= sibling.type then
return false
end
local _, sc, _, ec = next_sibling:range()
if (ec - sc) ~= sibling.length then
return false
end
next_sibling = next_sibling:next_sibling()
end
local id = table.concat({ 'date', node_type }, '_')
local seek_id = table.concat({ 'date', node_type == '[' and ']' or '>' }, '_')

return {
type = 'date',
id = id,
char = node_type,
seek_id = seek_id,
nestable = false,
range = self.markup:node_to_range(node),
node = node,
}
end

---@private
---@param node TSNode
---@return OrgMarkupNode | false
function OrgDates:_parse_end_node(node)
local node_type = node:type()
local prev_sibling = node:prev_sibling()
local next_sibling = node:next_sibling()
local is_prev_sibling_valid = not prev_sibling or prev_sibling:type() == 'str' or prev_sibling:type() == 'num'
-- Ensure it's not a link
local is_next_sibling_valid = not next_sibling or (node_type == ']' and next_sibling:type() ~= ']')
if is_prev_sibling_valid and is_next_sibling_valid then
local id = table.concat({ 'date', node_type }, '_')
local seek_id = table.concat({ 'date', node_type == ']' and '[' or '<' }, '_')

return {
type = 'date',
id = id,
seek_id = seek_id,
char = node_type,
nestable = false,
range = self.markup:node_to_range(node),
node = node,
}
end

return false
end

---@param entry OrgMarkupNode
---@return boolean
function OrgDates:is_valid_start_node(entry)
return entry.type == 'date' and (entry.id == 'date_[' or entry.id == 'date_<')
end

---@param entry OrgMarkupNode
---@return boolean
function OrgDates:is_valid_end_node(entry)
return entry.type == 'date' and (entry.id == 'date_]' or entry.id == 'date_>')
end

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

for _, entry in ipairs(highlights) do
vim.api.nvim_buf_set_extmark(bufnr, namespace, entry.from.line, entry.from.start_col, {
ephemeral = true,
end_col = entry.to.end_col,
hl_group = entry.char == '>' and 'OrgTSTimestampActive' or 'OrgTSTimestampInactive',
priority = 110,
})
end
end

---@param item OrgMarkupNode
---@return boolean
function OrgDates:has_valid_parent(item)
---At this point we know that node has 2 valid parents
local parent = item.node:parent():parent()

if parent and parent:type() == 'value' then
return parent:parent() and parent:parent():type() == 'property' or false
end

return false
end

return OrgDates
25 changes: 14 additions & 11 deletions lua/orgmode/colors/highlighter/markup/emphasis.lua
Original file line number Diff line number Diff line change
Expand Up @@ -68,26 +68,26 @@ function OrgEmphasis:highlight(highlights, bufnr)
vim.api.nvim_buf_set_extmark(bufnr, namespace, entry.from.line, entry.from.start_col, {
ephemeral = true,
end_col = entry.from.start_col + hl_offset,
hl_group = markers[entry.type].hl_name .. '_delimiter',
spell = markers[entry.type].spell,
hl_group = markers[entry.char].hl_name .. '_delimiter',
spell = markers[entry.char].spell,
priority = 110 + entry.from.start_col,
})

-- Closing delimiter
vim.api.nvim_buf_set_extmark(bufnr, namespace, entry.from.line, entry.to.end_col - hl_offset, {
ephemeral = true,
end_col = entry.to.end_col,
hl_group = markers[entry.type].hl_name .. '_delimiter',
spell = markers[entry.type].spell,
hl_group = markers[entry.char].hl_name .. '_delimiter',
spell = markers[entry.char].spell,
priority = 110 + entry.from.start_col,
})

-- Main body highlight
vim.api.nvim_buf_set_extmark(bufnr, namespace, entry.from.line, entry.from.start_col + hl_offset, {
ephemeral = true,
end_col = entry.to.end_col - hl_offset,
hl_group = markers[entry.type].hl_name,
spell = markers[entry.type].spell,
hl_group = markers[entry.char].hl_name,
spell = markers[entry.char].spell,
priority = 110 + entry.from.start_col,
})

Expand All @@ -109,16 +109,19 @@ end
---@param node TSNode
---@return OrgMarkupNode | false
function OrgEmphasis:parse_node(node)
local type = node:type()
if not markers[type] then
local node_type = node:type()
if not markers[node_type] then
return false
end

local id = table.concat({'emphasis', node_type}, '_')

return {
type = 'emphasis',
char = type,
seek_char = type,
nestable = markers[type].nestable,
char = node_type,
id = id,
seek_id = id,
nestable = markers[node_type].nestable,
range = self.markup:node_to_range(node),
node = node,
}
Expand Down
31 changes: 21 additions & 10 deletions lua/orgmode/colors/highlighter/markup/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ function OrgMarkup:_init_highlighters()
self.parsers = {
emphasis = require('orgmode.colors.highlighter.markup.emphasis'):new({ markup = self }),
link = require('orgmode.colors.highlighter.markup.link'):new({ markup = self }),
date = require('orgmode.colors.highlighter.markup.dates'):new({ markup = self }),
latex = require('orgmode.colors.highlighter.markup.latex'):new({ markup = self }),
}
end
Expand Down Expand Up @@ -54,6 +55,7 @@ function OrgMarkup:_get_highlights(bufnr, line, tree)
emphasis = {},
link = {},
latex = {},
date = {},
}
---@type OrgMarkupNode[]
local entries = {}
Expand Down Expand Up @@ -81,34 +83,35 @@ function OrgMarkup:_get_highlights(bufnr, line, tree)
if last_seek and not last_seek.nestable then
return false
end
if not self:has_valid_parent(item.node:parent()) then
if not self:has_valid_parent(item) then
return false
end
return self.parsers[item.type]:is_valid_start_node(item, bufnr)
end

local is_valid_end_item = function(item)
if not self:has_valid_parent(item.node:parent()) then
if not self:has_valid_parent(item) then
return false
end

return self.parsers[item.type]:is_valid_end_node(item, bufnr)
end

for _, item in ipairs(entries) do
local from = seek[item.seek_char]
local from = seek[item.seek_id]

if not from and not item.self_contained then
if is_valid_start_item(item) then
seek[item.char] = item
seek[item.id] = item
last_seek = item
end
goto continue
end

if is_valid_end_item(item) then
table.insert(result[item.type], {
type = item.char,
id = item.id,
char = item.char,
from = item.self_contained and item.range or from.range,
to = item.range,
})
Expand All @@ -122,7 +125,7 @@ function OrgMarkup:_get_highlights(bufnr, line, tree)
goto continue
end

seek[item.seek_char] = nil
seek[item.seek_id] = nil
for t, pos in pairs(seek) do
if
pos.range.line == from.range.line
Expand Down Expand Up @@ -180,12 +183,16 @@ function OrgMarkup:node_to_range(node)
}
end

---@param node? TSNode
function OrgMarkup:has_valid_parent(node)
if not node then
---@param item OrgMarkupNode
---@return boolean
function OrgMarkup:has_valid_parent(item)
-- expr
local parent = item.node:parent()
if not parent then
return false
end
local parent = node:parent()

parent = parent:parent()
if not parent then
return false
end
Expand All @@ -204,6 +211,10 @@ function OrgMarkup:has_valid_parent(node)
return p:type() == 'drawer' or p:type() == 'cell'
end

if self.parsers[item.type].has_valid_parent then
return self.parsers[item.type]:has_valid_parent(item)
end

return false
end

Expand Down
Loading

0 comments on commit 6fb42c4

Please sign in to comment.