Skip to content

Commit 36141c7

Browse files
feat(ts): add link and timestamp tree-sitter captures
1 parent 998035a commit 36141c7

File tree

15 files changed

+103
-370
lines changed

15 files changed

+103
-370
lines changed

lua/orgmode/colors/highlighter/markup/dates.lua

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -171,17 +171,4 @@ function OrgDates:prepare_highlights(highlights)
171171
return extmarks
172172
end
173173

174-
---@param item OrgMarkupNode
175-
---@return boolean
176-
function OrgDates:has_valid_parent(item)
177-
---At this point we know that node has 2 valid parents
178-
local parent = item.node:parent():parent()
179-
180-
if parent and parent:type() == 'value' then
181-
return parent:parent() and parent:parent():type() == 'property' or false
182-
end
183-
184-
return false
185-
end
186-
187174
return OrgDates

lua/orgmode/colors/highlighter/markup/init.lua

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ end
2222
function OrgMarkup:_init_highlighters()
2323
self.parsers = {
2424
emphasis = require('orgmode.colors.highlighter.markup.emphasis'):new({ markup = self }),
25-
link = require('orgmode.colors.highlighter.markup.link'):new({ markup = self }),
26-
date = require('orgmode.colors.highlighter.markup.dates'):new({ markup = self }),
2725
footnote = require('orgmode.colors.highlighter.markup.footnotes'):new({ markup = self }),
2826
latex = require('orgmode.colors.highlighter.markup.latex'):new({ markup = self }),
2927
}
@@ -72,9 +70,7 @@ end
7270
function OrgMarkup:get_node_highlights(root_node, source, line)
7371
local result = {
7472
emphasis = {},
75-
link = {},
7673
latex = {},
77-
date = {},
7874
footnote = {},
7975
}
8076
---@type OrgMarkupNode[]
@@ -230,7 +226,7 @@ function OrgMarkup:has_valid_parent(item)
230226
return false
231227
end
232228

233-
if parent:type() == 'paragraph' then
229+
if parent:type() == 'paragraph' or parent:type() == 'link_desc' then
234230
return true
235231
end
236232

@@ -248,8 +244,8 @@ function OrgMarkup:has_valid_parent(item)
248244
return true
249245
end
250246

251-
if self.parsers[item.type].has_valid_parent then
252-
return self.parsers[item.type]:has_valid_parent(item)
247+
if parent:type() == 'value' then
248+
return p and p:type() == 'property' or false
253249
end
254250

255251
return false

lua/orgmode/config/init.lua

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -405,38 +405,15 @@ function Config:setup_ts_predicates()
405405
end, { force = true, all = false })
406406

407407
vim.treesitter.query.add_predicate('org-is-valid-priority?', function(match, _, source, predicate)
408+
---@type TSNode | nil
408409
local node = match[predicate[2]]
409410
local type = predicate[3]
410411
if not node then
411412
return false
412413
end
413414

414415
local text = vim.treesitter.get_node_text(node, source)
415-
local is_valid = valid_priorities[text] and valid_priorities[text].type == type
416-
if not is_valid then
417-
return false
418-
end
419-
local priority_text = '[#' .. text .. ']'
420-
local full_node_text = vim.treesitter.get_node_text(node:parent(), source)
421-
if priority_text ~= full_node_text then
422-
return false
423-
end
424-
425-
local prev_sibling = node:parent():prev_sibling()
426-
-- If first child, consider it valid
427-
if not prev_sibling then
428-
return true
429-
end
430-
431-
-- If prev sibling has more prev siblings, it means that the prev_sibling is not a todo keyword
432-
-- so this priority is not valid
433-
if prev_sibling:prev_sibling() then
434-
return false
435-
end
436-
437-
local todo_text = vim.treesitter.get_node_text(prev_sibling, source)
438-
local is_prev_sibling_todo_keyword = todo_keywords[todo_text] and true or false
439-
return is_prev_sibling_todo_keyword
416+
return valid_priorities[text] and valid_priorities[text].type == type
440417
end, { force = true, all = false })
441418

442419
vim.treesitter.query.add_directive('org-set-block-language!', function(match, _, bufnr, pred, metadata)

lua/orgmode/files/elements/logbook.lua

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
local Range = require('orgmode.files.elements.range')
22
local utils = require('orgmode.utils')
3-
local config = require('orgmode.config')
43
local Date = require('orgmode.objects.date')
54
local Duration = require('orgmode.objects.duration')
65

lua/orgmode/files/file.lua

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -735,21 +735,17 @@ memoize('get_links')
735735
---@return OrgHyperlink[]
736736
function OrgFile:get_links()
737737
self:parse(true)
738-
local ts_query = ts_utils.get_query([[
739-
(paragraph (expr) @links)
740-
(drawer (contents (expr) @links))
741-
(headline (item (expr)) @links)
738+
local links = {}
739+
local matches = self:get_ts_matches([[
740+
(link) @link
741+
(link_desc) @link
742742
]])
743743

744-
local links = {}
745-
local processed_lines = {}
746-
for _, match in ts_query:iter_captures(self.root, self:get_source()) do
747-
local line = match:start()
748-
if not processed_lines[line] then
749-
vim.list_extend(links, Hyperlink.all_from_line(self.lines[line + 1], line + 1))
750-
processed_lines[line] = true
751-
end
744+
local source = self:get_source()
745+
for _, match in ipairs(matches) do
746+
table.insert(links, Hyperlink.from_node(match.link.node, source))
752747
end
748+
753749
return links
754750
end
755751

lua/orgmode/files/headline.lua

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -67,26 +67,21 @@ end
6767
memoize('get_priority')
6868
---@return string, TSNode | nil
6969
function Headline:get_priority()
70-
local _, todo_node = self:get_todo()
7170
local item = self:_get_child_node('item')
7271

73-
local priority_node = item and item:named_child(1)
72+
local priority_node = item and item:field('priority')[1]
7473

75-
if not todo_node then
76-
priority_node = item and item:named_child(0)
74+
if not priority_node then
75+
return '', nil
7776
end
7877

79-
if priority_node then
80-
local text = self.file:get_node_text(priority_node)
81-
local priority = text:match('%[#(%w+)%]')
82-
if priority then
83-
local priorities = config:get_priorities()
84-
if priorities[priority] then
85-
return priority, priority_node
86-
end
87-
end
78+
local value = self.file:get_node_text(priority_node:field('value')[1])
79+
80+
if not value then
81+
return '', nil
8882
end
89-
return '', nil
83+
84+
return value, priority_node
9085
end
9186

9287
---@param amount number
@@ -424,15 +419,6 @@ function Headline:get_title()
424419
return title, offset
425420
end
426421

427-
function Headline:get_title_with_priority()
428-
local priority = self:get_priority()
429-
local title = self:get_title()
430-
if priority ~= '' then
431-
return ('[#%s] %s'):format(priority, self:get_title())
432-
end
433-
return title
434-
end
435-
436422
memoize('get_own_properties')
437423
---@return table<string, string>, TSNode | nil
438424
function Headline:get_own_properties()

lua/orgmode/org/links/hyperlink.lua

Lines changed: 17 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
local OrgLinkUrl = require('orgmode.org.links.url')
22
local Range = require('orgmode.files.elements.range')
3+
local ts_utils = require('orgmode.utils.treesitter')
34

45
---@class OrgHyperlink
56
---@field url OrgLinkUrl
@@ -21,61 +22,26 @@ function OrgHyperlink:new(str, range)
2122
return this
2223
end
2324

24-
---@return string
25-
function OrgHyperlink:to_str()
26-
if self.desc then
27-
return string.format('[[%s][%s]]', self.url:to_string(), self.desc)
28-
else
29-
return string.format('[[%s]]', self.url:to_string())
30-
end
31-
end
32-
33-
---@param line string
34-
---@param pos number
35-
---@return OrgHyperlink | nil, { from: number, to: number } | nil
36-
function OrgHyperlink.at_pos(line, pos)
37-
local links = {}
38-
local found_link = nil
39-
local position
40-
for link in line:gmatch(pattern) do
41-
local start_from = #links > 0 and links[#links].to or nil
42-
local from, to = line:find(pattern, start_from)
43-
local current_pos = { from = from, to = to }
44-
if pos >= from and pos <= to then
45-
found_link = link
46-
position = current_pos
47-
break
48-
end
49-
table.insert(links, current_pos)
50-
end
51-
if not found_link then
52-
return nil, nil
53-
end
54-
return OrgHyperlink:new(found_link), position
25+
---@param node TSNode
26+
---@param source number | string
27+
---@return OrgHyperlink
28+
function OrgHyperlink.from_node(node, source)
29+
local url = node:field('url')[1]
30+
local desc = node:field('desc')[1]
31+
local this = setmetatable({}, { __index = OrgHyperlink })
32+
this.url = OrgLinkUrl:new(vim.treesitter.get_node_text(url, source))
33+
this.desc = desc and vim.treesitter.get_node_text(desc, source)
34+
this.range = Range.from_node(node)
35+
return this
5536
end
5637

57-
---@return OrgHyperlink | nil, { from: number, to: number } | nil
38+
---@return OrgHyperlink | nil
5839
function OrgHyperlink.at_cursor()
59-
local line = vim.fn.getline('.')
60-
local col = vim.fn.col('.') or 0
61-
return OrgHyperlink.at_pos(line, col)
62-
end
63-
64-
---@return OrgHyperlink[]
65-
function OrgHyperlink.all_from_line(line, line_number)
66-
local links = {}
67-
for link in line:gmatch(pattern) do
68-
local start_from = #links > 0 and links[#links].to or nil
69-
local from, to = line:find(pattern, start_from)
70-
if from and to then
71-
local range = Range.from_line(line_number)
72-
range.start_col = from
73-
range.end_col = to
74-
table.insert(links, OrgHyperlink:new(link, range))
75-
end
40+
local link_node = ts_utils.closest_node(ts_utils.get_node(), { 'link', 'link_desc' })
41+
if not link_node then
42+
return nil
7643
end
77-
78-
return links
44+
return OrgHyperlink.from_node(link_node, vim.api.nvim_get_current_buf())
7945
end
8046

8147
return OrgHyperlink

lua/orgmode/org/links/init.lua

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,11 @@ function OrgLinks:insert_link(link_location, desc)
141141
local target_col = #link_location + #link_description + 2
142142

143143
-- check if currently on link
144-
local link, position = OrgHyperlink.at_cursor()
145-
if link and position then
146-
insert_from = position.from - 1
147-
insert_to = position.to + 1
148-
target_col = target_col + position.from
144+
local link = OrgHyperlink.at_cursor()
145+
if link then
146+
insert_from = link.range.start_col - 1
147+
insert_to = link.range.end_col + 1
148+
target_col = target_col + link.range.start_col
149149
elseif vim.fn.mode() == 'v' then
150150
local start_pos = vim.fn.getpos('v')
151151
local end_pos = vim.fn.getpos('.')

lua/orgmode/utils/treesitter/init.lua

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,22 @@ function M.closest_headline_node(cursor)
6363
return M.find_headline(node)
6464
end
6565

66+
---@param node TSNode | nil
67+
---@param node_type string | string[]
6668
---@return TSNode | nil
67-
function M.closest_node(node, type)
69+
function M.closest_node(node, node_type)
6870
if not node then
6971
return nil
7072
end
71-
if node:type() == type then
72-
return node
73+
local types = type(node_type) == 'table' and node_type or { node_type }
74+
75+
for _, t in ipairs(types) do
76+
if node:type() == t then
77+
return node
78+
end
7379
end
7480

75-
return M.closest_node(node:parent(), type)
81+
return M.closest_node(node:parent(), types)
7682
end
7783

7884
---@param node? TSNode

lua/orgmode/utils/treesitter/install.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ end
145145

146146
---@param type? 'install' | 'update' | 'reinstall''
147147
function M.run(type)
148-
local url = 'https://github.com/nvim-orgmode/tree-sitter-org'
148+
-- local url = 'https://github.com/nvim-orgmode/tree-sitter-org'
149+
local url = '/home/kristijan/github/tree-sitter-org'
149150
local compiler = vim.tbl_filter(function(exe)
150151
return exe ~= vim.NIL and vim.fn.executable(exe) == 1
151152
end, M.compilers)[1]

queries/org/highlights.scm

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@
1111
(headline (stars) @stars (#org-is-headline-level? @stars "8")) @org.headline.level8
1212
(item . (expr) @org.keyword.todo @nospell (#org-is-todo-keyword? @org.keyword.todo "TODO"))
1313
(item . (expr) @org.keyword.done @nospell (#org-is-todo-keyword? @org.keyword.done "DONE"))
14-
(item (expr "[" "#" "str" @_priority "]") @org.priority.highest (#org-is-valid-priority? @_priority "highest"))
15-
(item (expr "[" "#" "str" @_priority "]") @org.priority.high (#org-is-valid-priority? @_priority "high"))
16-
(item (expr "[" "#" "str" @_priority "]") @org.priority.default (#org-is-valid-priority? @_priority "default"))
17-
(item (expr "[" "#" "str" @_priority "]") @org.priority.low (#org-is-valid-priority? @_priority "low"))
18-
(item (expr "[" "#" "str" @_priority "]") @org.priority.lowest (#org-is-valid-priority? @_priority "lowest"))
14+
(item priority: (priority value: (expr) @_priority) @org.priority.highest (#org-is-valid-priority? @_priority "highest"))
15+
(item priority: (priority value: (expr) @_priority) @org.priority.high (#org-is-valid-priority? @_priority "high"))
16+
(item priority: (priority value: (expr) @_priority) @org.priority.default (#org-is-valid-priority? @_priority "default"))
17+
(item priority: (priority value: (expr) @_priority) @org.priority.low (#org-is-valid-priority? @_priority "low"))
18+
(item priority: (priority value: (expr) @_priority) @org.priority.lowest (#org-is-valid-priority? @_priority "lowest"))
1919
(list (listitem (paragraph) @spell))
2020
(body (paragraph) @spell)
2121
(bullet) @org.bullet
@@ -42,3 +42,8 @@
4242
(table (row (cell (contents) @org.table.heading)))
4343
(table (hr) @org.table.delimiter)
4444
(fndef label: (expr) @org.footnote (#offset! @org.footnote 0 -4 0 1))
45+
(link) @org.hyperlink
46+
(link_desc) @org.hyperlink
47+
(link "[[" @_link_open "]]" @_link_close (#set! conceal ""))
48+
(link_desc "[[" @_link_open "][" @_link_separator "]]" @_link_close (#set! conceal ""))
49+
((link_desc url: (expr)+ @_link_url (#set! @_link_url conceal "")) @_link (#set! @_link url @_link_url))

queries/org/images.scm

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
((expr "[") @image (#org-set-link! @image))
1+
(link url: (expr) @image.src
2+
(#gsub! @image.src "^file:" "")
3+
(#match? @image.src "(png|jpg|jpeg|gif|bmp|webp|tiff|heic|avif|mp4|mov|avi|mkv|webm|pdf)$")
4+
)

syntax/orgagenda.vim

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,39 @@ syn match org_hyperlinkBracketsLeft contained "\[\{2}" conceal
55
syn match org_hyperlinkURL contained "[^][]*\]\[" conceal
66
syn match org_hyperlinkBracketsRight contained "\]\{2}" conceal
77

8+
syn match org_timestamp /\(<\d\d\d\d-\d\d-\d\d \k\k\k>\)/
9+
"<2003-09-16 Tue 12:00>
10+
syn match org_timestamp /\(<\d\d\d\d-\d\d-\d\d \k\k\k \d\d:\d\d>\)/
11+
"<2003-09-16 Tue 12:00 +1d>
12+
syn match org_timestamp /\(<\d\d\d\d-\d\d-\d\d \k\k\k \d\d:\d\d [+-]\d\+\w>\)/
13+
"<2003-09-16 Tue 12:00 +1d -1y>
14+
syn match org_timestamp /\(<\d\d\d\d-\d\d-\d\d \k\k\k \d\d:\d\d [+-]\d\+\w [+-]\d\+\w>\)/
15+
"<2003-09-16 Tue 12:00-12:30>
16+
syn match org_timestamp /\(<\d\d\d\d-\d\d-\d\d \k\k\k \d\d:\d\d-\d\d:\d\d>\)/
17+
18+
"<2003-09-16 Tue>--<2003-09-16 Tue>
19+
syn match org_timestamp /\(<\d\d\d\d-\d\d-\d\d \k\k\k>--<\d\d\d\d-\d\d-\d\d \k\k\k>\)/
20+
"<2003-09-16 Tue 12:00>--<2003-09-16 Tue 12:00>
21+
syn match org_timestamp /\(<\d\d\d\d-\d\d-\d\d \k\k\k \d\d:\d\d>--<\d\d\d\d-\d\d-\d\d \k\k\k \d\d:\d\d>\)/
22+
23+
"[2003-09-16 Tue]
24+
syn match org_timestamp_inactive /\(\[\d\d\d\d-\d\d-\d\d \k\k\k\]\)/
25+
"[2003-09-16 Tue 12:00]
26+
syn match org_timestamp_inactive /\(\[\d\d\d\d-\d\d-\d\d \k\k\k \d\d:\d\d\]\)/
27+
28+
"[2003-09-16 Tue 12:00 +1d]
29+
syn match org_timestamp_inactive /\(\[\d\d\d\d-\d\d-\d\d \k\k\k \d\d:\d\d [+-]\d\+\w\]\)/
30+
"[2003-09-16 Tue 12:00 +1d -1y]
31+
syn match org_timestamp_inactive /\(\[\d\d\d\d-\d\d-\d\d \k\k\k \d\d:\d\d [+-]\d\+\w [+-]\d\+\w\]\)/
32+
33+
"[2003-09-16 Tue]--[2003-09-16 Tue]
34+
syn match org_timestamp_inactive /\(\[\d\d\d\d-\d\d-\d\d \k\k\k\]--\[\d\d\d\d-\d\d-\d\d \k\k\k\]\)/
35+
"[2003-09-16 Tue 12:00]--[2003-09-16 Tue 12:00]
36+
syn match org_timestamp_inactive /\(\[\d\d\d\d-\d\d-\d\d \k\k\k \d\d:\d\d\]--\[\d\d\d\d-\d\d-\d\d \k\k\k \d\d:\d\d\]\)/
37+
38+
hi default link org_timestamp @org.timestamp.active
39+
hi default link org_timestamp_inactive @org.timestamp.inactive
40+
841
hi default link @org.agenda.day Statement
942
hi default link @org.agenda.today @org.bold
1043
hi default link @org.agenda.weekend @org.bold

0 commit comments

Comments
 (0)