Skip to content

fix: preserve indentation when applying code edits to Python files#11289

Open
mvanhorn wants to merge 2 commits intocontinuedev:mainfrom
mvanhorn:fix/python-indentation-edit
Open

fix: preserve indentation when applying code edits to Python files#11289
mvanhorn wants to merge 2 commits intocontinuedev:mainfrom
mvanhorn:fix/python-indentation-edit

Conversation

@mvanhorn
Copy link
Contributor

@mvanhorn mvanhorn commented Mar 11, 2026

Summary

  • Fixes indentation corruption when the code edit tool uses fallback match strategies (trimmedMatch, whitespaceIgnoredMatch, etc.) to apply edits
  • When an LLM provides search/replace strings with different indentation than the target code (common with Python), the replacement text is now re-indented to match the file's actual indentation level

How it works

adjustReplacementIndentation() compares the indentation of the file line at the match position against the leading indent of the LLM-provided old_string. If they differ:

  • First line: strips the old indent prefix (the file content before the splice point already provides correct indentation)
  • Subsequent lines: replaces the old indent prefix with the matched indent, preserving relative inner indentation
  • Exact matches: passed through unchanged - no indentation adjustment needed

This handles tab/space mismatches, completely unindented LLM output, and deeply nested code blocks.

Test plan

  • Unindented old_string matching indented file content gets correct indentation
  • Multi-level nesting preserves relative inner indentation
  • Tab vs space mismatch resolved correctly
  • Exact matches unchanged (no regression)
  • whitespaceIgnoredMatch with differing internal whitespace works correctly
  • replaceAll mode applies indentation adjustment per-match

Fixes #11282

🤖 Generated with Claude Code

@mvanhorn mvanhorn requested a review from a team as a code owner March 11, 2026 06:16
@mvanhorn mvanhorn requested review from Patrick-Erichsen and removed request for a team March 11, 2026 06:16
@mvanhorn
Copy link
Contributor Author

I have read the CLA Document and I hereby sign the CLA

@dosubot dosubot bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Mar 11, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="core/edit/searchAndReplace/performReplace.ts">

<violation number="1" location="core/edit/searchAndReplace/performReplace.ts:58">
P2: First replacement line can remain unindented when `oldIndent` is non-empty, because fallback indentation adjustment only runs for `oldIndent === ""`.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

mvanhorn and others added 2 commits March 12, 2026 20:47
When fallback matching strategies (trimmedMatch, whitespaceIgnoredMatch)
find a match with different indentation than the LLM-provided old_string,
the replacement new_string now has its indentation adjusted to match the
actual indentation level in the file. This fixes incorrect indentation in
Python code edits where the LLM provides unindented search/replace strings
that match indented code blocks.

Fixes continuedev#11282

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…eplacements

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mvanhorn mvanhorn force-pushed the fix/python-indentation-edit branch from 70b6b17 to 1cb68a5 Compare March 13, 2026 03:58
@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Mar 13, 2026
@mvanhorn
Copy link
Contributor Author

Thanks to @luzhihaoTestingLab for reporting #11282 and to @cubic-dev-ai for catching the first-line indentation bug in review.

What was wrong with the original approach (commit 3d257cd):

The first version used getLeadingIndent(matchedText) to determine the file's indentation - extracting it from the matched substring itself. It then applied indentation uniformly to all lines, including the first. The problem is that when the splice happens at startIndex, the file content before startIndex already provides the leading whitespace for the first line. So the first line would get double-indented. The fallback if (index === 0 && oldIndent === "" ...) only helped when the LLM sent completely unindented text - if oldIndent was non-empty (e.g., spaces vs tabs mismatch), the first line was left wrong.

What the new approach does (commit 1cb68a5):

Replaced getLeadingIndent(matchedText) with getLineIndentAtPosition(fileContent, match.startIndex), which reads indentation from the full file line at the match position. The first line now strips the old indent instead of adding the new one (since the splice point inherits the file's leading whitespace). Subsequent lines replace oldIndent with matchedIndent, preserving relative nesting. Handles all cases - unindented LLM output, tab/space mismatches, and multi-level nesting.

Added 5 test cases covering: basic indent adjustment, relative inner indentation preservation, tab vs space mismatch, exact match passthrough, and whitespace-ignored matching.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

The code modification tool has incorrect indentation on Python

1 participant