Skip to content

Commit 3407abd

Browse files
committed
feat: support read snippets from json file
1 parent 3329995 commit 3407abd

File tree

4 files changed

+178
-27
lines changed

4 files changed

+178
-27
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ vim.g.phoenix = {
7979
throttle_delay_ms = 200, -- Wait 200ms between updates
8080
ignore_patterns = {}, -- Dictionary or file ignored when path completion
8181
},
82+
snippet = '' -- path of snippet json file like c.json/zig.json/go.json
8283
}
8384
```
8485

lua/phoenix/async.lua

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
local uv = vim.uv
2+
3+
local function wrap_async(func)
4+
return function(...)
5+
local args = { ... }
6+
return function(callback)
7+
table.insert(args, callback)
8+
func(unpack(args))
9+
end
10+
end
11+
end
12+
13+
local async_fs_open = wrap_async(uv.fs_open)
14+
local async_fs_fstat = wrap_async(uv.fs_fstat)
15+
local async_fs_read = wrap_async(uv.fs_read)
16+
local async_fs_close = wrap_async(uv.fs_close)
17+
18+
local function await(promise)
19+
local co = coroutine.running()
20+
promise(function(...)
21+
local args = { ... }
22+
vim.schedule(function()
23+
assert(coroutine.resume(co, unpack(args)))
24+
end)
25+
end)
26+
return coroutine.yield()
27+
end
28+
29+
local function async(func)
30+
return function(...)
31+
local co = coroutine.create(func)
32+
local function step(...)
33+
local ok, err = coroutine.resume(co, ...)
34+
if not ok then
35+
error(err)
36+
end
37+
end
38+
step(...)
39+
end
40+
end
41+
42+
local read_file = async(function(filepath, callback)
43+
local err, fd = await(async_fs_open(filepath, 'r', 438))
44+
assert(not err, err)
45+
46+
local err, stat = await(async_fs_fstat(fd))
47+
assert(not err, err)
48+
49+
local err, data = await(async_fs_read(fd, stat.size, 0))
50+
await(async_fs_close(fd))
51+
assert(not err, err)
52+
callback(data)
53+
end)
54+
55+
function throttle(fn, delay)
56+
local timer = nil
57+
return function(...)
58+
local args = { ... }
59+
if timer and not timer:is_closing() then
60+
timer:stop()
61+
timer:close()
62+
end
63+
timer = assert(vim.uv.new_timer())
64+
timer:start(
65+
delay,
66+
0,
67+
vim.schedule_wrap(function()
68+
if timer and not timer:is_closing() then
69+
timer:stop()
70+
timer:close()
71+
fn(unpack(args))
72+
end
73+
end)
74+
)
75+
end
76+
end
77+
78+
return {
79+
read_file = read_file,
80+
throttle = throttle,
81+
}

lua/phoenix/init.lua

Lines changed: 95 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ local default = {
4646
throttle_delay_ms = 200, -- Wait 200ms between updates
4747
ignore_patterns = {}, -- No ignore patterns by default
4848
},
49+
snippet = '',
4950
}
5051

5152
--@type PhoenixConfig
@@ -227,31 +228,6 @@ end
227228

228229
local scan_cache = LRUCache:new(100)
229230

230-
local async = {}
231-
232-
function async.throttle(fn, delay)
233-
local timer = nil
234-
return function(...)
235-
local args = { ... }
236-
if timer and not timer:is_closing() then
237-
timer:stop()
238-
timer:close()
239-
end
240-
timer = assert(vim.uv.new_timer())
241-
timer:start(
242-
delay,
243-
0,
244-
vim.schedule_wrap(function()
245-
if timer and not timer:is_closing() then
246-
timer:stop()
247-
timer:close()
248-
fn(unpack(args))
249-
end
250-
end)
251-
)
252-
end
253-
end
254-
255231
local server = {}
256232

257233
local function get_root(filename)
@@ -500,6 +476,8 @@ local function process_lines_batch(lines, start_idx, batch_size, seen, dict_conf
500476
return end_idx
501477
end
502478

479+
local async = require('phoenix.async')
480+
503481
-- Initialize dictionary with full content
504482
local initialize_dict = async.throttle(function(content)
505483
local dict_config = Config.dict
@@ -563,6 +541,88 @@ local update_dict = async.throttle(function(new, old)
563541
end
564542
end, Config.scanner.throttle_delay_ms)
565543

544+
local Snippet = {
545+
cache = {},
546+
loading = {},
547+
}
548+
549+
function Snippet:preload()
550+
local ft = vim.bo.filetype
551+
if self.cache[ft] or self.loading[ft] then
552+
return
553+
end
554+
local path = vim.fs.joinpath(Config.snippet, ('%s.json'):format(ft))
555+
if vim.fn.filereadable(path) == 1 then
556+
self.loading[ft] = true
557+
async.read_file(path, function(data)
558+
local success, snippets = pcall(vim.json.decode, data)
559+
if success then
560+
self.cache[ft] = snippets
561+
self.loading[ft] = nil
562+
else
563+
vim.notify(
564+
string.format('Error parsing snippet file for %s: %s', ft, snippets),
565+
vim.log.levels.ERROR
566+
)
567+
self.cache[ft] = {}
568+
self.loading[ft] = nil
569+
end
570+
end)
571+
end
572+
end
573+
574+
local function parse_snippet(input)
575+
local ok, parsed = pcall(function()
576+
return vim.lsp._snippet_grammar.parse(input)
577+
end)
578+
return ok and tostring(parsed) or input
579+
end
580+
581+
function Snippet:get_completions(prefix)
582+
local ft = vim.bo.filetype
583+
local results = {}
584+
585+
if not self.cache[ft] then
586+
return results
587+
end
588+
589+
local snippets = self.cache[ft]
590+
for trigger, snippet_data in pairs(snippets) do
591+
if vim.startswith(trigger:lower(), prefix:lower()) then
592+
local body = snippet_data.body
593+
local insert_text = body
594+
595+
if type(body) == 'table' then
596+
insert_text = table.concat(body, '\n')
597+
end
598+
599+
table.insert(results, {
600+
label = trigger,
601+
kind = 15,
602+
insertText = insert_text,
603+
documentation = {
604+
kind = 'markdown',
605+
value = snippet_data.description
606+
.. '\n\n```'
607+
.. ft
608+
.. '\n'
609+
.. parse_snippet(insert_text)
610+
.. '\n```',
611+
},
612+
detail = 'Snippet: ' .. (snippet_data.description or ''),
613+
sortText = string.format('001%s', trigger),
614+
insertTextFormat = 2,
615+
})
616+
end
617+
end
618+
619+
table.sort(results, function(a, b)
620+
return a.label < b.label
621+
end)
622+
623+
return results
624+
end
625+
566626
local function collect_completions(prefix)
567627
local results = Trie.search_prefix(dict.trie, prefix)
568628
local now = vim.uv.now()
@@ -742,7 +802,8 @@ function server.create()
742802
end
743803

744804
local items = collect_completions(prefix)
745-
schedule_result(callback, items)
805+
local snippets = Snippet:get_completions(prefix)
806+
schedule_result(callback, vim.list_extend(items, snippets))
746807
end
747808
end
748809

@@ -791,12 +852,19 @@ return {
791852
register = function()
792853
vim.api.nvim_create_autocmd('FileType', {
793854
pattern = Config.filetypes,
794-
callback = function()
855+
callback = function(args)
795856
vim.lsp.start({
796857
name = 'phoenix',
797858
cmd = server.create(),
798859
root_dir = vim.uv.cwd(),
860+
reuse_client = function()
861+
return true
862+
end,
799863
})
864+
865+
if #Config.snippet > 0 then
866+
Snippet:preload()
867+
end
800868
end,
801869
})
802870
end,

lua/phoenix/types.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,4 @@
4545
---@field completion CompletionConfig Settings controlling the core dictionary behavior
4646
---@field cleanup CleanupConfig Settings controlling how and when dictionary cleanup occurs
4747
---@field scanner ScannerConfig Settings controlling file system scanning behavior
48+
---@field snippet string Snippet path

0 commit comments

Comments
 (0)