Skip to content

Commit 19c7d6d

Browse files
authored
feat: Implements go to references (#41)
1 parent 9cbc052 commit 19c7d6d

17 files changed

+311
-58
lines changed

lua/js-i18n/analyzer.lua

Lines changed: 59 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ local library_query = {
1919
--- @param file string ファイルパス
2020
--- @return string|nil クエリ
2121
function M.load_query_from_file(file)
22-
local cache = {}
22+
local query_cache = {}
2323

2424
local function load_query_from_file(file)
25-
if cache[file] ~= nil then
26-
return cache[file]
25+
if query_cache[file] ~= nil then
26+
return query_cache[file]
2727
end
2828

2929
local f = io.open(file, "r")
@@ -34,7 +34,7 @@ function M.load_query_from_file(file)
3434
local query = f:read("*a")
3535
f:close()
3636

37-
cache[file] = query
37+
query_cache[file] = query
3838
return query
3939
end
4040

@@ -71,13 +71,19 @@ local function calculate_node_depth(node)
7171
end
7272

7373
--- Treesitterパーサーをセットアップしてキーにマッチするノードを取得する関数
74-
--- @param bufnr number 文言ファイルのバッファ番号
74+
--- @param source integer|string バッファ番号 or ソース
7575
--- @param keys string[] キー
7676
--- @return TSNode | nil, string | nil
77-
function M.get_node_for_key(bufnr, keys)
77+
function M.get_node_for_key(source, keys)
7878
local ts = vim.treesitter
7979

80-
local parser = ts.get_parser(bufnr, "json")
80+
local parser = (function()
81+
if type(source) == "string" then
82+
return ts.get_string_parser(source, "json")
83+
else
84+
return ts.get_parser(source)
85+
end
86+
end)()
8187
local tree = parser:parse()[1]
8288
local root = tree:root()
8389

@@ -86,15 +92,15 @@ function M.get_node_for_key(bufnr, keys)
8692
local query = ts.query.parse("json", [[
8793
(pair
8894
key: (string) @key (#eq? @key "\"]] .. k .. [[\"")
89-
value: [(object)(string)] @value
95+
value: [(object)(array)(string)] @value
9096
)
9197
]])
9298

9399
local key_node = nil
94100
local key_node_depth = 9999
95101
local value_node = nil
96102
local value_node_depth = 9999
97-
for id, node, _ in query:iter_captures(json_node, bufnr) do
103+
for id, node, _ in query:iter_captures(json_node, source) do
98104
local name = query.captures[id]
99105
local depth = calculate_node_depth(node)
100106
if name == "key" and depth < key_node_depth then
@@ -164,20 +170,20 @@ end
164170

165171
--- t関数の取得に関する情報を解析する
166172
--- @param target_node TSNode t関数取得のノード
167-
--- @param bufnr integer バッファ番号
173+
--- @param source (integer|string) バッファ番号 or ソース
168174
--- @param query vim.treesitter.Query クエリ
169175
--- @return GetTDetail|nil
170-
local function parse_get_t(target_node, bufnr, query)
176+
local function parse_get_t(target_node, source, query)
171177
local namespace = ""
172178
local key_prefix = ""
173179

174-
for id, node, _ in query:iter_captures(target_node, bufnr, 0, -1) do
180+
for id, node, _ in query:iter_captures(target_node, source, 0, -1) do
175181
local name = query.captures[id]
176182

177183
if name == "i18n.namespace" then
178-
namespace = vim.treesitter.get_node_text(node, bufnr)
184+
namespace = vim.treesitter.get_node_text(node, source)
179185
elseif name == "i18n.key_prefix" then
180-
key_prefix = vim.treesitter.get_node_text(node, bufnr)
186+
key_prefix = vim.treesitter.get_node_text(node, source)
181187
end
182188
end
183189

@@ -199,30 +205,30 @@ end
199205

200206
--- t関数の呼び出しに関する情報を解析する
201207
--- @param target_node TSNode t関数呼び出しのノード
202-
--- @param bufnr integer バッファ番号
208+
--- @param source (integer|string) バッファ番号 or ソース
203209
--- @param query vim.treesitter.Query クエリ
204210
--- @return CallTDetail|nil
205-
local function parse_call_t(target_node, bufnr, query)
211+
local function parse_call_t(target_node, source, query)
206212
local key = nil
207213
local key_node = nil
208214
local key_arg_node = nil
209215
local namespace = nil
210216
local key_prefix = nil
211217

212-
for id, node, _ in query:iter_captures(target_node, bufnr, 0, -1) do
218+
for id, node, _ in query:iter_captures(target_node, source, 0, -1) do
213219
local name = query.captures[id]
214220

215221
-- t関数の呼び出しがネストしている場合があるため、最初に見つかったものを採用する
216222
-- そのため key = ke or ... のような形にしている
217223
if name == "i18n.key" then
218-
key = key or vim.treesitter.get_node_text(node, bufnr)
224+
key = key or vim.treesitter.get_node_text(node, source)
219225
key_node = key_node or node
220226
elseif name == "i18n.key_arg" then
221227
key_arg_node = key_arg_node or node
222228
elseif name == "i18n.namespace" then
223-
namespace = namespace or vim.treesitter.get_node_text(node, bufnr)
229+
namespace = namespace or vim.treesitter.get_node_text(node, source)
224230
elseif name == "i18n.key_prefix" then
225-
key_prefix = key_prefix or vim.treesitter.get_node_text(node, bufnr)
231+
key_prefix = key_prefix or vim.treesitter.get_node_text(node, source)
226232
end
227233
end
228234

@@ -239,6 +245,14 @@ local function parse_call_t(target_node, bufnr, query)
239245
}
240246
end
241247

248+
--- t関数を含むノードを検索する
249+
--- @param bufnr integer バッファ番号
250+
--- @return FindTExpressionResultItem[]
251+
function M.find_call_t_expressions_from_buf(bufnr)
252+
local workspace_dir = utils.get_workspace_root(bufnr)
253+
return M.find_call_t_expressions(bufnr, utils.detect_library(workspace_dir))
254+
end
255+
242256
--- @class FindTExpressionResultItem
243257
--- @field node TSNode t関数のノード
244258
--- @field key_node TSNode t関数のキーのノード
@@ -249,24 +263,39 @@ end
249263
--- @field namespace? string t 関数の namespace
250264

251265
--- t関数を含むノードを検索する
252-
--- @param bufnr integer バッファ番号
266+
--- @param source integer|string バッファ番号 or ソース
267+
--- @param lib? string ライブラリ
268+
--- @param lang? string 言語
253269
--- @return FindTExpressionResultItem[]
254-
function M.find_call_t_expressions(bufnr)
255-
local ok, parser = pcall(vim.treesitter.get_parser, bufnr)
270+
function M.find_call_t_expressions(source, lib, lang)
271+
local ok, parser = pcall(function()
272+
if type(source) == "string" then
273+
vim.validate({ lang = { lang, "string", true } })
274+
if lang == nil then
275+
error("lang is required when source is string")
276+
end
277+
return vim.treesitter.get_string_parser(source, lang)
278+
else
279+
return vim.treesitter.get_parser(source)
280+
end
281+
end)
256282
if not ok then
257283
return {}
258284
end
285+
259286
local tree = parser:parse()[1]
260287
local root_node = tree:root()
261-
local language = parser:lang()
288+
local language = lang or parser:lang()
262289

263290
if not vim.tbl_contains({ "javascript", "typescript", "jsx", "tsx" }, language) then
264291
return {}
265292
end
266293

267-
local library = utils.detect_library(bufnr) or utils.Library.I18Next
268294
local query_str = ""
269-
for _, query_file in ipairs(library_query[library][language] or library_query[library]["*"]) do
295+
if type(library_query[lib]) ~= "table" then
296+
return {}
297+
end
298+
for _, query_file in ipairs(library_query[lib][language] or library_query[lib]["*"]) do
270299
local str = M.load_query_from_file(query_file)
271300
if str and type(str) == "string" and str ~= "" then
272301
query_str = query_str .. "\n" .. str
@@ -303,7 +332,7 @@ function M.find_call_t_expressions(bufnr)
303332
--- @type FindTExpressionResultItem[]
304333
local result = {}
305334

306-
for id, node, _ in query:iter_captures(root_node, bufnr) do
335+
for id, node, _ in query:iter_captures(root_node, source) do
307336
local name = query.captures[id]
308337

309338
-- 現在のスコープから抜けたかどうかを判定する
@@ -313,7 +342,7 @@ function M.find_call_t_expressions(bufnr)
313342
end
314343

315344
if name == "i18n.get_t" then
316-
local get_t_detail = parse_get_t(node, bufnr, query)
345+
local get_t_detail = parse_get_t(node, source, query)
317346
if get_t_detail then
318347
-- 同一のスコープ内で get_t が呼ばれた場合はスコープを上書きする形になるように、一度 leave_scope してから enter_scope する
319348
if get_t_detail.scope_node == current_scope().scope_node then
@@ -323,7 +352,7 @@ function M.find_call_t_expressions(bufnr)
323352
end
324353
elseif name == "i18n.call_t" then
325354
local scope = current_scope()
326-
local call_t_detail = parse_call_t(node, bufnr, query)
355+
local call_t_detail = parse_call_t(node, source, query)
327356

328357
if call_t_detail == nil then
329358
goto continue
@@ -363,7 +392,7 @@ end
363392
--- @return boolean ok カーソルが t 関数の引数内にあるかどうか
364393
--- @return FindTExpressionResultItem | nil result カーソルが t 関数の引数内にある場合は t 関数の情報
365394
function M.check_cursor_in_t_argument(bufnr, position)
366-
local t_calls = M.find_call_t_expressions(bufnr)
395+
local t_calls = M.find_call_t_expressions_from_buf(bufnr)
367396

368397
for _, t_call in ipairs(t_calls) do
369398
local key_arg_node = t_call.key_arg_node

lua/js-i18n/init.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ i18n.setup = function(opts)
7474
"typescriptreact",
7575
"javascript.jsx",
7676
"typescript.tsx",
77+
"json",
7778
},
7879
single_file_support = true,
7980
},

lua/js-i18n/lsp/checker.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ function M.check(client, uri)
1010
local bufnr = vim.uri_to_bufnr(uri)
1111
local workspace_dir = utils.get_workspace_root(bufnr)
1212
local t_source = client.t_source_by_workspace[workspace_dir]
13-
local library = utils.detect_library(bufnr)
13+
local library = utils.detect_library(workspace_dir)
1414

1515
local dispatchers = require("js-i18n.lsp.config").dispatchers
1616
if not dispatchers then
@@ -28,7 +28,7 @@ function M.check(client, uri)
2828
--- @type lsp.Diagnostic[]
2929
local diagnostics = {}
3030

31-
local t_calls = analyzer.find_call_t_expressions(bufnr)
31+
local t_calls = analyzer.find_call_t_expressions_from_buf(bufnr)
3232
for _, t_call in ipairs(t_calls) do
3333
local key = t_call.key
3434
local keys = vim.split(key, c.config.key_separator, { plain = true })

lua/js-i18n/lsp/config.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,7 @@ local M = {}
33
--- @type vim.lsp.rpc.Dispatchers
44
M.dispatchers = nil
55

6+
--- @type table<string, I18n.ReferenceTable>
7+
M.ref_table_by_workspace = {}
8+
69
return M

lua/js-i18n/lsp/init.lua

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@ function M.create_rpc(dispatchers, client)
3535
--- @type I18n.lsp.ProtocolModule
3636
local protocol_module = module
3737

38-
local err, result = protocol_module.handler(params, client)
39-
if err then
40-
callback(err, nil)
41-
else
42-
callback(nil, result)
43-
end
38+
vim.schedule(function()
39+
local err, result = protocol_module.handler(params, client)
40+
if err then
41+
callback(err, nil)
42+
else
43+
callback(nil, result)
44+
end
45+
end)
4446
return true
4547
end,
4648
notify = function(method, params)
@@ -52,7 +54,10 @@ function M.create_rpc(dispatchers, client)
5254
end
5355
--- @type I18n.lsp.NotifyProtocolModule
5456
local protocol_module = module
55-
protocol_module.handler(params, client)
57+
58+
vim.schedule(function()
59+
protocol_module.handler(params, client)
60+
end)
5661
return true
5762
end,
5863
is_closing = function()

lua/js-i18n/lsp/protocol/notify/text_document_did_change.lua

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,26 @@
1+
local lsp_config = require("js-i18n.lsp.config")
2+
local reference_table = require("js-i18n.reference_table")
3+
14
--- ハンドラ
25
--- @param params lsp.DidChangeTextDocumentParams
36
--- @param client I18n.Client
47
local function handler(params, client)
58
if #params.contentChanges ~= 0 then
69
local uri = params.textDocument.uri
10+
11+
local bufnr = vim.uri_to_bufnr(uri)
12+
local workspace_dir = require("js-i18n.utils").get_workspace_root(bufnr)
13+
local ref_table = lsp_config.ref_table_by_workspace[workspace_dir]
14+
if lsp_config.ref_table_by_workspace[workspace_dir] == nil then
15+
local ref_table = reference_table.ReferenceTable.new({
16+
workspace_dir = workspace_dir,
17+
})
18+
lsp_config.ref_table_by_workspace[workspace_dir] = ref_table
19+
ref_table:load_all()
20+
else
21+
ref_table:load_path(vim.uri_to_fname(uri), params.contentChanges[1].text)
22+
end
23+
724
vim.schedule(function()
825
require("js-i18n.lsp.checker").check(client, uri)
926
end)

lua/js-i18n/lsp/protocol/notify/text_document_did_open.lua

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
1+
local lsp_config = require("js-i18n.lsp.config")
2+
local reference_table = require("js-i18n.reference_table")
3+
local utils = require("js-i18n.utils")
4+
15
--- ハンドラ
26
--- @param params lsp.DidOpenTextDocumentParams
37
--- @param client I18n.Client
48
local function handler(params, client)
59
local uri = params.textDocument.uri
10+
11+
local bufnr = vim.uri_to_bufnr(uri)
12+
local workspace_dir = require("js-i18n.utils").get_workspace_root(bufnr)
13+
if lsp_config.ref_table_by_workspace[workspace_dir] == nil then
14+
local ref_table = reference_table.ReferenceTable.new({
15+
workspace_dir = workspace_dir,
16+
})
17+
lsp_config.ref_table_by_workspace[workspace_dir] = ref_table
18+
ref_table:load_all()
19+
end
20+
621
vim.schedule(function()
722
require("js-i18n.lsp.checker").check(client, uri)
823
end)

lua/js-i18n/lsp/protocol/request/initialize.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ local function handler(params, _client)
1515
capabilities = {
1616
textDocumentSync = 1,
1717
definitionProvider = true,
18+
referencesProvider = true,
1819
hoverProvider = true,
1920
completionProvider = {},
2021
codeActionProvider = {},

lua/js-i18n/lsp/protocol/request/text_document_completion.lua

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ end
3939
--- @return lsp.CompletionItem[]
4040
local function get_completion_items(client, bufnr, t_call)
4141
local lang = client:get_language(bufnr)
42-
local t_source = client.t_source_by_workspace[utils.get_workspace_root(bufnr)]
43-
local library = utils.detect_library(bufnr)
42+
local workspace_dir = utils.get_workspace_root(bufnr)
43+
local t_source = client.t_source_by_workspace[workspace_dir]
44+
local library = utils.detect_library(workspace_dir)
4445

4546
local key_prefix = t_call.key_prefix or ""
4647

0 commit comments

Comments
 (0)