Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,25 +353,26 @@ Automates the commit process using AI. This command has three main modes:
When run without flags (or with `-n`), it automates the creation of a new commit:
1. Stages all current changes (`git add .`).
2. Generates a commit message based on the staged diff.
3. Displays the generated message for confirmation (`y/n`).
4. If confirmed, it commits the changes with the generated message.
3. Opens a commit message buffer for editing. The buffer lists all files included in the commit as comments at the bottom.
4. In Normal mode, move the cursor over the file list and press `<Space>` to toggle file inclusion (deselected files are marked with a `-` sign, and pressing `<Space>` again re-adds them).
5. Save and close the buffer to commit. All files marked with `-` are removed from the commit, and only the remaining files are committed. Close without saving to abort.

**Regenerate Mode (`-r`):**

When run with the `-r` flag, it regenerates the commit message for the last commit (`HEAD`) and amends it:
1. It gets the diff from the `HEAD` commit.
2. It generates a new commit message based on that diff.
3. It displays the message for confirmation (`y/n`).
4. If confirmed, it amends the `HEAD` commit with the new message. This is useful for quickly rewriting a commit message you just made.
3. Opens the commit message buffer listing the files in `HEAD` (press `<Space>` in Normal mode to toggle/deselect files).
4. If confirmed and saved, it amends the `HEAD` commit with the new message and deselected files removed.

**Amend Code and Regenerate Mode (`-a`):**

When run with the `-a` flag, it amends the current working tree changes into the last commit (`HEAD`) and regenerates the commit message:
1. Stages current changes.
2. Amends the code into `HEAD` (`git commit --amend --no-edit`).
3. Generates a new commit message based on the new combined diff from `HEAD`.
4. Displays the message for confirmation (`y/n`).
5. If confirmed, it amends the `HEAD` commit with the new message.
4. Opens the commit message buffer listing files (press `<Space>` in Normal mode to toggle/deselect files).
5. If confirmed and saved, it amends the `HEAD` commit with the new message and deselected files removed.

You can also provide optional `[instructions]` at the end of the command to guide the AI's message generation (e.g., "Mention issue #123" or "Keep it very brief").

Expand Down
4 changes: 4 additions & 0 deletions doc/vimini.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ COMMANDS *vimini-commands*

*:ViminiCommit* [-n] [-r] [-a] [instruction]
Generates a commit message and commits changes using Gemini.
Files included in the commit are listed as comments at the bottom of the
buffer. In Normal mode, press <Space> on a file to toggle its inclusion
(deselected files are marked with -). When saved and closed, deselected
files are removed and only remaining files are committed.
-n: Omit co-author trailer.
-r: Regenerate commit message for HEAD and amend.
-a: Amend working tree changes into HEAD and regenerate commit message.
Expand Down
11 changes: 11 additions & 0 deletions plugin/vimini.vim
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,17 @@ endfunction

command! -nargs=* ViminiCommit call ViminiCommit(<q-args>)

" Expose a function to toggle file selection in commit buffer
function! ViminiCommitToggleFile()
py3 << EOF
try:
from vimini.commit import toggle_file
toggle_file()
except Exception as e:
pass
EOF
endfunction

" Expose a function to manage uploaded files
function! ViminiFiles()
py3 << EOF
Expand Down
3 changes: 3 additions & 0 deletions python3/vimini/agent/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ def execute_function(req_id, method, params, result_queue, conn):
"diff_stat_output": params.get("diff_stat_output")
if isinstance(params, dict)
else None,
"commit_files": params.get("commit_files")
if isinstance(params, dict)
else None,
"regenerate": params.get("regenerate")
if isinstance(params, dict)
else False,
Expand Down
108 changes: 63 additions & 45 deletions python3/vimini/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,58 +403,76 @@ def apply_patch(diff_content, project_root=None, silent=False):
util.display_message(err_msg, error=True)
return False, err_msg

# Success
util.display_message("Successfully applied modified diff.", history=True)

# Parse diff to identify modified files for reloading
modified_files = set()
for line in diff_content.split("\n"):
if line.startswith("--- a/"):
path = line[len("--- a/") :].strip()
if path != "/dev/null":
modified_files.add(path)
elif line.startswith("+++ b/"):
path = line[len("+++ b/") :].strip()
if path != "/dev/null":
modified_files.add(path)

for relative_path in modified_files:
absolute_path = os.path.join(project_root, relative_path)

# Clean up .orig files potentially created by patch
orig_path = absolute_path + ".orig"
if os.path.exists(orig_path):
try:
os.remove(orig_path)
except Exception:
pass

normalized_target_path = os.path.realpath(absolute_path)
for buf in vim.buffers:
if buf.name and os.path.realpath(buf.name) == normalized_target_path:
# Reload buffer if visible
win_nr = vim.eval(f"bufwinnr({buf.number})")
if int(win_nr) > 0:
vim.command(f"{win_nr}wincmd w")
vim.command("e!")
vim.command("wincmd p")
else:
# Mark buffer to be reloaded when entered
vim.command(f"checktime {buf.number}")
break
return True, None

except FileNotFoundError:
err_msg = "Error: `patch` command not found. Is it in your PATH?"
if not silent:
util.display_message(err_msg, error=True)
return False, err_msg
# Fall back to git apply if patch command is not in PATH
try:
git_cmd = ["git", "-C", project_root, "apply", "--whitespace=nowarn", "-"]
result = subprocess.run(
git_cmd,
input=diff_content,
encoding="utf-8",
errors="replace",
text=True,
check=False,
capture_output=True,
)
if result.returncode != 0:
err_msg = (result.stderr or result.stdout or "git apply failed").strip()
if not silent:
util.display_message(f"Git apply failed: {err_msg}", error=True)
return False, err_msg
except Exception as ge:
err_msg = f"Error: `patch` and `git` commands not available: {ge}"
if not silent:
util.display_message(err_msg, error=True)
return False, err_msg
except Exception as e:
err_msg = f"Error applying diff: {e}"
if not silent:
util.display_message(err_msg, error=True)
return False, err_msg

# Success
util.display_message("Successfully applied modified diff.", history=True)

# Parse diff to identify modified files for reloading
modified_files = set()
for line in diff_content.split("\n"):
if line.startswith("--- a/"):
path = line[len("--- a/") :].strip()
if path != "/dev/null":
modified_files.add(path)
elif line.startswith("+++ b/"):
path = line[len("+++ b/") :].strip()
if path != "/dev/null":
modified_files.add(path)

for relative_path in modified_files:
absolute_path = os.path.join(project_root, relative_path)

# Clean up .orig files potentially created by patch
orig_path = absolute_path + ".orig"
if os.path.exists(orig_path):
try:
os.remove(orig_path)
except Exception:
pass

normalized_target_path = os.path.realpath(absolute_path)
for buf in vim.buffers:
if buf.name and os.path.realpath(buf.name) == normalized_target_path:
# Reload buffer if visible
win_nr = vim.eval(f"bufwinnr({buf.number})")
if int(win_nr) > 0:
vim.command(f"{win_nr}wincmd w")
vim.command("e!")
vim.command("wincmd p")
else:
# Mark buffer to be reloaded when entered
vim.command(f"checktime {buf.number}")
break
return True, None


def apply_code(job_id=None):
"""
Expand Down
Loading
Loading