Skip to content

Commit bec3efa

Browse files
feat(files): Use TS string parser for non-nightly versions
1 parent dc01c6f commit bec3efa

File tree

4 files changed

+148
-30
lines changed

4 files changed

+148
-30
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ end
169169
---@return OrgMarkupPreparedHighlight[]
170170
function OrgMarkup:get_prepared_headline_highlights(headline)
171171
local highlights =
172-
self:get_node_highlights(headline:node(), headline.file:bufnr(), select(1, headline:node():range()))
172+
self:get_node_highlights(headline:node(), headline.file:get_source(), select(1, headline:node():range()))
173173

174174
local result = {}
175175

lua/orgmode/files/file.lua

Lines changed: 89 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ local Hyperlink = require('orgmode.org.links.hyperlink')
99
local Range = require('orgmode.files.elements.range')
1010
local Footnote = require('orgmode.objects.footnote')
1111
local Memoize = require('orgmode.utils.memoize')
12+
local is_nightly = vim.fn.has('nvim-0.12') > 0
1213

1314
---@class OrgFileMetadata
1415
---@field mtime number File modified time in nanoseconds
@@ -17,13 +18,15 @@ local Memoize = require('orgmode.utils.memoize')
1718

1819
---@class OrgFileOpts
1920
---@field filename string
21+
---@field lines? string[]
2022
---@field buf? number
2123

2224
---@class OrgFile
2325
---@field filename string
2426
---@field buf number
2527
---@field index number
2628
---@field lines string[]
29+
---@field content string
2730
---@field metadata OrgFileMetadata
2831
---@field parser vim.treesitter.LanguageTree
2932
---@field root TSNode
@@ -45,18 +48,19 @@ function OrgFile:new(opts)
4548
filename = opts.filename,
4649
index = 0,
4750
buf = opts.buf or -1,
48-
lines = {},
51+
lines = opts.lines or {},
52+
content = table.concat(opts.lines or {}, '\n'),
4953
metadata = {
5054
mtime = stat and stat.mtime.nsec or 0,
5155
mtime_sec = stat and stat.mtime.sec or 0,
5256
changedtick = opts.buf and vim.api.nvim_buf_get_changedtick(opts.buf) or 0,
5357
},
5458
}
55-
if data.buf > 0 then
56-
data.lines = self:_get_lines(data.buf)
59+
local this = setmetatable(data, self)
60+
if this.buf > 0 then
61+
this:_update_lines(this:_get_lines(this.buf))
5762
end
58-
setmetatable(data, self)
59-
return data
63+
return this
6064
end
6165

6266
---Load the file
@@ -75,12 +79,23 @@ function OrgFile.load(filename)
7579
return Promise.resolve(false)
7680
end
7781

78-
bufnr = OrgFile._load_buffer(filename)
82+
-- TODO: Remove once Neovim adds string parser back
83+
-- See: https://github.com/nvim-orgmode/orgmode/issues/1049
84+
if is_nightly then
85+
bufnr = OrgFile._load_buffer(filename)
7986

80-
return Promise.resolve(OrgFile:new({
81-
filename = filename,
82-
buf = bufnr,
83-
}))
87+
return Promise.resolve(OrgFile:new({
88+
filename = filename,
89+
buf = bufnr,
90+
}))
91+
end
92+
93+
return utils.readfile(filename, { schedule = true }):next(function(lines)
94+
return OrgFile:new({
95+
filename = filename,
96+
lines = lines,
97+
})
98+
end)
8499
end
85100

86101
---Reload the file if it has been modified
@@ -90,16 +105,17 @@ function OrgFile:reload()
90105
return Promise.resolve(self)
91106
end
92107

108+
93109
local bufnr = self:bufnr()
94110
local buf_changed = false
95111
local file_changed = false
96112

97-
if bufnr then
113+
if bufnr > -1 then
98114
local new_changedtick = vim.api.nvim_buf_get_changedtick(bufnr)
99115
buf_changed = self.metadata.changedtick ~= new_changedtick
100116
self.metadata.changedtick = new_changedtick
101117
if buf_changed then
102-
self.lines = self:_get_lines(bufnr)
118+
self:_update_lines(self:_get_lines(bufnr))
103119
end
104120
end
105121
local stat = vim.uv.fs_stat(self.filename)
@@ -114,7 +130,7 @@ function OrgFile:reload()
114130

115131
if file_changed and not buf_changed then
116132
return utils.readfile(self.filename, { schedule = true }):next(function(lines)
117-
self.lines = lines
133+
self:_update_lines(lines)
118134
return self
119135
end)
120136
end
@@ -184,7 +200,7 @@ function OrgFile:parse(skip_if_not_modified)
184200
if skip_if_not_modified and self.root and not self:is_modified() then
185201
return self.root
186202
end
187-
self.parser = ts.get_parser(self:bufnr(), 'org', {})
203+
self.parser = self:_get_parser()
188204
local trees = self.parser:parse()
189205
self.root = trees[1]:root()
190206
return self.root
@@ -203,7 +219,7 @@ function OrgFile:get_ts_matches(query, parent_node)
203219
local ts_query = ts_utils.get_query(query)
204220
local matches = {}
205221

206-
for _, match, _ in ts_query:iter_matches(parent_node, self:bufnr(), nil, nil, { all = true }) do
222+
for _, match, _ in ts_query:iter_matches(parent_node, self:get_source(), nil, nil, { all = true }) do
207223
local items = {}
208224
for id, nodes in pairs(match) do
209225
local name = ts_query.captures[id]
@@ -233,7 +249,7 @@ function OrgFile:get_ts_captures(query, node)
233249
local ts_query = ts_utils.get_query(query)
234250
local matches = {}
235251

236-
for _, match in ts_query:iter_captures(node, self:bufnr()) do
252+
for _, match in ts_query:iter_captures(node, self:get_source()) do
237253
table.insert(matches, match)
238254
end
239255
return matches
@@ -489,13 +505,13 @@ function OrgFile:get_node_text(node, range)
489505
return ''
490506
end
491507
if range then
492-
return ts.get_node_text(node, self:bufnr(), {
508+
return ts.get_node_text(node, self:get_source(), {
493509
metadata = {
494510
range = range,
495511
},
496512
})
497513
end
498-
return ts.get_node_text(node, self:bufnr())
514+
return ts.get_node_text(node, self:get_source())
499515
end
500516

501517
---@param node? TSNode
@@ -557,15 +573,27 @@ end
557573

558574
---@return number
559575
function OrgFile:bufnr()
560-
local bufnr = self.buf
576+
-- TODO: Remove once Neovim adds string parser back
577+
-- See: https://github.com/nvim-orgmode/orgmode/issues/1049
578+
if is_nightly then
579+
local bufnr = self.buf
580+
-- Do not consider unloaded buffers as valid
581+
-- Treesitter is not working in them
582+
if bufnr > -1 and vim.api.nvim_buf_is_loaded(bufnr) then
583+
return bufnr
584+
end
585+
local new_bufnr = self._load_buffer(self.filename)
586+
self.buf = new_bufnr
587+
return new_bufnr
588+
end
589+
590+
local bufnr = utils.get_buffer_by_filename(self.filename)
561591
-- Do not consider unloaded buffers as valid
562592
-- Treesitter is not working in them
563593
if bufnr > -1 and vim.api.nvim_buf_is_loaded(bufnr) then
564594
return bufnr
565595
end
566-
local new_bufnr = self._load_buffer(self.filename)
567-
self.buf = new_bufnr
568-
return new_bufnr
596+
return -1
569597
end
570598

571599
---@private
@@ -819,7 +847,7 @@ function OrgFile:get_links()
819847
(link_desc) @link
820848
]])
821849

822-
local source = self:bufnr()
850+
local source = self:get_source()
823851
for _, node in ipairs(matches) do
824852
table.insert(links, Hyperlink.from_node(node, source))
825853
end
@@ -840,7 +868,7 @@ function OrgFile:get_footnote_references()
840868

841869
local footnotes = {}
842870
local processed_lines = {}
843-
for _, match in ts_query:iter_captures(self.root, self:bufnr()) do
871+
for _, match in ts_query:iter_captures(self.root, self:get_source()) do
844872
local line_start, _, line_end = match:range()
845873
if not processed_lines[line_start] then
846874
if line_start == line_end then
@@ -947,6 +975,13 @@ function OrgFile:_get_directive(directive_name, all_matches)
947975
return nil
948976
end
949977

978+
function OrgFile:_update_lines(lines)
979+
self.lines = lines
980+
self.content = table.concat(lines, '\n')
981+
self:parse()
982+
return self
983+
end
984+
950985
---@private
951986
---Get all buffer lines, ensure empty buffer returns empty table
952987
---@return string[]
@@ -958,4 +993,34 @@ function OrgFile:_get_lines(bufnr)
958993
return lines
959994
end
960995

996+
---@private
997+
---@return vim.treesitter.LanguageTree
998+
function OrgFile:_get_parser()
999+
local bufnr = self:bufnr()
1000+
1001+
if bufnr > -1 then
1002+
-- Always get the fresh parser for the buffer
1003+
return ts.get_parser(bufnr, 'org', {})
1004+
end
1005+
1006+
-- In case the buffer got unloaded, go back to string parser
1007+
if not self.parser or self:is_modified() or type(self.parser:source()) == 'number' then
1008+
return ts.get_string_parser(self.content, 'org', {})
1009+
end
1010+
1011+
return self.parser
1012+
end
1013+
1014+
--- Get the ts source for the file
1015+
--- If there is a buffer, return buffer number
1016+
--- Otherwise, return the string content
1017+
---@return integer | string
1018+
function OrgFile:get_source()
1019+
local bufnr = self:bufnr()
1020+
if bufnr > -1 then
1021+
return bufnr
1022+
end
1023+
return self.content
1024+
end
1025+
9611026
return OrgFile

lua/orgmode/files/headline.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,7 @@ function Headline:get_plan_dates()
738738
if name ~= 'NONE' then
739739
has_plan_dates = true
740740
end
741-
dates[name:upper()] = Date.from_node(timestamp, self.file:bufnr(), {
741+
dates[name:upper()] = Date.from_node(timestamp, self.file:get_source(), {
742742
type = name:upper(),
743743
})
744744
dates_nodes[name:upper()] = node
@@ -792,7 +792,7 @@ function Headline:get_non_plan_dates()
792792
end
793793

794794
local all_dates = {}
795-
local source = self.file:bufnr()
795+
local source = self.file:get_source()
796796
for _, match in ipairs(matches) do
797797
local dates = Date.from_node(match, source)
798798
vim.list_extend(all_dates, dates)

tests/plenary/files/file_spec.lua

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
local OrgFile = require('orgmode.files.file')
22
local config = require('orgmode.config')
33
local Range = require('orgmode.files.elements.range')
4+
-- TODO: Remove once Neovim adds string parser back
5+
-- See: https://github.com/nvim-orgmode/orgmode/issues/1049
6+
local is_nightly = vim.fn.has('nvim-0.12') > 0
47

58
describe('OrgFile', function()
69
---@return OrgFile
@@ -20,7 +23,11 @@ describe('OrgFile', function()
2023
local stat = vim.uv.fs_stat(filename) or {}
2124
assert.are.same(stat.mtime.nsec, file.metadata.mtime)
2225
assert.are.same(stat.mtime.sec, file.metadata.mtime_sec)
23-
assert.are.same(2, file.metadata.changedtick)
26+
if is_nightly then
27+
assert.are.same(2, file.metadata.changedtick)
28+
else
29+
assert.are.same(0, file.metadata.changedtick)
30+
end
2431
end)
2532

2633
it('should not load a file that is not an org file', function()
@@ -39,10 +46,18 @@ describe('OrgFile', function()
3946
local stat = vim.uv.fs_stat(filename) or {}
4047
assert.are.same(stat.mtime.nsec, file.metadata.mtime)
4148
assert.are.same(stat.mtime.sec, file.metadata.mtime_sec)
42-
assert.are.same(2, file.metadata.changedtick)
49+
if is_nightly then
50+
assert.are.same(2, file.metadata.changedtick)
51+
else
52+
assert.are.same(0, file.metadata.changedtick)
53+
end
4354
vim.cmd('write!')
4455
file:reload_sync()
45-
assert.are.same(2, file.metadata.changedtick)
56+
if is_nightly then
57+
assert.are.same(2, file.metadata.changedtick)
58+
else
59+
assert.are.same(4, file.metadata.changedtick)
60+
end
4661
end)
4762

4863
it('should load files with special characters in filename from buffer', function()
@@ -477,6 +492,20 @@ describe('OrgFile', function()
477492
end)
478493

479494
describe('set_node_text', function()
495+
if not is_nightly then
496+
it('should throw an error if file is not loaded in buffer', function()
497+
local file = load_file_sync({
498+
'* Headline 1 :TAG:',
499+
' The content',
500+
' Multi line',
501+
})
502+
local paragraph_node = file:get_node_at_cursor():parent()
503+
assert.is.error_matches(function()
504+
return file:set_node_text(paragraph_node, 'New Text')
505+
end, '%[orgmode%] No valid buffer for file ' .. file.filename .. ' to edit')
506+
end)
507+
end
508+
480509
it('should set node text', function()
481510
local file = load_file_sync({
482511
'* Headline 1 :TAG:',
@@ -530,6 +559,30 @@ describe('OrgFile', function()
530559
end)
531560

532561
describe('bufnr', function()
562+
if not is_nightly then
563+
it('should return -1 if there is no buffer', function()
564+
local file = load_file_sync({
565+
'* Headline 1 :TAG:',
566+
' The content',
567+
' Multi line',
568+
})
569+
assert.are.same(-1, file:bufnr())
570+
end)
571+
572+
it('should return -1 if file is loaded in buffer but buffer is not loaded', function()
573+
local file = load_file_sync({
574+
'* Headline 1 :TAG:',
575+
' The content',
576+
' Multi line',
577+
})
578+
vim.cmd('edit ' .. file.filename)
579+
assert.is.True(file:bufnr() > 0)
580+
vim.cmd('bdelete')
581+
assert.are.same(-1, file:bufnr())
582+
assert.is.True(vim.fn.bufnr(file.filename) > 0)
583+
end)
584+
end
585+
533586
it('should return buffer number if file is loaded', function()
534587
local file = load_file_sync({
535588
'* Headline 1 :TAG:',

0 commit comments

Comments
 (0)