Skip to content

Commit cd982f6

Browse files
committed
✨ feat: Implement commit picker with AI suggestions
1 parent 6b653f8 commit cd982f6

File tree

6 files changed

+170
-22
lines changed

6 files changed

+170
-22
lines changed

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ trim_trailing_whitespace = true
55

66
[*.lua]
77
indent_style = space
8-
indent_size = 4
8+
indent_size = 2

lua/commit-ai/backends/gemini.lua

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@ local Common = require("commit-ai.backends.common")
66
local M = {}
77

88
function M.is_available()
9-
local config = require("commit-ai.config")
10-
return Utils.get_api_key(config.provider_options.gemini.api_key) and true or false
9+
local config = require("commit-ai").config
10+
return (config.provider_options.gemini.api_key) and true or false
1111
end
1212

13-
if not M.is_available() then
14-
Log.error("Gemini API_KEY is not set")
15-
end
13+
-- if not M.is_available() then
14+
-- Log.error("Gemini API_KEY is not set")
15+
-- end
1616

17-
function M.query_ai(prompt, cb)
18-
local config = require("commit-ai.config")
19-
local options = vim.deepcopy(config.provider_options.gemini)
17+
function M.call_ai(prompt, cb)
18+
local commit_ai = require("commit-ai")
19+
local options = vim.deepcopy(commit_ai.config.provider_options.gemini)
2020

2121
local request_data = {
2222
contents = {
@@ -42,8 +42,9 @@ function M.query_ai(prompt, cb)
4242
string.format(
4343
'https://generativelanguage.googleapis.com/v1beta/models/%s:%skey=%s',
4444
options.model,
45-
options.stream and 'streamGenerateContent?alt=sse&' or 'generateContent?',
46-
Utils.get_api_key(options.api_key)
45+
-- options.stream and 'streamGenerateContent?alt=sse&' or
46+
'generateContent?',
47+
(options.api_key)
4748
),
4849
'-H', 'Content-Type: application/json',
4950
'-d', '@' .. tmp_file
@@ -76,7 +77,7 @@ function M.query_ai(prompt, cb)
7677
end)
7778
}
7879

79-
Common.register_job(new_job)
80+
-- Common.register_job(new_job)
8081
new_job:start()
8182

8283
return new_job

lua/commit-ai/backends/init.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
require("commit-ai.backends.gemini")

lua/commit-ai/commit.lua

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,140 @@
11
local M = {}
2+
local Utils = require("commit-ai.utils")
3+
local pickers = require("telescope.pickers")
4+
local Log = require("commit-ai.log")
5+
local finders = require("telescope.finders")
6+
local conf = require("telescope.config").values
7+
local actions = require("telescope.actions")
8+
local action_state = require("telescope.actions.state")
9+
local previewers = require("telescope.previewers")
10+
local telescope = require("telescope.builtin")
11+
local backends = require("commit-ai.backends.gemini")
212

13+
local function query_ai(prompt, cb)
14+
backends.call_ai(prompt, function(response)
15+
-- TODO: need to handle error from gemini
16+
if response then
17+
local commit_messages = {}
18+
if response.candidates and response.candidates[1] and response.candidates[1].content then
19+
local content = response.candidates[1].content.parts[1].text
20+
for line in content:gmatch("%- ([^\n]+)") do
21+
table.insert(commit_messages, line)
22+
end
23+
end
324

25+
cb(commit_messages)
26+
else
27+
print("No response received")
28+
cb({})
29+
end
30+
end)
31+
end
32+
33+
local function generate_commit_suggestions(cb)
34+
local diff = Utils.get_git_diff()
35+
if not diff or diff == "" then
36+
print("No staged changes found")
37+
return {}
38+
end
39+
40+
local config = require("commit-ai").config
41+
local git_conventions = config.git_conventions
42+
local format_lines = {}
43+
for _, convention in pairs(git_conventions) do
44+
table.insert(format_lines, string.format("- %s %s: %s", convention.icon, convention.prefix, convention.type))
45+
end
46+
47+
local prompt = string.format(
48+
"Analyze this git diff and generate commit messages in plain text.\n" ..
49+
"Use exactly this format without additional explanation:\n\n" ..
50+
"- <icon> <prefix>: <commit message>\n\n" ..
51+
"Options:\n%s\n\nGit diff:\n%s",
52+
table.concat(format_lines, "\n"),
53+
diff
54+
)
55+
56+
-- local prompt = string.format(
57+
-- "Analyze this git diff and generate multiple commit messages using exactly one of these formats:\n" ..
58+
-- "Format:\n" ..
59+
-- "- <icon> <prefix>: <commit message>\n\n" ..
60+
-- "Only respond with the commit messages, without any explanations.\n\n" ..
61+
-- "- %s %s: Documentation changes\n" ..
62+
-- "- %s %s: Bug fix\n" ..
63+
-- "- %s %s: New feature\n" ..
64+
-- "- %s %s: Chore\n" ..
65+
-- "- %s %s: Breaking change\n" ..
66+
-- "- %s %s: Enhancement\n\n" ..
67+
-- "Git diff:\n%s",
68+
-- git_conventions.docs.icon, git_conventions.docs.prefix,
69+
-- git_conventions.fix.icon, git_conventions.fix.prefix,
70+
-- git_conventions.feat.icon, git_conventions.feat.prefix,
71+
-- git_conventions.chore.icon, git_conventions.chore.prefix,
72+
-- git_conventions.refactor.icon, git_conventions.refactor.prefix,
73+
-- git_conventions.enhance.icon, git_conventions.enhance.prefix,
74+
-- diff
75+
-- )
76+
query_ai(prompt, cb)
77+
end
78+
79+
local diff_previewer = previewers.new_buffer_previewer({
80+
title = "Git Diff Preview",
81+
get_buffer_by_name = function()
82+
return "Git Diff"
83+
end,
84+
define_preview = function(self, entry)
85+
local diff = Utils.get_git_diff()
86+
vim.api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, vim.split(diff, "\n"))
87+
vim.api.nvim_buf_set_option(self.state.bufnr, "filetype", "diff")
88+
end
89+
})
90+
91+
-- TODO: lets try to call default_config directly here
92+
function M.setup(config)
93+
local default_config = require("commit-ai.config")
94+
M.config = vim.tbl_deep_extend('force', default_config, config or {})
95+
end
96+
97+
function M.commit_picker()
98+
generate_commit_suggestions(function(commit_suggestions)
99+
if not commit_suggestions or #commit_suggestions == 0 then
100+
print("No commit suggestions available")
101+
return
102+
end
103+
104+
pickers.new({}, {
105+
prompt_title = "Select Commit Message",
106+
finder = finders.new_table({
107+
results = commit_suggestions,
108+
entry_maker = function(entry)
109+
return {
110+
value = entry,
111+
display = entry,
112+
ordinal = entry,
113+
}
114+
end,
115+
}),
116+
sorter = conf.generic_sorter({}),
117+
previewer = diff_previewer,
118+
attach_mappings = function(prompt_bufnr)
119+
actions.select_default:replace(function()
120+
local selection = action_state.get_selected_entry()
121+
actions.close(prompt_bufnr)
122+
123+
local cmd = string.format('git commit -m "%s"', selection.value)
124+
-- TODO:as for now we still add the staged changes into one single commit
125+
-- later it should be possible to add each change into a separate commit
126+
vim.fn.system('git add .')
127+
vim.fn.system(cmd)
128+
print("Committed: " .. selection.value)
129+
end)
130+
return true
131+
end,
132+
}):find()
133+
end)
134+
end
135+
136+
vim.api.nvim_create_user_command("Commit", function()
137+
M.commit_picker()
138+
end, {})
4139

5140
return M

lua/commit-ai/config.lua

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,35 @@
1+
-- default config
12
local M = {
2-
-- default config
3-
conventions = {
4-
docs = { icon = "📖", prefix = "docs", type = "Documentation changes" },
5-
fix = { icon = "🐛", prefix = "fix", type = "Bug fix" },
6-
feat = { icon = "", prefix = "feat", type = "New feature" },
7-
enhance = { icon = "", prefix = "enhance", type = "Enhancement" },
8-
chore = { icon = "🧹", prefix = "chore", type = "Chore" },
9-
refactor = { icon = "⚠️", prefix = "refactor", type = "Breaking change" }
10-
},
3+
-- define for adding icon to your commit msg
4+
icons = false
5+
}
6+
7+
-- unopiniated commit conventions
8+
M.git_conventions = {
9+
docs = { icon = "📖", prefix = "docs", type = "Documentation changes" },
10+
fix = { icon = "🐛", prefix = "fix", type = "Bug fix" },
11+
feat = { icon = "", prefix = "feat", type = "New feature" },
12+
enhance = { icon = "", prefix = "enhance", type = "Enhancement" },
13+
chore = { icon = "🧹", prefix = "chore", type = "Chore" },
14+
refactor = { icon = "⚠️", prefix = "refactor", type = "Breaking change" }
1115
}
1216

17+
-- provider options
1318
M.provider_options = {
1419
openai = {
20+
model = 'gpt-4o',
1521
api_key = 'YOUR_API_KEY',
22+
stream = false,
1623
},
1724
gemini = {
25+
model = 'gemini-2.0-flash',
1826
api_key = 'YOUR_API_KEY',
27+
stream = false,
1928
},
2029
claude = {
30+
model = 'sonnet',
2131
api_key = 'YOUR_API_KEY',
32+
stream = false,
2233
},
2334
}
2435

lua/commit-ai/init.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
require('commit-ai.commit')
1+
return require('commit-ai.commit')

0 commit comments

Comments
 (0)