Skip to content

feat(ts)!: add link and timestamp tree-sitter captures #912

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/luarocks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ jobs:
with:
version: ${{ env.LUAROCKS_VERSION }}
dependencies: |
tree-sitter-orgmode ~> 1
tree-sitter-orgmode ~> 2
2 changes: 2 additions & 0 deletions docs/configuration.org
Original file line number Diff line number Diff line change
Expand Up @@ -2825,6 +2825,8 @@ The following highlight groups are used:
- =@org.drawer=: Drawer start/end delimiters - linked to =@property=
- =@org.tag=: A tag for a headline item, shown on the righthand side like =:foo:= - linked to =@tag.attribute=
- =@org.plan=: =SCHEDULED=, =DEADLINE=, =CLOSED=, etc. keywords - linked to =Constant=
- =@org.block=: A =begin/end= block (example: =#begin_src=) - linked to =@comment=
- =@org.inline_block=: A =src_lang= block (example: src_lua{ print('foo') } ) - linked to =@comment=
- =@org.comment=: A comment block - linked to =@comment=
- =@org.latex_env=: LaTeX block - linked to =@markup.environment=
- =@org.directive=: Blocks starting with =#+= - linked to =@comment=
Expand Down
13 changes: 0 additions & 13 deletions lua/orgmode/colors/highlighter/markup/dates.lua
Original file line number Diff line number Diff line change
Expand Up @@ -171,17 +171,4 @@ function OrgDates:prepare_highlights(highlights)
return extmarks
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
10 changes: 3 additions & 7 deletions lua/orgmode/colors/highlighter/markup/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ end
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 }),
footnote = require('orgmode.colors.highlighter.markup.footnotes'):new({ markup = self }),
latex = require('orgmode.colors.highlighter.markup.latex'):new({ markup = self }),
}
Expand Down Expand Up @@ -72,9 +70,7 @@ end
function OrgMarkup:get_node_highlights(root_node, source, line)
local result = {
emphasis = {},
link = {},
latex = {},
date = {},
footnote = {},
}
---@type OrgMarkupNode[]
Expand Down Expand Up @@ -230,7 +226,7 @@ function OrgMarkup:has_valid_parent(item)
return false
end

if parent:type() == 'paragraph' then
if parent:type() == 'paragraph' or parent:type() == 'link_desc' then
return true
end

Expand All @@ -248,8 +244,8 @@ function OrgMarkup:has_valid_parent(item)
return true
end

if self.parsers[item.type].has_valid_parent then
return self.parsers[item.type]:has_valid_parent(item)
if parent:type() == 'value' then
return p and p:type() == 'property' or false
end

return false
Expand Down
3 changes: 2 additions & 1 deletion lua/orgmode/colors/highlighter/markup/link.lua
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ function OrgLink:_set_directive()
---@type TSNode
local capture_id = pred[2]
local node = match[capture_id]
node = node and node[#node]
metadata['image.ignore'] = true

if not node or not self.has_extmark_url_support then
Expand Down Expand Up @@ -63,7 +64,7 @@ function OrgLink:_set_directive()
end
metadata['image.ignore'] = nil
metadata['image.src'] = url
end, { force = true, all = false })
end, { force = true, all = true })
end

---@param node TSNode
Expand Down
73 changes: 41 additions & 32 deletions lua/orgmode/config/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ local PriorityState = require('orgmode.objects.priority_state')
---@class OrgConfig:OrgConfigOpts
---@field opts table
---@field todo_keywords OrgTodoKeywords
---@field priorities table<string, { type: string, hl_group: string }>
local Config = {}

---@param opts? table
function Config:new(opts)
local data = {
opts = vim.tbl_deep_extend('force', defaults, opts or {}),
todo_keywords = nil,
priorities = nil,
}
setmetatable(data, self)
return data
Expand All @@ -41,6 +43,7 @@ end
---@return OrgConfig
function Config:extend(opts)
self.todo_keywords = nil
self.priorities = nil
opts = opts or {}
self:_deprecation_notify(opts)
if not self:_are_priorities_valid(opts) then
Expand Down Expand Up @@ -326,6 +329,10 @@ function Config:get_priority_range()
end

function Config:get_priorities()
if self.priorities then
return self.priorities
end

local priorities = {
[self.opts.org_priority_highest] = { type = 'highest', hl_group = '@org.priority.highest' },
}
Expand All @@ -351,6 +358,9 @@ function Config:get_priorities()
-- we need to overwrite the lowest value set by the second loop
priorities[self.opts.org_priority_lowest] = { type = 'lowest', hl_group = '@org.priority.lowest' }

-- Cache priorities to avoid unnecessary recalculations
self.priorities = priorities

return priorities
end

Expand All @@ -360,23 +370,24 @@ function Config:setup_ts_predicates()

vim.treesitter.query.add_predicate('org-is-todo-keyword?', function(match, _, source, predicate)
local node = match[predicate[2]]
node = node and node[#node]
if node then
local text = vim.treesitter.get_node_text(node, source)
return todo_keywords[text] and todo_keywords[text].type == predicate[3] or false
end

return false
end, { force = true, all = false })
end, { force = true, all = true })

local org_cycle_separator_lines = math.max(self.opts.org_cycle_separator_lines, 0)

vim.treesitter.query.add_directive('org-set-fold-offset!', function(match, _, bufnr, pred, metadata)
if org_cycle_separator_lines == 0 then
return
end
---@type TSNode | nil
local capture_id = pred[2]
local section_node = match[capture_id]
section_node = section_node and section_node[#section_node]
if not capture_id or not section_node or section_node:type() ~= 'section' then
return
end
Expand All @@ -402,65 +413,63 @@ function Config:setup_ts_predicates()
end
range[3] = range[3] - 1
metadata[capture_id].range = range
end, { force = true, all = false })
end, { force = true, all = true })

vim.treesitter.query.add_predicate('org-is-valid-priority?', function(match, _, source, predicate)
---@type TSNode | nil
local node = match[predicate[2]]
local type = predicate[3]
node = node and node[#node]
if not node then
return false
end

local type = predicate[3]
local text = vim.treesitter.get_node_text(node, source)
local is_valid = valid_priorities[text] and valid_priorities[text].type == type
if not is_valid then
return false
end
local priority_text = '[#' .. text .. ']'
local full_node_text = vim.treesitter.get_node_text(node:parent(), source)
if priority_text ~= full_node_text then
return false
end
-- Leave only priority cookie: [#A] -> A
text = text:sub(3, -2)
return valid_priorities[text] and valid_priorities[text].type == type
end, { force = true, all = true })

local prev_sibling = node:parent():prev_sibling()
-- If first child, consider it valid
if not prev_sibling then
return true
vim.treesitter.query.add_directive('org-set-block-language!', function(match, _, bufnr, pred, metadata)
local lang_node = match[pred[2]]
lang_node = lang_node and lang_node[#lang_node]
if not lang_node then
return
end

-- If prev sibling has more prev siblings, it means that the prev_sibling is not a todo keyword
-- so this priority is not valid
if prev_sibling:prev_sibling() then
return false
local text = vim.treesitter.get_node_text(lang_node, bufnr)
if not text or vim.trim(text) == '' then
return
end
metadata['injection.language'] = utils.detect_filetype(text, true)
end, { force = true, all = true })

local todo_text = vim.treesitter.get_node_text(prev_sibling, source)
local is_prev_sibling_todo_keyword = todo_keywords[todo_text] and true or false
return is_prev_sibling_todo_keyword
end, { force = true, all = false })

vim.treesitter.query.add_directive('org-set-block-language!', function(match, _, bufnr, pred, metadata)
vim.treesitter.query.add_directive('org-set-inline-block-language!', function(match, _, bufnr, pred, metadata)
local lang_node = match[pred[2]]
lang_node = lang_node and lang_node[#lang_node]
if not lang_node then
return
end
local text = vim.treesitter.get_node_text(lang_node, bufnr)
if not text or vim.trim(text) == '' then
return
end
-- Remove `src_` part: src_lua -> lua
text = text:sub(5)
-- Remove opening brackend and parameters: lua[params]{ -> lua
text = text:gsub('[%{%[].*', '')
metadata['injection.language'] = utils.detect_filetype(text, true)
end, { force = true, all = false })
end, { force = true, all = true })

vim.treesitter.query.add_predicate('org-is-headline-level?', function(match, _, _, predicate)
---@type TSNode
local node = match[predicate[2]]
local level = tonumber(predicate[3])
node = node and node[#node]
if not node then
return false
end
local level = tonumber(predicate[3])
local _, _, _, node_end_col = node:range()
return ((node_end_col - 1) % 8) + 1 == level
end, { force = true, all = false })
end, { force = true, all = true })
end

---@param content table
Expand Down
1 change: 0 additions & 1 deletion lua/orgmode/files/elements/logbook.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
local Range = require('orgmode.files.elements.range')
local utils = require('orgmode.utils')
local config = require('orgmode.config')
local Date = require('orgmode.objects.date')
local Duration = require('orgmode.objects.duration')

Expand Down
Loading