Skip to content

Commit 2f4a4fc

Browse files
committed
perf(inline_var): parse and query each buf once
1 parent 8299aaa commit 2f4a4fc

File tree

3 files changed

+109
-75
lines changed

3 files changed

+109
-75
lines changed

lua/refactoring/debug/print_var.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ function M.print_var(range_type, config)
5858
return
5959
end
6060
-- TODO: use async parsing
61+
-- TODO: check if using a range parses only when necessary (by peeking into
62+
-- the implementation, it does use `LanguageTree:valid`, but it always
63+
-- returns false when `range` is `true`)
6164
lang_tree:parse(true)
6265
local nested_lang_tree = lang_tree:language_for_range { extracted_range:to_treesitter() }
6366
local lang = nested_lang_tree:lang()

lua/refactoring/refactor/inline_func.lua

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,39 +31,31 @@ local function get_processed_match_info(definitions, references, lang)
3131
return
3232
end
3333

34+
---@type {[integer]: refactor.inline_func.MatchInfo}
3435
local ts_info = iter({ definitions, references })
3536
:flatten(1)
3637
:map(
3738
---@param item refactor.QfItem
3839
function(item)
3940
local buf = vim.fn.bufadd(item.filename)
4041
if not api.nvim_buf_is_loaded(buf) then vim.fn.bufload(buf) end
41-
return buf, item
42+
return buf
4243
end
4344
)
4445
:filter(is_unique())
4546
:map(
4647
---@param buf integer
47-
---@param item refactor.QfItem
48-
function(buf, item)
49-
local item_lang_tree, err2 = ts.get_parser(buf, lang, { error = false })
50-
if not item_lang_tree then
48+
function(buf)
49+
local lang_tree, err2 = ts.get_parser(buf, lang, { error = false })
50+
if not lang_tree then
5151
vim.notify(err2, vim.log.levels.ERROR)
5252
return
5353
end
5454

55-
item_lang_tree:parse(true)
56-
local item_nested_lang_tree = item_lang_tree:language_for_range {
57-
item.lnum - 1,
58-
item.col - 1,
59-
item.end_lnum - 1,
60-
item.end_col - 1,
61-
}
62-
6355
local functions_info = {} ---@type refactor.FunctionInfo[]
6456
local returns_info = {} ---@type refactor.ReturnInfo[]
6557
local function_calls_info = {} ---@type refactor.FunctionCallInfo[]
66-
for _, tree in ipairs(item_nested_lang_tree:trees()) do
58+
for _, tree in ipairs(lang_tree:trees()) do
6759
for _, match in query:iter_matches(tree:root(), buf) do
6860
local function_info ---@type nil|refactor.FunctionInfo
6961
local return_info ---@type nil|refactor.ReturnInfo

lua/refactoring/refactor/inline_var.lua

Lines changed: 100 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -7,58 +7,17 @@ local range = require "refactoring.range"
77

88
local M = {}
99

10-
-- TODO: using `inline_var` in something like `local f = vim.bo.filetype` will
11-
-- block the editor (because it tried to parse the buffer with each reference
12-
-- to `vim.bo.filetype`? Look into it)
13-
1410
---@param definition refactor.QfItem
15-
---@param query vim.treesitter.Query
16-
---@return nil|refactor.VariableInfo
17-
local function get_definition_info(definition, query)
11+
---@param variables_info refactor.VariableInfo[]
12+
---@return nil|refactor.ProcessedVariableInfo
13+
local function get_definition_info(definition, variables_info)
1814
local definition_buf = vim.fn.bufadd(definition.filename)
19-
if not api.nvim_buf_is_loaded(definition_buf) then vim.fn.bufload(definition_buf) end
20-
local definition_lang_tree, err2 = ts.get_parser(definition_buf, nil, { error = false })
21-
if not definition_lang_tree then
22-
vim.notify(err2, vim.log.levels.ERROR)
23-
return
24-
end
25-
-- TODO: use async parsing
26-
definition_lang_tree:parse(true)
27-
local definition_nested_lang_tree = definition_lang_tree:language_for_range {
28-
definition.lnum - 1,
29-
definition.col - 1,
30-
definition.end_lnum - 1,
31-
definition.end_col - 1,
32-
}
33-
34-
local definition_matches_info = {} ---@type refactor.VariableMatchInfo[]
35-
for _, tree in ipairs(definition_nested_lang_tree:trees()) do
36-
for _, match in query:iter_matches(tree:root(), definition_buf) do
37-
local match_info = {} ---@type refactor.VariableMatchInfo|{}
38-
for capture_id, nodes in pairs(match) do
39-
local name = query.captures[capture_id]
40-
41-
if name == "variable.identifier" then
42-
match_info.identifier = nodes
43-
elseif name == "variable.identifier_separator" then
44-
match_info.identifier_separator = nodes
45-
elseif name == "variable.value_separator" then
46-
match_info.value_separator = nodes
47-
elseif name == "variable.value" then
48-
match_info.value = nodes
49-
elseif name == "variable.declaration" then
50-
match_info.declaration = nodes
51-
end
52-
end
53-
if not vim.tbl_isempty(match_info) then table.insert(definition_matches_info, match_info) end
54-
end
55-
end
5615

5716
local definition_start = pos.vimscript(definition_buf, definition.lnum, definition.col)
58-
---@type refactor.VariableInfo
59-
local variable_info = iter(definition_matches_info)
17+
---@type refactor.ProcessedVariableInfo
18+
local variable_info = iter(variables_info)
6019
:map(
61-
---@param match_info refactor.VariableMatchInfo
20+
---@param match_info refactor.VariableInfo
6221
function(match_info)
6322
local variable_info = iter(ipairs(match_info.identifier))
6423
:filter(
@@ -72,7 +31,7 @@ local function get_definition_info(definition, query)
7231
:map(
7332
---@param i integer
7433
---@param identifier TSNode
75-
---@return refactor.VariableInfo
34+
---@return refactor.ProcessedVariableInfo
7635
function(i, identifier)
7736
return {
7837
identifier = identifier,
@@ -91,7 +50,7 @@ local function get_definition_info(definition, query)
9150
end
9251
)
9352
:filter(
94-
---@param variable_info refactor.VariableInfo
53+
---@param variable_info refactor.ProcessedVariableInfo
9554
function(variable_info)
9655
return variable_info ~= nil
9756
end
@@ -101,14 +60,96 @@ local function get_definition_info(definition, query)
10160
return variable_info
10261
end
10362

104-
---@class refactor.VariableMatchInfo
63+
---@class refactor.inline_var.MatchInfo
64+
---@field variables refactor.VariableInfo[]
65+
66+
--As a side effect, loads all the buffers for all of the definitions and references
67+
---@param definitions refactor.QfItem[]
68+
---@param references refactor.QfItem[]
69+
---@param lang string
70+
---@return nil|{[integer]: refactor.inline_var.MatchInfo}
71+
local function get_match_info(definitions, references, lang)
72+
local is_unique = require("refactoring.utils").is_unique
73+
74+
local query = ts.query.get(lang, "refactor")
75+
if not query then
76+
vim.notify(("There is no `refactor` query file for language %s"):format(lang), vim.log.levels.ERROR)
77+
return
78+
end
79+
80+
---@type {[integer]: refactor.inline_var.MatchInfo}
81+
local ts_info = iter({ definitions, references })
82+
:flatten(1)
83+
:map(
84+
---@param item refactor.QfItem
85+
function(item)
86+
local buf = vim.fn.bufadd(item.filename)
87+
if not api.nvim_buf_is_loaded(buf) then vim.fn.bufload(buf) end
88+
return buf
89+
end
90+
)
91+
:filter(is_unique())
92+
:map(
93+
---@param buf integer
94+
function(buf)
95+
local lang_tree, err2 = ts.get_parser(buf, lang, { error = false })
96+
if not lang_tree then
97+
vim.notify(err2, vim.log.levels.ERROR)
98+
return
99+
end
100+
101+
local variables_info = {} ---@type refactor.VariableInfo[]
102+
for _, tree in ipairs(lang_tree:trees()) do
103+
for _, match in query:iter_matches(tree:root(), buf) do
104+
local variable_info ---@type refactor.VariableInfo|nil
105+
for capture_id, nodes in pairs(match) do
106+
local name = query.captures[capture_id]
107+
108+
if name == "variable.identifier" then
109+
variable_info = variable_info or {}
110+
variable_info.identifier = nodes
111+
elseif name == "variable.identifier_separator" then
112+
variable_info = variable_info or {}
113+
variable_info.identifier_separator = nodes
114+
elseif name == "variable.value_separator" then
115+
variable_info = variable_info or {}
116+
variable_info.value_separator = nodes
117+
elseif name == "variable.value" then
118+
variable_info = variable_info or {}
119+
variable_info.value = nodes
120+
elseif name == "variable.declaration" then
121+
variable_info = variable_info or {}
122+
variable_info.declaration = nodes
123+
end
124+
end
125+
if variable_info then table.insert(variables_info, variable_info) end
126+
end
127+
end
128+
129+
return buf, { variables = variables_info }
130+
end
131+
)
132+
:fold(
133+
{},
134+
---@param acc {[integer]: refactor.inline_var.MatchInfo}
135+
---@param k integer
136+
---@param v nil|refactor.inline_var.MatchInfo
137+
function(acc, k, v)
138+
acc[k] = v
139+
return acc
140+
end
141+
)
142+
return ts_info
143+
end
144+
145+
---@class refactor.VariableInfo
105146
---@field identifier TSNode[]
106147
---@field identifier_separator TSNode[]|nil
107148
---@field value TSNode[]
108149
---@field value_separator TSNode[]|nil
109150
---@field declaration TSNode[]
110151

111-
---@class refactor.VariableInfo
152+
---@class refactor.ProcessedVariableInfo
112153
---@field identifier TSNode
113154
---@field identifier_separator TSNode|nil
114155
---@field value TSNode
@@ -150,24 +191,22 @@ function M.inline_var(_, config)
150191
local definitions = unpack(results[1]) ---@type refactor.QfItem[]
151192
local references = unpack(results[2]) ---@type refactor.QfItem[]
152193

153-
local query = ts.query.get(lang, "refactor")
154-
if not query then
155-
vim.notify(("There is no `refactor` query file for language %s"):format(lang), vim.log.levels.ERROR)
156-
return
157-
end
194+
local ts_info = get_match_info(definitions, references, lang)
195+
if not ts_info then return end
158196

159-
---@type {definition: refactor.QfItem, match: refactor.VariableInfo}[]
197+
---@type {definition: refactor.QfItem, match: refactor.ProcessedVariableInfo}[]
160198
local definitions_with_match = iter(definitions)
161199
:map(
162200
---@param d refactor.QfItem
163201
function(d)
164-
-- TODO: parse once and reuse parsed info by buffer (like `inline_func`)
165-
local definition_match = get_definition_info(d, query)
202+
local definition_buf = vim.fn.bufadd(d.filename)
203+
local variables_info = ts_info[definition_buf].variables
204+
local definition_match = get_definition_info(d, variables_info)
166205
return { definition = d, match = definition_match }
167206
end
168207
)
169208
:filter(
170-
---@param dwm {definition: refactor.QfItem, match: refactor.VariableInfo}
209+
---@param dwm {definition: refactor.QfItem, match: refactor.ProcessedVariableInfo}
171210
function(dwm)
172211
return dwm.match ~= nil
173212
end
@@ -182,7 +221,7 @@ function M.inline_var(_, config)
182221
or select(definitions_with_match, {
183222
prompt = "Mutliple definitions found, select one",
184223
format_item =
185-
---@param item {definition: refactor.QfItem, match: refactor.VariableInfo}
224+
---@param item {definition: refactor.QfItem, match: refactor.ProcessedVariableInfo}
186225
function(item)
187226
local buf = vim.fn.bufadd(item.definition.filename)
188227
return ts.get_node_text(item.match.declaration, buf)

0 commit comments

Comments
 (0)