Skip to content

Commit 9900cf2

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

File tree

4 files changed

+147
-30
lines changed

4 files changed

+147
-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: 88 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
@@ -94,12 +109,12 @@ function OrgFile:reload()
94109
local buf_changed = false
95110
local file_changed = false
96111

97-
if bufnr then
112+
if bufnr > -1 then
98113
local new_changedtick = vim.api.nvim_buf_get_changedtick(bufnr)
99114
buf_changed = self.metadata.changedtick ~= new_changedtick
100115
self.metadata.changedtick = new_changedtick
101116
if buf_changed then
102-
self.lines = self:_get_lines(bufnr)
117+
self:_update_lines(self:_get_lines(bufnr))
103118
end
104119
end
105120
local stat = vim.uv.fs_stat(self.filename)
@@ -114,7 +129,7 @@ function OrgFile:reload()
114129

115130
if file_changed and not buf_changed then
116131
return utils.readfile(self.filename, { schedule = true }):next(function(lines)
117-
self.lines = lines
132+
self:_update_lines(lines)
118133
return self
119134
end)
120135
end
@@ -184,7 +199,7 @@ function OrgFile:parse(skip_if_not_modified)
184199
if skip_if_not_modified and self.root and not self:is_modified() then
185200
return self.root
186201
end
187-
self.parser = ts.get_parser(self:bufnr(), 'org', {})
202+
self.parser = self:_get_parser()
188203
local trees = self.parser:parse()
189204
self.root = trees[1]:root()
190205
return self.root
@@ -203,7 +218,7 @@ function OrgFile:get_ts_matches(query, parent_node)
203218
local ts_query = ts_utils.get_query(query)
204219
local matches = {}
205220

206-
for _, match, _ in ts_query:iter_matches(parent_node, self:bufnr(), nil, nil, { all = true }) do
221+
for _, match, _ in ts_query:iter_matches(parent_node, self:get_source(), nil, nil, { all = true }) do
207222
local items = {}
208223
for id, nodes in pairs(match) do
209224
local name = ts_query.captures[id]
@@ -233,7 +248,7 @@ function OrgFile:get_ts_captures(query, node)
233248
local ts_query = ts_utils.get_query(query)
234249
local matches = {}
235250

236-
for _, match in ts_query:iter_captures(node, self:bufnr()) do
251+
for _, match in ts_query:iter_captures(node, self:get_source()) do
237252
table.insert(matches, match)
238253
end
239254
return matches
@@ -489,13 +504,13 @@ function OrgFile:get_node_text(node, range)
489504
return ''
490505
end
491506
if range then
492-
return ts.get_node_text(node, self:bufnr(), {
507+
return ts.get_node_text(node, self:get_source(), {
493508
metadata = {
494509
range = range,
495510
},
496511
})
497512
end
498-
return ts.get_node_text(node, self:bufnr())
513+
return ts.get_node_text(node, self:get_source())
499514
end
500515

501516
---@param node? TSNode
@@ -557,15 +572,27 @@ end
557572

558573
---@return number
559574
function OrgFile:bufnr()
560-
local bufnr = self.buf
575+
-- TODO: Remove once Neovim adds string parser back
576+
-- See: https://github.com/nvim-orgmode/orgmode/issues/1049
577+
if is_nightly then
578+
local bufnr = self.buf
579+
-- Do not consider unloaded buffers as valid
580+
-- Treesitter is not working in them
581+
if bufnr > -1 and vim.api.nvim_buf_is_loaded(bufnr) then
582+
return bufnr
583+
end
584+
local new_bufnr = self._load_buffer(self.filename)
585+
self.buf = new_bufnr
586+
return new_bufnr
587+
end
588+
589+
local bufnr = utils.get_buffer_by_filename(self.filename)
561590
-- Do not consider unloaded buffers as valid
562591
-- Treesitter is not working in them
563592
if bufnr > -1 and vim.api.nvim_buf_is_loaded(bufnr) then
564593
return bufnr
565594
end
566-
local new_bufnr = self._load_buffer(self.filename)
567-
self.buf = new_bufnr
568-
return new_bufnr
595+
return -1
569596
end
570597

571598
---@private
@@ -819,7 +846,7 @@ function OrgFile:get_links()
819846
(link_desc) @link
820847
]])
821848

822-
local source = self:bufnr()
849+
local source = self:get_source()
823850
for _, node in ipairs(matches) do
824851
table.insert(links, Hyperlink.from_node(node, source))
825852
end
@@ -840,7 +867,7 @@ function OrgFile:get_footnote_references()
840867

841868
local footnotes = {}
842869
local processed_lines = {}
843-
for _, match in ts_query:iter_captures(self.root, self:bufnr()) do
870+
for _, match in ts_query:iter_captures(self.root, self:get_source()) do
844871
local line_start, _, line_end = match:range()
845872
if not processed_lines[line_start] then
846873
if line_start == line_end then
@@ -947,6 +974,13 @@ function OrgFile:_get_directive(directive_name, all_matches)
947974
return nil
948975
end
949976

977+
function OrgFile:_update_lines(lines)
978+
self.lines = lines
979+
self.content = table.concat(lines, '\n')
980+
self:parse()
981+
return self
982+
end
983+
950984
---@private
951985
---Get all buffer lines, ensure empty buffer returns empty table
952986
---@return string[]
@@ -958,4 +992,34 @@ function OrgFile:_get_lines(bufnr)
958992
return lines
959993
end
960994

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