Skip to content

feat(formater): restrict formatting to only the changed range of a file. #4604

Open
micuintus wants to merge 1 commit intoanomalyco:devfrom
micuintus:dev
Open

feat(formater): restrict formatting to only the changed range of a file. #4604
micuintus wants to merge 1 commit intoanomalyco:devfrom
micuintus:dev

Conversation

@micuintus
Copy link

@micuintus micuintus commented Nov 21, 2025

Restrict formatting only to the edited line range for clang-format
When using the Edit tool, clang-format now only formats the specific lines
that were changed, rather than reformatting the entire file. This prevents
unrelated formatting changes from cluttering the diff.

Closes #4603

@github-actions github-actions bot force-pushed the dev branch 2 times, most recently from 917250b to f4593c6 Compare November 22, 2025 05:02
@micuintus micuintus changed the title feat(editing): Add skipAutoFormatting Parameter to Edit Tools feat(formater): restrict formatting to only the changed range of a file. Nov 22, 2025
@github-actions github-actions bot force-pushed the dev branch 2 times, most recently from df8bdf9 to 0dd5039 Compare November 22, 2025 18:21
@micuintus
Copy link
Author

@rekram1-node What is the state on this? Are you considering these suggestions?

@rekram1-node
Copy link
Collaborator

rekram1-node commented Nov 24, 2025

Yeah I see what you are trying to solve there. I would need some time to look into it more to review but I will say this looks very vibe coded so I won't just auto merge without a throughout review

@micuintus
Copy link
Author

@rekram1-node

I would need some time to look into it more to review but I will say this looks very vibe coded so I won't just auto merge without a throughout review

Yes, I did use opencode for this PR ;). I am obviously new to this codebase and not a particular TypeScript expert. I had opencode run through several iterations with several models (Sonnet 4.5, GLM 4.6, Gemini 3, CODEX) though, letting the models review and discuss their results; asked them to asses the solution and the code quality, find bugs, make suggestions for simplification and improvement etc.

But yeah, this code needs to be reviewed, of course :)

I started with the line-number-based range API of clang-format and actually left it as a separate commit (the rest is squashed into the subsequent commit). Then I checked which interface Qt Creator uses: char/byte range API for subfile formatting --- and as it enables us to apply the same logic with Prettier I thought this makes sense as a "source of truth" format.

Aside from my use case (legacy code bases with humongous unformatted areas): Auto-formatting only the affected part of the file that opencode had touched makes a lot of sense to me; formatting the rest of the file should be a different task / step anyways IMO.

Having such a system in place would not only be very beneficial for my workflow, I believe it would make opencode a better tool for everyone.

Feel feel to request changes or I'd also be happy to hand this branch over.

@micuintus
Copy link
Author

@rekram1-node Is there any way I can increase your confidence with that topic?

@micuintus
Copy link
Author

@rekram1-node Is there any way I can increase your confidence with that topic?

@rekram1-node bump :)

@thdxr
Copy link
Contributor

thdxr commented Dec 3, 2025

this is good we're gonna merge it, aiden will follow up

@rekram1-node
Copy link
Collaborator

@micuintus can you update your code to follow some of our style guidelines a bit better?
https://github.com/sst/opencode/blob/dev/STYLE_GUIDE.md

Ex: change changedRanges to just ranges, stuff like that

@micuintus
Copy link
Author

micuintus commented Dec 5, 2025

@micuintus can you update your code to follow some of our style guidelines a bit better? https://github.com/sst/opencode/blob/dev/STYLE_GUIDE.md

Ex: change changedRanges to just ranges, stuff like that

@rekram1-node Sorry for the delay, I only just saw this. -> Done: 2d47783

Anything else?

@micuintus micuintus force-pushed the dev branch 4 times, most recently from ceebe59 to 50bfdd6 Compare February 9, 2026 17:38
@micuintus
Copy link
Author

@thdxr @rekram1-node ? 🥺

@rekram1-node
Copy link
Collaborator

Oof this fell under radar again, u can always reach out via dm

@rekram1-node
Copy link
Collaborator

Ran a review with an llm to check over it, this could be bs:

Yes — here is a concrete, numeric proof using the code as written.
Where the mismatch happens:

  • calculateRanges diffs normalized content (\r\n → \n) and then accumulates charOffset and byteOffset from change.value lengths.
  • Those offsets are later used as byte offsets for clang-format against the raw file content.
  • That’s fine for LF, but wrong for CRLF because every \r\n is 2 bytes in the raw file and only 1 byte in the normalized diff.
    Proof by example (CRLF file, insert a line):
    Raw contents (CRLF):
    oldContent = "a\r\nb\r\nc\r\n"
    newContent = "a\r\nb\r\nX\r\nc\r\n"
    Normalized for diff (\r\n → \n):
    oldNorm = "a\nb\nc\n"
    newNorm = "a\nb\nX\nc\n"
    When the diff sees the added line "X\n", the offsets just before it are computed from newNorm:
  • charOffset before "X\n" = length of "a\nb\n" = 4
  • byteOffset before "X\n" = Buffer.byteLength("a\nb\n") = 4
    So the range is produced with _byteOffset = 4.
    But in the raw newContent, the true byte offset before "X\r\n" is:
  • "a\r\n" = 3 bytes
  • "b\r\n" = 3 bytes
  • total = 6 bytes
    So the correct byte offset is 6, not 4. The code undercounts by the number of preceding CRLFs (2 here). That means clang-format --offset=4 will start 2 bytes earlier than intended (inside the CRLF sequence or the previous line), and the formatted range is wrong or skipped.
    This is exactly the mismatch caused by:
  • normalizing in calculateRanges (diffLines(oldContent.replace(/\r\n/g, "\n"), ...))
  • but later using the computed _byteOffset against the raw file in buildRangeCommand for clang-format.
    That’s the bug. The fix is to compute offsets against the raw newContent (no CRLF normalization) or build a mapping from normalized indices back to raw indices when constructing DiffRange.

@micuintus
Copy link
Author

@rekram1-node Thank you. It was not BS, valid points although not critical.
Addressed it and straightened up the PR

@micuintus
Copy link
Author

@rekram1-node Private DM... how would I send that? :)

@micuintus micuintus force-pushed the dev branch 5 times, most recently from 8ffe542 to eedde6b Compare February 18, 2026 16:14
Add range-restricted formatting to improve performance by only formatting
the portions of files that actually changed.

Changes:
- Add diff-range.ts module to calculate character and byte offset ranges
  from text diffs, accounting for CRLF/LF normalization and Unicode
- Add ranges field to File.Event.Edited event schema
- Add optional buildRangeCommand method to formatter Info interface
- Implement range commands for prettier, clang-format, and ruff
- Calculate and publish ranges in Edit tool for new files and edits
@micuintus
Copy link
Author

@rekram1-node
Hey Aiden,
Could you review again, please?
Would be really nice to get this included.

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.

[FEATURE]: Restrict formatting only to the edited range for clang-format (and Prettier)

4 participants

Comments