Skip to content

Commit 63ace3c

Browse files
fix(files): remove string tree-sitter parser (#1041)
* fix(files): remove string tree-sitter parser With the change on nightly neovim/neovim#35988, string parser creates a scratch buffer under the hood, and uses that as a source. This drastically decreases the performance of the current code. This commit refactors the code to remove the string parser, and adds own loading of the org buffers for the tree-sitter usage. Performance should be same as before, with the downside that all of the org files that are in org_agenda_files are now loaded as unlisted buffers. * ci(tests): Run tests only on version 0.11.3+ For some reason, tests on older versions fail without reporting any actual issues in the tests. Skip running tests on those versions.
1 parent 03777ca commit 63ace3c

File tree

11 files changed

+139
-193
lines changed

11 files changed

+139
-193
lines changed

.github/workflows/tests.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,6 @@ jobs:
2929
- windows-latest
3030
- macos-latest
3131
version:
32-
- v0.11.0
33-
- v0.11.1
34-
- v0.11.2
3532
- v0.11.3
3633
- v0.11.4
3734
- nightly

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:get_source(), select(1, headline:node():range()))
172+
self:get_node_highlights(headline:node(), headline.file:bufnr(), select(1, headline:node():range()))
173173

174174
local result = {}
175175

lua/orgmode/files/file.lua

Lines changed: 70 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,13 @@ local Memoize = require('orgmode.utils.memoize')
1717

1818
---@class OrgFileOpts
1919
---@field filename string
20-
---@field lines string[]
21-
---@field bufnr? number
20+
---@field buf number
2221

2322
---@class OrgFile
2423
---@field filename string
24+
---@field buf number
2525
---@field index number
2626
---@field lines string[]
27-
---@field content string
2827
---@field metadata OrgFileMetadata
2928
---@field parser vim.treesitter.LanguageTree
3029
---@field root TSNode
@@ -44,15 +43,17 @@ function OrgFile:new(opts)
4443
local stat = vim.uv.fs_stat(opts.filename)
4544
local data = {
4645
filename = opts.filename,
47-
lines = opts.lines,
48-
content = table.concat(opts.lines, '\n'),
4946
index = 0,
47+
buf = opts.buf or -1,
5048
metadata = {
5149
mtime = stat and stat.mtime.nsec or 0,
5250
mtime_sec = stat and stat.mtime.sec or 0,
53-
changedtick = opts.bufnr and vim.api.nvim_buf_get_changedtick(opts.bufnr) or 0,
51+
changedtick = opts.buf and vim.api.nvim_buf_get_changedtick(opts.buf) or 0,
5452
},
5553
}
54+
if data.buf > 0 then
55+
data.lines = self:_get_lines(data.buf)
56+
end
5657
setmetatable(data, self)
5758
return data
5859
end
@@ -62,28 +63,29 @@ end
6263
function OrgFile.load(filename)
6364
local bufnr = utils.get_buffer_by_filename(filename)
6465

65-
if
66-
bufnr > -1
67-
and vim.api.nvim_buf_is_loaded(bufnr)
68-
and vim.api.nvim_get_option_value('filetype', { buf = bufnr }) == 'org'
69-
then
66+
if bufnr > -1 and vim.api.nvim_buf_is_loaded(bufnr) and vim.bo[bufnr].filetype == 'org' then
7067
return Promise.resolve(OrgFile:new({
7168
filename = filename,
72-
lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false),
73-
bufnr = bufnr,
69+
buf = bufnr,
7470
}))
7571
end
7672

7773
if not vim.uv.fs_stat(filename) or not utils.is_org_file(filename) then
7874
return Promise.resolve(false)
7975
end
8076

81-
return utils.readfile(filename, { schedule = true }):next(function(lines)
82-
return OrgFile:new({
83-
filename = filename,
84-
lines = lines,
85-
})
86-
end)
77+
bufnr = vim.fn.bufadd(filename)
78+
79+
if bufnr == 0 then
80+
return Promise.resolve(false)
81+
end
82+
83+
vim.fn.bufload(bufnr)
84+
85+
return Promise.resolve(OrgFile:new({
86+
filename = filename,
87+
buf = bufnr,
88+
}))
8789
end
8890

8991
---Reload the file if it has been modified
@@ -94,15 +96,34 @@ function OrgFile:reload()
9496
end
9597

9698
local bufnr = self:bufnr()
99+
local buf_changed = false
100+
local file_changed = false
97101

98-
if bufnr > -1 then
99-
local updated_file = self:_update_lines(vim.api.nvim_buf_get_lines(bufnr, 0, -1, false), bufnr)
100-
return Promise.resolve(updated_file)
102+
if bufnr then
103+
local new_changedtick = vim.api.nvim_buf_get_changedtick(bufnr)
104+
buf_changed = self.metadata.changedtick ~= new_changedtick
105+
self.metadata.changedtick = new_changedtick
106+
if buf_changed then
107+
self.lines = self:_get_lines(bufnr)
108+
end
101109
end
102-
103-
return utils.readfile(self.filename, { schedule = true }):next(function(lines)
104-
return self:_update_lines(lines)
105-
end)
110+
local stat = vim.uv.fs_stat(self.filename)
111+
if stat then
112+
local new_mtime_nsec = stat.mtime.nsec
113+
local new_mtime_sec = stat.mtime.sec
114+
file_changed = (new_mtime_nsec > 0 and self.metadata.mtime ~= new_mtime_nsec)
115+
or self.metadata.mtime_sec ~= new_mtime_sec
116+
self.metadata.mtime = new_mtime_nsec
117+
self.metadata.mtime_sec = new_mtime_sec
118+
end
119+
120+
if file_changed and not buf_changed then
121+
return utils.readfile(self.filename, { schedule = true }):next(function(lines)
122+
self.lines = lines
123+
return self
124+
end)
125+
end
126+
return Promise.resolve(self)
106127
end
107128

108129
---sync reload the file if it has been modified
@@ -146,7 +167,9 @@ function OrgFile:is_modified()
146167
local bufnr = self:bufnr()
147168
if bufnr > -1 then
148169
local cur_changedtick = vim.api.nvim_buf_get_changedtick(bufnr)
149-
return cur_changedtick ~= self.metadata.changedtick
170+
if cur_changedtick ~= self.metadata.changedtick then
171+
return true
172+
end
150173
end
151174
local stat = vim.uv.fs_stat(self.filename)
152175
if not stat then
@@ -166,7 +189,7 @@ function OrgFile:parse(skip_if_not_modified)
166189
if skip_if_not_modified and self.root and not self:is_modified() then
167190
return self.root
168191
end
169-
self.parser = self:_get_parser()
192+
self.parser = ts.get_parser(self:bufnr(), 'org', {})
170193
local trees = self.parser:parse()
171194
self.root = trees[1]:root()
172195
return self.root
@@ -185,7 +208,7 @@ function OrgFile:get_ts_matches(query, parent_node)
185208
local ts_query = ts_utils.get_query(query)
186209
local matches = {}
187210

188-
for _, match, _ in ts_query:iter_matches(parent_node, self:get_source(), nil, nil, { all = true }) do
211+
for _, match, _ in ts_query:iter_matches(parent_node, self:bufnr(), nil, nil, { all = true }) do
189212
local items = {}
190213
for id, nodes in pairs(match) do
191214
local name = ts_query.captures[id]
@@ -215,7 +238,7 @@ function OrgFile:get_ts_captures(query, node)
215238
local ts_query = ts_utils.get_query(query)
216239
local matches = {}
217240

218-
for _, match in ts_query:iter_captures(node, self:get_source()) do
241+
for _, match in ts_query:iter_captures(node, self:bufnr()) do
219242
table.insert(matches, match)
220243
end
221244
return matches
@@ -466,13 +489,13 @@ function OrgFile:get_node_text(node, range)
466489
return ''
467490
end
468491
if range then
469-
return ts.get_node_text(node, self:get_source(), {
492+
return ts.get_node_text(node, self:bufnr(), {
470493
metadata = {
471494
range = range,
472495
},
473496
})
474497
end
475-
return ts.get_node_text(node, self:get_source())
498+
return ts.get_node_text(node, self:bufnr())
476499
end
477500

478501
---@param node? TSNode
@@ -534,19 +557,22 @@ end
534557

535558
---@return number
536559
function OrgFile:bufnr()
537-
local bufnr = utils.get_buffer_by_filename(self.filename)
560+
local bufnr = self.buf
538561
-- Do not consider unloaded buffers as valid
539562
-- Treesitter is not working in them
540563
if bufnr > -1 and vim.api.nvim_buf_is_loaded(bufnr) then
541564
return bufnr
542565
end
543-
return -1
566+
local new_bufnr = vim.fn.bufadd(self.filename)
567+
vim.fn.bufload(new_bufnr)
568+
self.buf = new_bufnr
569+
return new_bufnr
544570
end
545571

546572
---Return valid buffer handle or throw an error if it's not valid
547573
---@return number
548574
function OrgFile:get_valid_bufnr()
549-
local bufnr = utils.get_buffer_by_filename(self.filename)
575+
local bufnr = self:bufnr()
550576
if bufnr < 0 then
551577
error('[orgmode] No valid buffer for file ' .. self.filename .. ' to edit', 0)
552578
end
@@ -784,7 +810,7 @@ function OrgFile:get_links()
784810
(link_desc) @link
785811
]])
786812

787-
local source = self:get_source()
813+
local source = self:bufnr()
788814
for _, node in ipairs(matches) do
789815
table.insert(links, Hyperlink.from_node(node, source))
790816
end
@@ -805,7 +831,7 @@ function OrgFile:get_footnote_references()
805831

806832
local footnotes = {}
807833
local processed_lines = {}
808-
for _, match in ts_query:iter_captures(self.root, self:get_source()) do
834+
for _, match in ts_query:iter_captures(self.root, self:bufnr()) do
809835
local line_start, _, line_end = match:range()
810836
if not processed_lines[line_start] then
811837
if line_start == line_end then
@@ -895,51 +921,14 @@ function OrgFile:_get_directive(directive_name)
895921
end
896922

897923
---@private
898-
---@param lines string[]
899-
---@param bufnr? number
900-
function OrgFile:_update_lines(lines, bufnr)
901-
self.lines = lines
902-
self.content = table.concat(lines, '\n')
903-
self:parse()
904-
if bufnr then
905-
self.metadata.changedtick = vim.api.nvim_buf_get_changedtick(bufnr)
906-
end
907-
local stat = vim.uv.fs_stat(self.filename)
908-
if stat then
909-
self.metadata.mtime = stat.mtime.nsec
910-
self.metadata.mtime_sec = stat.mtime.sec
911-
end
912-
return self
913-
end
914-
915-
---@private
916-
---@return vim.treesitter.LanguageTree
917-
function OrgFile:_get_parser()
918-
local bufnr = self:bufnr()
919-
920-
if bufnr > -1 then
921-
-- Always get the fresh parser for the buffer
922-
return ts.get_parser(bufnr, 'org', {})
923-
end
924-
925-
-- In case the buffer got unloaded, go back to string parser
926-
if not self.parser or self:is_modified() or type(self.parser:source()) == 'number' then
927-
return ts.get_string_parser(self.content, 'org', {})
928-
end
929-
930-
return self.parser
931-
end
932-
933-
--- Get the ts source for the file
934-
--- If there is a buffer, return buffer number
935-
--- Otherwise, return the string content
936-
---@return integer | string
937-
function OrgFile:get_source()
938-
local bufnr = self:bufnr()
939-
if bufnr > -1 then
940-
return bufnr
924+
---Get all buffer lines, ensure empty buffer returns empty table
925+
---@return string[]
926+
function OrgFile:_get_lines(bufnr)
927+
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
928+
if #lines == 1 and lines[1] == '' then
929+
lines = {}
941930
end
942-
return self.content
931+
return lines
943932
end
944933

945934
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:get_source(), {
741+
dates[name:upper()] = Date.from_node(timestamp, self.file:bufnr(), {
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:get_source()
795+
local source = self.file:bufnr()
796796
for _, match in ipairs(matches) do
797797
local dates = Date.from_node(match, source)
798798
vim.list_extend(all_dates, dates)

lua/orgmode/init.lua

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,15 @@ end
9191

9292
function Org:setup_autocmds()
9393
local org_augroup = vim.api.nvim_create_augroup('orgmode_nvim', { clear = true })
94+
vim.api.nvim_create_autocmd('BufWinEnter', {
95+
pattern = { '*.org', '*.org_archive' },
96+
group = org_augroup,
97+
callback = function(event)
98+
if not vim.bo[event.buf].filetype or vim.bo[event.buf].filetype == '' then
99+
vim.bo[event.buf].filetype = 'org'
100+
end
101+
end,
102+
})
94103
vim.api.nvim_create_autocmd('BufWritePost', {
95104
pattern = { '*.org', '*.org_archive' },
96105
group = org_augroup,

tests/plenary/agenda/agenda_item_spec.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ local helpers = require('tests.plenary.helpers')
88
local function generate(content_line, keyword)
99
keyword = keyword or 'TODO'
1010

11-
local file = helpers.create_file_instance({
11+
local file = helpers.create_file({
1212
'* ' .. keyword .. ' This is some content',
1313
content_line,
1414
})

tests/plenary/capture/capture_spec.lua

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ describe('Refile', function()
198198
})
199199

200200
local capture_lines = { '** baz' }
201-
local capture_file = helpers.create_file_instance(capture_lines)
201+
local capture_file = helpers.create_file(capture_lines)
202202
local item = capture_file:get_headlines()[1]
203203

204204
---@diagnostic disable-next-line: invisible
@@ -226,7 +226,7 @@ describe('Capture', function()
226226
local destination_file = helpers.create_file({})
227227

228228
local capture_lines = { '* foo' }
229-
local capture_file = helpers.create_file_instance(capture_lines)
229+
local capture_file = helpers.create_file(capture_lines)
230230
local item = capture_file:get_headlines()[1]
231231
local template = Template:new({
232232
properties = {
@@ -310,7 +310,7 @@ describe('Capture', function()
310310
})
311311

312312
local capture_lines = { '** baz' }
313-
local capture_file = helpers.create_file_instance(capture_lines)
313+
local capture_file = helpers.create_file(capture_lines)
314314
local item = capture_file:get_headlines()[1]
315315
local template = Template:new({
316316
properties = {
@@ -355,7 +355,7 @@ describe('Capture', function()
355355
})
356356

357357
local capture_lines = { '** baz' }
358-
local capture_file = helpers.create_file_instance(capture_lines)
358+
local capture_file = helpers.create_file(capture_lines)
359359
local item = capture_file:get_headlines()[1]
360360
local template = Template:new({
361361
regexp = 'appendhere',

0 commit comments

Comments
 (0)