mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-17 11:07:11 +08:00
fix(edit): preserve file indentation when oldText is unindented
The fuzzy fallback anchored every line with a leading [ \t]* that greedily consumed the file's indentation into the matched region, so a no-indent oldText dropped the edited line's indentation. Fold leading whitespace into the match only when oldText was itself indented on the first line, mirroring exact-substring semantics. Add a regression test.
This commit is contained in:
@@ -123,17 +123,24 @@ def fuzzy_find_text(content: str, old_text: str) -> FuzzyMatchResult:
|
|||||||
# with collapsed indentation (every untouched line got reformatted).
|
# with collapsed indentation (every untouched line got reformatted).
|
||||||
stripped = old_text.strip('\n')
|
stripped = old_text.strip('\n')
|
||||||
if stripped.strip():
|
if stripped.strip():
|
||||||
|
source_lines = stripped.split('\n')
|
||||||
line_patterns = []
|
line_patterns = []
|
||||||
for line in stripped.split('\n'):
|
for i, line in enumerate(source_lines):
|
||||||
tokens = line.split()
|
tokens = line.split()
|
||||||
if tokens:
|
if not tokens:
|
||||||
# Tolerate flexible indentation and any run of blanks between
|
|
||||||
# tokens, matching the leniency of the old normalized compare.
|
|
||||||
line_patterns.append(
|
|
||||||
r'[ \t]*' + r'[ \t]+'.join(re.escape(tok) for tok in tokens) + r'[ \t]*'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
line_patterns.append(r'[ \t]*')
|
line_patterns.append(r'[ \t]*')
|
||||||
|
continue
|
||||||
|
# Tolerate any run of blanks between tokens.
|
||||||
|
core = r'[ \t]+'.join(re.escape(tok) for tok in tokens)
|
||||||
|
# First-line leading whitespace is folded into the match only when
|
||||||
|
# old_text itself was indented here; otherwise it stays OUTSIDE the
|
||||||
|
# match so a no-indent old_text preserves (does not swallow and drop)
|
||||||
|
# the file's existing indentation -- mirroring an exact substring
|
||||||
|
# match. Inner lines always tolerate indentation: it sits inside the
|
||||||
|
# matched region and is re-supplied by new_text.
|
||||||
|
if i > 0 or line[:1] in (' ', '\t'):
|
||||||
|
core = r'[ \t]*' + core
|
||||||
|
line_patterns.append(core + r'[ \t]*')
|
||||||
pattern = '\n'.join(line_patterns)
|
pattern = '\n'.join(line_patterns)
|
||||||
match = re.search(pattern, content)
|
match = re.search(pattern, content)
|
||||||
if match:
|
if match:
|
||||||
|
|||||||
@@ -80,6 +80,24 @@ class TestEditFuzzyPreservesWhitespace(unittest.TestCase):
|
|||||||
"def foo():\n x = 9\n y = 8\n return x + y\n",
|
"def foo():\n x = 9\n y = 8\n return x + y\n",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_fuzzy_match_with_unindented_oldtext_preserves_file_indent(self):
|
||||||
|
# oldText has NO leading indentation (and loose spacing around '='), so
|
||||||
|
# the exact match fails and the fuzzy path runs against an indented file
|
||||||
|
# line. The file's indentation must be preserved -- kept OUTSIDE the
|
||||||
|
# replaced region -- instead of being swallowed into the match and
|
||||||
|
# dropped (which would break the file's indentation). newText is
|
||||||
|
# likewise unindented, mirroring exact-substring replacement.
|
||||||
|
result = self.tool.execute({
|
||||||
|
"path": self.path,
|
||||||
|
"oldText": "x = 1",
|
||||||
|
"newText": "x = 100",
|
||||||
|
})
|
||||||
|
self.assertEqual(result.status, "success", result.result)
|
||||||
|
self.assertEqual(
|
||||||
|
self._read(),
|
||||||
|
"def foo():\n x = 100\n y = 2\n return x + y\n",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user