diff --git a/codemcp/code_command.py b/codemcp/code_command.py index 97e9a49..6789bbb 100644 --- a/codemcp/code_command.py +++ b/codemcp/code_command.py @@ -3,7 +3,7 @@ import logging import os import subprocess -from typing import Any, Dict, List, Optional, cast +from typing import Any, Dict, List, Optional, Tuple, cast import tomli @@ -15,6 +15,7 @@ "get_command_from_config", "check_for_changes", "run_code_command", + "run_formatter_without_commit", ] @@ -215,3 +216,41 @@ async def run_code_command( error_msg = f"Error during {command_name}: {e}" logging.error(error_msg) return f"Error: {error_msg}" + + +async def run_formatter_without_commit(file_path: str) -> Tuple[bool, str]: + """Run the formatter on a specific file without performing pre/post commit operations. + + Args: + file_path: Absolute path to the file to format + + Returns: + A tuple containing (success_status, message) + + Raises: + Propagates any unexpected errors during formatting + """ + # Get the project directory (repository root) + project_dir = await get_repository_root(file_path) + + # Get the format command from config - this is the only expected failure mode + format_command = get_command_from_config(project_dir, "format") + if not format_command: + return False, "No format command configured in codemcp.toml" + + # Use relative path from project_dir for the formatting command + rel_path = os.path.relpath(file_path, project_dir) + + # Run the formatter with the specific file path + command = format_command + [rel_path] + result = await run_command( + command, + cwd=project_dir, + check=True, + capture_output=True, + text=True, + ) + + # If we get here, the formatter ran successfully + truncated_stdout = truncate_output_content(result.stdout, prefer_end=True) + return True, f"File formatted successfully:\n{truncated_stdout}" diff --git a/codemcp/tools/edit_file.py b/codemcp/tools/edit_file.py index 454b74c..081947a 100644 --- a/codemcp/tools/edit_file.py +++ b/codemcp/tools/edit_file.py @@ -9,6 +9,7 @@ from difflib import SequenceMatcher from typing import Any, Dict, List, Tuple +from ..code_command import run_formatter_without_commit from ..common import get_edit_snippet from ..file_utils import ( async_open_text, @@ -773,6 +774,22 @@ async def edit_file_content( if read_file_timestamps is not None: read_file_timestamps[full_file_path] = os.stat(full_file_path).st_mtime + # Try to run the formatter on the file + format_message = "" + formatter_success, formatter_output = await run_formatter_without_commit( + full_file_path + ) + if formatter_success: + logger.info(f"Auto-formatted {full_file_path}") + if formatter_output.strip(): + format_message = "\nAuto-formatted the file" + else: + # Only log warning if there was actually a format command configured but it failed + if not "No format command configured" in formatter_output: + logger.warning( + f"Failed to auto-format {full_file_path}: {formatter_output}" + ) + # Generate a snippet of the edited file to show in the response snippet = get_edit_snippet(content, old_string, new_string) @@ -787,4 +804,4 @@ async def edit_file_content( else: git_message = f"\n\nFailed to commit changes to git: {message}" - return f"Successfully edited {full_file_path}\n\nHere's a snippet of the edited file:\n{snippet}{git_message}" + return f"Successfully edited {full_file_path}\n\nHere's a snippet of the edited file:\n{snippet}{format_message}{git_message}" diff --git a/codemcp/tools/write_file.py b/codemcp/tools/write_file.py index 5ac56fe..3efa8f4 100644 --- a/codemcp/tools/write_file.py +++ b/codemcp/tools/write_file.py @@ -1,7 +1,9 @@ #!/usr/bin/env python3 +import logging import os +from ..code_command import run_formatter_without_commit from ..file_utils import ( check_file_path_and_permissions, check_git_tracking_for_existing_file, @@ -61,6 +63,18 @@ async def write_file_content( # Write the content with UTF-8 encoding and proper line endings await write_text_content(file_path, content, "utf-8", line_endings) + # Try to run the formatter on the file + format_message = "" + formatter_success, formatter_output = await run_formatter_without_commit(file_path) + if formatter_success: + logging.info(f"Auto-formatted {file_path}") + if formatter_output.strip(): + format_message = f"\nAuto-formatted the file" + else: + # Only log warning if there was actually a format command configured but it failed + if not "No format command configured" in formatter_output: + logging.warning(f"Failed to auto-format {file_path}: {formatter_output}") + # Commit the changes git_message = "" success, message = await commit_changes(file_path, description, chat_id) @@ -69,4 +83,4 @@ async def write_file_content( else: git_message = f"\nFailed to commit changes to git: {message}" - return f"Successfully wrote to {file_path}{git_message}" + return f"Successfully wrote to {file_path}{format_message}{git_message}"