Skip to content

Commit 1bade2b

Browse files
author
zhangfuwen
committed
add github_repos picker
1 parent befa31b commit 1bade2b

File tree

8 files changed

+396
-131
lines changed

8 files changed

+396
-131
lines changed

lua/github_nvim/cache.lua

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
-- lua/github_nvim/cache.lua
2+
local util = require("github_nvim.util")
3+
4+
local M = {}
5+
6+
-- === Configuration: Default settings === --
7+
local DEFAULT_TTL = 60 * 60 * 24 -- 24 hours
8+
local MAX_CACHE_FILES = 50
9+
local CACHE_DIR_PREFIX = "github_nvim" -- e.g. "github", "weather", "news"
10+
11+
-- === Internal helpers === --
12+
13+
-- Get cache directory for a given name
14+
local function cache_dir(cache_name)
15+
return vim.fn.stdpath("cache") .. "/" .. (cache_name or CACHE_DIR_PREFIX)
16+
end
17+
-- Generate safe filename from key
18+
local function generate_filename(cache_name, key)
19+
local sanitized_key = key:gsub("[^%w_]", "_")
20+
local truncated = string.sub(sanitized_key, 1, 64)
21+
return string.format("%s/%s_%s.json", cache_dir(cache_name), cache_name, truncated)
22+
end
23+
24+
25+
-- Ensure cache dir exists
26+
local function ensure_cache_dir(cache_name)
27+
local path = cache_dir(cache_name)
28+
if vim.uv.fs_stat(path) then
29+
return path
30+
end
31+
32+
util.mkdir_p(path) -- 0755
33+
return path
34+
end
35+
36+
-- Check if file is valid (within TTL)
37+
local function is_valid(cache_name, key, ttl)
38+
local file = generate_filename(cache_name, key)
39+
local stat = vim.uv.fs_stat(file)
40+
if not stat then
41+
print(string.format("file %s does not exist.", file))
42+
return false
43+
end
44+
45+
local now = vim.uv.now()
46+
local age = now - stat.mtime.sec
47+
return age < (ttl or DEFAULT_TTL)
48+
end
49+
50+
-- === Public API === --
51+
52+
-- Load cached data by key in a named cache
53+
-- @param cache_name (string): e.g. "github", "weather"
54+
-- @param key (string): unique identifier (e.g. "neovim", "London")
55+
-- @param ttl (number): override TTL in seconds (optional)
56+
-- @return table | nil: returns cached data or nil
57+
function M.load(cache_name, key, ttl)
58+
local file = generate_filename(cache_name, key)
59+
local f = io.open(file, "r")
60+
if not f then
61+
print(string.format("cannot open %s.", file))
62+
return nil
63+
end
64+
65+
local content = f:read("*a")
66+
f:close()
67+
68+
local success, data = pcall(vim.json.decode, content)
69+
if not success then
70+
print("❌ Invalid JSON in cache file:", file, content)
71+
return nil
72+
end
73+
74+
-- print("data is ", vim.inspect(data))
75+
if not data or not data.results then
76+
print(string.format("data decode error, content:%s", content))
77+
return nil
78+
end
79+
80+
if not is_valid(cache_name, key, ttl) then
81+
return nil -- expired
82+
end
83+
84+
return data.results
85+
end
86+
87+
-- Save data to cache
88+
-- @param cache_name (string): cache namespace (e.g. "github")
89+
-- @param key (string): unique key
90+
-- @param results (table): your data (e.g. list of repos)
91+
-- @param ttl (number): time-to-live in seconds (optional)
92+
function M.save(cache_name, key, results, ttl)
93+
local file = generate_filename(cache_name, key)
94+
local dir = cache_dir(cache_name)
95+
ensure_cache_dir(cache_name)
96+
97+
local data = {
98+
results = results,
99+
timestamp = vim.uv.now(),
100+
key = key,
101+
cache_name = cache_name,
102+
ttl = ttl or DEFAULT_TTL,
103+
}
104+
105+
-- print("save results: ", vim.inspect(results))
106+
-- print("data: ", vim.inspect(data))
107+
108+
local f = io.open(file, "w")
109+
if not f then
110+
print("❌ Failed to write cache file:", file)
111+
return
112+
end
113+
114+
local content = vim.json.encode(data)
115+
print("content is " .. content)
116+
local err = f:write(content)
117+
print(vim.inspect(err))
118+
if err then
119+
print("failed to write to file, msg:", err)
120+
end
121+
f:close()
122+
123+
-- Optional: cleanup after save
124+
M.cleanup(cache_name)
125+
end
126+
127+
-- Cleanup old files in a specific cache
128+
-- @param cache_name (string): e.g. "github"
129+
function M.cleanup(cache_name)
130+
local dir = cache_dir(cache_name)
131+
local files = vim.split(vim.fn.glob(dir .. "/*.json"), "\n", { plain = true })
132+
local valid_files = {}
133+
134+
for _, file in ipairs(files) do
135+
local stat = vim.uv.fs_stat(file)
136+
if stat then
137+
table.insert(valid_files, { file = file, mtime = stat.mtime.sec })
138+
end
139+
end
140+
141+
-- Sort by mtime (oldest first)
142+
table.sort(valid_files, function(a, b)
143+
return a.mtime < b.mtime
144+
end)
145+
146+
-- Remove excess files
147+
while #valid_files > MAX_CACHE_FILES do
148+
local old_file = table.remove(valid_files, 1)
149+
vim.uv.fs_unlink(old_file.file)
150+
end
151+
end
152+
153+
-- Clear all files in a cache
154+
-- @param cache_name (string)
155+
function M.clear(cache_name)
156+
local dir = cache_dir(cache_name)
157+
local files = vim.split(vim.fn.glob(dir .. "/*.json"), "\n", { plain = true })
158+
for _, file in ipairs(files) do
159+
vim.uv.fs_unlink(file)
160+
end
161+
end
162+
163+
-- List all cached keys in a cache (for debugging)
164+
-- @param cache_name (string)
165+
-- @return table: list of keys
166+
function M.list_keys(cache_name)
167+
local dir = cache_dir(cache_name)
168+
local files = vim.split(vim.fn.glob(dir .. "/*.json"), "\n", { plain = true })
169+
local keys = {}
170+
171+
for _, file in ipairs(files) do
172+
local basename = vim.fn.fnamemodify(file, ":t:r")
173+
local parts = vim.split(basename, "_", { plain = true })
174+
if #parts >= 2 then
175+
table.insert(keys, parts[2])
176+
end
177+
end
178+
179+
return keys
180+
end
181+
182+
-- === Utility: Safe JSON decode with fallback === --
183+
function M.safe_decode(str)
184+
local ok, data = pcall(vim.json.decode, str)
185+
return ok and data or nil
186+
end
187+
188+
return M

lua/github_nvim/init.lua

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
local M = {}
22
local config = require("github_nvim.config")
3-
print(vim.inspect(config))
3+
4+
5+
-- M.github_repos = require('github_nvim.pickers.github_repos')
6+
-- local telescope = require("telescope")
7+
-- telescope.register_extension {
8+
-- exports = {
9+
-- github_repos = require('github_nvim/pickers/github_repos')
10+
-- }
11+
-- }
12+
require('telescope').load_extension('github_repos')
413

514
function M.setup(options)
615
setmetatable(M, {
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
cache = require("github_nvim.cache")
2+
-- Safely check if telescope is available
3+
local function get_telescope()
4+
local ok, telescope = pcall(require, 'telescope')
5+
if not ok then return nil end
6+
return telescope
7+
end
8+
9+
local function get_fzf_extension()
10+
local ok, fzf = pcall(require, 'telescope.extensions.fzf')
11+
if not ok then return nil end
12+
return fzf
13+
end
14+
15+
local cache_ns = "github_nvim.pickers"
16+
local cache_key = "github_repos"
17+
18+
local function get_github_repos()
19+
cached_result = cache.load(cache_ns, cache_key)
20+
if cached_result then
21+
print("cache hit")
22+
return cached_result
23+
end
24+
25+
local handle = io.popen(
26+
'gh repo list -L 200 --json nameWithOwner,description,isPrivate,isFork,isTemplate 2>/dev/null')
27+
local result = handle:read('*a')
28+
handle:close()
29+
30+
if result == '' then
31+
return {}
32+
end
33+
34+
local ok, json = pcall(vim.json.decode, result)
35+
if not ok then
36+
print("Error parsing GitHub repos: " .. tostring(json))
37+
return {}
38+
end
39+
40+
local items = {}
41+
42+
for _, repo in ipairs(json) do
43+
local name = repo.nameWithOwner
44+
local desc = repo.description or ""
45+
local is_private = repo.isPrivate and "🔒" or ""
46+
local is_fork = repo.isFork and "♻️" or ""
47+
local is_template = repo.isTemplate and "📦" or ""
48+
49+
local display = string.format("%s %s", name, is_private .. is_fork .. is_template)
50+
table.insert(items, {
51+
value = name,
52+
display = display,
53+
description = desc,
54+
is_private = repo.isPrivate,
55+
is_fork = repo.isFork,
56+
is_template = repo.isTemplate,
57+
})
58+
end
59+
60+
print("cache save")
61+
cache.save(cache_ns, cache_key, items)
62+
63+
return items
64+
end
65+
66+
return function()
67+
local conf = require('telescope.config').values
68+
local searcher = function(input, results)
69+
print(input)
70+
local filtered = {}
71+
for _, v in ipairs(results) do
72+
if string.find(v, input, 1, true) then
73+
table.insert(filtered, v)
74+
end
75+
end
76+
return filtered
77+
end
78+
79+
local finder = require('telescope.finders').new_table({
80+
results = get_github_repos(),
81+
entry_maker = function(entry)
82+
return {
83+
value = entry.value,
84+
display = entry.display,
85+
ordinal = entry.value,
86+
}
87+
end,
88+
-- searcher = conf.fuzzy_finder.new_searcher({}),
89+
searcher = searcher,
90+
})
91+
local pickers = require("telescope.pickers")
92+
pickers.new({}, {
93+
prompt_title = "Your github repos",
94+
finder = finder,
95+
sorter = conf.generic_sorter(opts),
96+
-- attach_mappings = function(prompt_bufnr, map)
97+
-- -- Open in browser
98+
-- actions.select_default:replace(function()
99+
-- local selection = action_state.get_selected_entry()
100+
-- local url = "https://github.com/" .. selection.value
101+
-- vim.cmd(('silent !open "%s"'):format(url))
102+
-- end)
103+
--
104+
-- -- Clone repo
105+
-- map('i', '<C-c>', function()
106+
-- local selection = action_state.get_selected_entry()
107+
-- local cmd = ('git clone https://github.com/%s.git'):format(selection.value)
108+
-- vim.cmd(('silent !%s'):format(cmd))
109+
-- vim.notify("Cloned: " .. selection.value, vim.log.levels.INFO)
110+
-- end)
111+
--
112+
-- return true
113+
-- end,
114+
attach_mappings = function(prompt_bufnr, map)
115+
-- Add mappings
116+
map('i', '<CR>', function()
117+
local selection = require('telescope.actions.state').get_selected_entry()
118+
print("Selected:", selection.value)
119+
end)
120+
return true
121+
end,
122+
}):find()
123+
end
124+
125+
-- Main picker function
126+
-- return function()
127+
-- --local telescope = get_telescope()
128+
-- local telescope = require("telescope")
129+
-- if not telescope then
130+
-- print("Telescope not available. Install nvim-telescope/telescope.nvim")
131+
-- return
132+
-- end
133+
--
134+
-- print(vim.inspect(telescope))
135+
--
136+
-- local fzf_ext = get_fzf_extension()
137+
-- -- local picker_fn = fzf_ext and fzf_ext.fzf_picker or telescope.picker.new
138+
-- local picker_fn = require("telescope.pickers").new
139+
--
140+
-- local opts = {
141+
-- prompt_title = "GitHub Repositories",
142+
-- results = get_github_repos(),
143+
-- attach_mappings = function(prompt_bufnr, map)
144+
-- -- Open in browser
145+
-- actions.select_default:replace(function()
146+
-- local selection = action_state.get_selected_entry()
147+
-- local url = "https://github.com/" .. selection.value
148+
-- vim.cmd(('silent !open "%s"'):format(url))
149+
-- end)
150+
--
151+
-- -- Clone repo
152+
-- map('i', '<C-c>', function()
153+
-- local selection = action_state.get_selected_entry()
154+
-- local cmd = ('git clone https://github.com/%s.git'):format(selection.value)
155+
-- vim.cmd(('silent !%s'):format(cmd))
156+
-- vim.notify("Cloned: " .. selection.value, vim.log.levels.INFO)
157+
-- end)
158+
--
159+
-- return true
160+
-- end,
161+
-- }
162+
--
163+
-- -- Use fzf picker if available, else fallback to default
164+
-- local picker = picker_fn(opts)
165+
-- picker:find()
166+
-- end

lua/github_nvim/util.lua

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
M = {}
22
function M.mkdir_p(path)
3-
if vim.loop then
4-
-- Neovim 0.9+:使用 vim.loop
5-
local ok, err = vim.loop.fs_mkdir(path, 0755)
6-
if not ok and err ~= "EEXIST" then
7-
print("Error:", err)
8-
end
9-
else
3+
-- if vim.loop then
4+
-- -- Neovim 0.9+:使用 vim.loop
5+
-- local ok, err = vim.loop.fs_mkdir(path, 0755)
6+
-- if not ok and err ~= "EEXIST" then
7+
-- print("Error:", err)
8+
-- end
9+
-- else
1010
-- Neovim < 0.9:用 shell 命令
11-
local cmd = "mkdir -p " .. path
12-
vim.system({ "bash", "-c", cmd })
13-
end
11+
local cmd = "mkdir -p " .. path
12+
vim.system({ "bash", "-c", cmd })
13+
-- end
1414
end
1515

1616
function M.rm_rf(path)

0 commit comments

Comments
 (0)