Skip to content

fix(diff): align inline highlight offsets with tab-expanded text#1709

Open
ahyangyi wants to merge 1 commit intoMoonshotAI:mainfrom
ahyangyi:head-fix-tab-diff
Open

fix(diff): align inline highlight offsets with tab-expanded text#1709
ahyangyi wants to merge 1 commit intoMoonshotAI:mainfrom
ahyangyi:head-fix-tab-diff

Conversation

@ahyangyi
Copy link
Copy Markdown

@ahyangyi ahyangyi commented Apr 1, 2026

Related Issue

N/A

Description

When modifying a file with tabs (\t), the inline diff highlights appear at wrong positions.

This is caused by the current order of operations:

  1. calculate the difference between two versions
  2. Syntax-highlight (which expands tabs)

This PR swaps 1) and 2) to avoid the problem.

(I did ran make gen-docs but it decided to produce lots of 1.28-1.29 updates, which is probably irrelevant here so I reverted those change)

Checklist

  • [*] I have read the CONTRIBUTING document.
  • [*] I have linked the related issue, if any.
  • [*] I have added tests that prove my fix is effective or that my feature works.
  • [*] I have run make gen-changelog to update the changelog.
  • [*] I have run make gen-docs to update the user documentation.

Open with Devin

Copilot AI review requested due to automatic review settings April 1, 2026 15:21
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes inline diff highlight misalignment on lines containing tabs by ensuring inline diff offsets are computed against tab-expanded (rendered) text rather than raw source text.

Changes:

  • Compute inline diff SequenceMatcher opcodes using Text.plain from syntax-highlighted output (tab-expanded) to keep offsets aligned with rendered Text.
  • Add a regression test covering inline diff highlights on tab-indented lines.
  • Add an Unreleased changelog entry documenting the fix.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/kimi_cli/utils/rich/diff_render.py Changes inline diff offset computation to compare highlighted (tab-expanded) plain text.
tests/utils/test_diff_render.py Adds regression test to ensure inline highlight spans align when tabs are present.
CHANGELOG.md Documents the tab-related inline diff highlight fix under Unreleased.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

# Compare the highlighted plain text so offsets match the Text objects
# (highlighting may expand tabs, so raw .code offsets would be wrong).
sm = SequenceMatcher(None, old_text.plain, new_text.plain)
if sm.ratio() < _INLINE_DIFF_MIN_RATIO:
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

In _apply_inline_diff, old_text/new_text are highlighted before the similarity threshold check. When sm.ratio() < _INLINE_DIFF_MIN_RATIO, those Text objects are discarded and the same lines will be highlighted again in _highlight_hunk’s second pass (since content stays None). Consider either (a) setting del_lines[j].content / add_lines[j].content even when skipping inline pairing (leaving is_inline_paired=False), or (b) doing the ratio check on tab-expanded raw strings first and only calling _highlight() when the pair will actually be inline-diffed, to avoid redundant highlighting work.

Suggested change
if sm.ratio() < _INLINE_DIFF_MIN_RATIO:
if sm.ratio() < _INLINE_DIFF_MIN_RATIO:
# Too dissimilar for inline word-level diff; still reuse the
# already-highlighted Text objects to avoid re-highlighting
# these lines in _highlight_hunk's second pass.
del_lines[j].content = old_text
add_lines[j].content = new_text

Copilot uses AI. Check for mistakes.
Comment on lines +187 to +190
(s.start, s.end) for s in deletes[0].content._spans if s.style == colors.del_hl
]
add_hl_spans = [
(s.start, s.end) for s in adds[0].content._spans if s.style == colors.add_hl
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

The new test reaches into Rich internals via Text._spans. That private attribute isn’t part of Rich’s stable API and may break across Rich versions. Prefer using the public Text.spans (or another public accessor) to inspect spans/styles for the assertion.

Suggested change
(s.start, s.end) for s in deletes[0].content._spans if s.style == colors.del_hl
]
add_hl_spans = [
(s.start, s.end) for s in adds[0].content._spans if s.style == colors.add_hl
(s.start, s.end) for s in deletes[0].content.spans if s.style == colors.del_hl
]
add_hl_spans = [
(s.start, s.end) for s in adds[0].content.spans if s.style == colors.add_hl

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12b5ec88a4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +158 to +162
old_text = _highlight(highlighter, del_lines[j].code)
new_text = _highlight(highlighter, add_lines[j].code)
# Compare the highlighted plain text so offsets match the Text objects
# (highlighting may expand tabs, so raw .code offsets would be wrong).
sm = SequenceMatcher(None, old_text.plain, new_text.plain)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid re-highlighting unpaired line pairs

_apply_inline_diff now highlights both lines before checking the similarity ratio, but when sm.ratio() < _INLINE_DIFF_MIN_RATIO it continues without storing those Text objects; _highlight_hunk then highlights the same lines again in its second pass (if dl.content is None). In replace blocks with many low-similarity pairs, this doubles syntax-highlighting work and can significantly slow rendering for large diffs.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants