Skip to content

Add support for format_on_save only changed lines#49964

Open
G36maid wants to merge 4 commits into
zed-industries:mainfrom
G36maid:feat/format-on-save-modified-lines
Open

Add support for format_on_save only changed lines#49964
G36maid wants to merge 4 commits into
zed-industries:mainfrom
G36maid:feat/format-on-save-modified-lines

Conversation

@G36maid
Copy link
Copy Markdown
Contributor

@G36maid G36maid commented Feb 24, 2026

Summary

Introduces format_on_save variants that format only modified lines, limiting formatting to lines changed since the last commit. This prevents massive diffs when editing legacy codebases. Aligns with VS Code's editor.formatOnSaveMode naming:

  • "modifications" — formats only git-diffed lines. Requires source control; skips formatting if no diff is available.
  • "modifications_if_available" — formats only git-diffed lines, falling back to full-file formatting for untracked files, when source control is unavailable, or when the LSP does not support range formatting.

Also supports importing equivalent VS Code settings (formatOnSaveMode).

This PR uses the range-based whitespace/newline infrastructure from the dependency PR above.

Changes

New settings, modified ranges computation, VS Code import

New settings (language.rs, default.json, all-settings.md)

Two new FormatOnSave variants: Modifications and ModificationsIfAvailable.

Modified ranges computation (items.rs)

  • compute_format_decision() — reads format_on_save setting across all buffers, determines whether to use ranged formatting (Ranges), full formatting (Full), or skip (Skip).
  • compute_modified_ranges() — extracts modified line ranges from git diff hunks via BufferDiffSnapshot.
  • is_empty_range() — helper to detect deletion-only hunks that produce no formatable content.

The save() method calls compute_format_decision() and dispatches accordingly.

Modifications mode in lsp_store.rs

  • Adds FormatOnSave::Modifications | FormatOnSave::ModificationsIfAvailable to the formatter selection match arm.
  • ModificationsIfAvailable falls back to full-file formatting when ranged formatting produces no results.

VS Code settings import (vscode_import.rs, settings_store.rs)

Imports editor.formatOnSaveMode mapping:

  • "modifications"FormatOnSave::Modifications
  • "modificationsIfAvailable"FormatOnSave::ModificationsIfAvailable
  • "file"FormatOnSave::On

Tests

  • test_modifications_format_on_save — basic modifications mode formatting with dirty buffer
  • test_modifications_format_no_changes — clean buffer triggers no formatting
  • test_modifications_format_lsp_no_range_support — LSP without range formatting skips entirely for Modifications
  • test_modifications_format_lsp_returns_empty_edits — empty edits handled gracefully
  • test_modifications_format_multiple_hunks — two non-adjacent edits produce two separate range formatting requests

Self-Review Checklist:

  • I've reviewed my own diff for quality, security, and reliability
  • Unsafe blocks (if any) have justifying comments
  • The content is consistent with the UI/UX checklist
  • Tests cover the new/changed behavior
  • Performance impact has been considered and is acceptable

Closes #16509
Depends on #53942

Release Notes:

  • Added modifications and modifications_if_available options to format_on_save, which format only git-changed lines instead of the entire file
  • When using modifications mode, remove_trailing_whitespace_on_save and ensure_final_newline_on_save are also scoped to changed lines, preventing unwanted diff jitter in legacy codebases
  • Added support for importing VS Code's editor.formatOnSaveMode setting

@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Feb 24, 2026
@maxdeviant maxdeviant changed the title feat: format_on_save save only changed lines Add support for format_on_save only changed lines Feb 24, 2026
@G36maid G36maid force-pushed the feat/format-on-save-modified-lines branch from c0cb755 to 4e49c0f Compare February 24, 2026 12:59
@G36maid G36maid marked this pull request as ready for review February 24, 2026 13:14
@G36maid G36maid force-pushed the feat/format-on-save-modified-lines branch 2 times, most recently from 4e49c0f to 35d18a5 Compare February 24, 2026 15:34
@G36maid
Copy link
Copy Markdown
Contributor Author

G36maid commented Feb 24, 2026

Perhaps there is a better name for the 'modifiedlines' option.

@UnknownDK
Copy link
Copy Markdown

UnknownDK commented Feb 24, 2026

I get this, when trying to use it with ty for Python. Maybe this should be handled, instead of an error?

2026-02-24T19:48:05+01:00 ERROR [project::lsp_store] Formatting failed: Failed to format ranges via language server: Unknown request: textDocument/rangeFormatting
2026-02-24T19:48:05+01:00 ERROR [crates/editor/src/editor.rs:19222] Failed to format ranges via language server

Caused by:
    Unknown request: textDocument/rangeFormatting

When trying with Ruff, it just formats like normal without throwing any errors, so that also doesnt work :/

Seems to work for C++ though.

@UnknownDK
Copy link
Copy Markdown

Perhaps there is a better name for the 'modifiedlines' option.

Maybe take inspiration from VSCode

They have a mode with fallback and one without called modificationsIfAvailable and modifications. That sounds pretty good

@G36maid
Copy link
Copy Markdown
Contributor Author

G36maid commented Feb 25, 2026

🧪 Testing: LSP Range Formatting Support Status

Since textDocument/rangeFormatting implementation varies wildly across the ecosystem.

let's track how different Language Servers and Formatters behave with format_on_save

🧪 checklist

Legend:

  • Works perfectly: Only modifies changed lines.
  • [~] Graceful Skip: Doesn't support ranges, safely skips without errors.
  • [F] Full-file Fallback: Ignores the ranges and formats the whole file anyway.
  • [!] Errors out: Throws an Unknown request or breaks the save pipeline.

📦 Built-in Languages

  • C / C++
    • clangd
  • Python
    • Ruff
    • ty
    • Pyright
  • Rust
    • rust-analyzer / rustfmt (Note: requires nightly/unstable config)
  • TypeScript / JavaScript
    • vtsls
    • typescript-language-server
    • Prettier (External)
    • Biome
  • Go
    • gopls (Expected to reject/fallback as Go prefers strict full-file formatting)
  • Web (JSON / HTML / CSS)
    • json-language-server
    • vscode-html-language-server

🧩 Popular Extensions

  • Vue (volar)
  • Svelte (svelte-language-server)
  • Ruby (ruby-lsp / rubocop)
  • PHP (intelephense / phpactor)
  • Java (jdtls)
  • C# (omnisharp / roslyn)
  • Elixir (elixir-ls / lexical)
  • Zig (zls)
  • Nix (nil / nixd)
  • Gleam (gleam)

How to test:

  1. Pull the branch: gh pr checkout 49964
  2. Set "format_on_save" in your settings.json`
  3. Edit a file, save, and observe the behavior (and check the Zed logs for errors!)

@UnknownDK
Copy link
Copy Markdown

UnknownDK commented Feb 25, 2026

It seems VSCode has some workaround for languages that don't support range formatting. It works for Ty and Ruff for them...

Hmm, doesnt seem like it https://github.com/microsoft/vscode/blob/f8f3e01afce0f5aea3acc6b87befe91a27aafa3f/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts#L254C13-L254C14

@G36maid G36maid force-pushed the feat/format-on-save-modified-lines branch from 488904e to 3f27878 Compare February 25, 2026 12:01
@G36maid
Copy link
Copy Markdown
Contributor Author

G36maid commented Feb 25, 2026

Perhaps there is a better name for the 'modifiedlines' option.

Maybe take inspiration from VSCode

They have a mode with fallback and one without called modificationsIfAvailable and modifications. That sounds pretty good

@UnknownDK That is a great suggestion!
It also elegantly solves the fallback dilemma.

I will finish the follow-up commits.

@G36maid
Copy link
Copy Markdown
Contributor Author

G36maid commented Feb 25, 2026

I get this, when trying to use it with ty for Python. Maybe this should be handled, instead of an error?

2026-02-24T19:48:05+01:00 ERROR [project::lsp_store] Formatting failed: Failed to format ranges via language server: Unknown request: textDocument/rangeFormatting
2026-02-24T19:48:05+01:00 ERROR [crates/editor/src/editor.rs:19222] Failed to format ranges via language server

Caused by:
    Unknown request: textDocument/rangeFormatting

When trying with Ruff, it just formats like normal without throwing any errors, so that also doesnt work :/

Seems to work for C++ though.

Maybe because ty isn't a formater?

According to Astral's official docs, ty is strictly a type checker, while Ruff is the formatter.

@UnknownDK
Copy link
Copy Markdown

Maybe because ty isn't a formater?

According to Astral's official docs, ty is strictly a type checker, while Ruff is the formatter.

Makes sense. My settings might be weird. I have however never had any warnings before for ty.

@G36maid G36maid force-pushed the feat/format-on-save-modified-lines branch 2 times, most recently from e307b3f to 3259f88 Compare March 3, 2026 21:58
@SomeoneToIgnore SomeoneToIgnore self-assigned this Mar 24, 2026
@G36maid G36maid force-pushed the feat/format-on-save-modified-lines branch 2 times, most recently from 166469e to 140371b Compare March 30, 2026 00:56
@G36maid G36maid force-pushed the feat/format-on-save-modified-lines branch from 1d50c57 to 25fcdd4 Compare April 7, 2026 06:33
@G36maid
Copy link
Copy Markdown
Contributor Author

G36maid commented Apr 8, 2026

Hi @SomeoneToIgnore, just checking in on this PR. Would a demo video help with the review?
Also this isn't currently on the Zed Guild - The Board — should I hold off, or is it still in scope?

I will keep resolve the conflict if needed.

@SomeoneToIgnore
Copy link
Copy Markdown
Contributor

SomeoneToIgnore commented Apr 8, 2026

Hey, sorry for the delay, I am just buried under all the rest of the PRs and trying to deal with the smaller ones first.
I am fine with the current state of the PR to start reviewing, just in the process of bracing myself for +1000 lines change and a relatively complex logic walkthrough — I hope next week would be the one when I finally come up with the review.

To note, I disagree with #49964 (comment) as the only related detail is the formatting, the functionality is rather different, IMO, as this one includes VCS interactions.

Copy link
Copy Markdown
Contributor

@SomeoneToIgnore SomeoneToIgnore left a comment

Choose a reason for hiding this comment

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

FWIW I've even started with the PR at some point, if you're so eager to get some review...

Comment thread crates/settings_content/src/language.rs Outdated
@G36maid
Copy link
Copy Markdown
Contributor Author

G36maid commented Apr 8, 2026

Hey, sorry for the delay, I am just buried under all the rest of the PRs and trying to deal with a smaller ones first. I am fine with the current state of the PR to start reviewing, just in the process of bracing myself for +1000 lines change and a relatively complex logic walkthrough — I hope next week would be the one when I finally come up with the review.

To note, I disagree with #49964 (comment) as the only related detail is the formatting, the functionality is rather different, IMO, as this one includes VCS interactions.

(Re-posting for context — accidentally deleted the original)

here are related issues and PRs

  • #25796 — FormatSelections command formats entire document instead of selection (Open)
  • #53178 — Gate Format Selections on whether the active formatter can actually format ranges

@G36maid
Copy link
Copy Markdown
Contributor Author

G36maid commented Apr 8, 2026

Got it, thanks for the update.

@SomeoneToIgnore
Copy link
Copy Markdown
Contributor

Heads up: test_sidebar_invariants fix is in main if you want to rebase and pick it up some day, no worries about it now.

Copy link
Copy Markdown
Contributor

@SomeoneToIgnore SomeoneToIgnore left a comment

Choose a reason for hiding this comment

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

Overall, looks rather nice, thank you for coming up with this.

One major question I have is whether we can split that into two PRs instead: one for the whitespaces + ranges and another for the git diff-related logic.

Comment thread crates/settings_content/src/language.rs Outdated
Comment thread crates/migrator/src/migrations/m_2025_10_02/settings.rs
Comment thread crates/project/src/lsp_store.rs Outdated
Comment thread crates/language/src/buffer.rs Outdated
Comment thread crates/editor/src/items.rs Outdated
Comment thread crates/editor/src/items.rs Outdated
Comment thread crates/editor/src/items.rs Outdated
Comment thread crates/editor/src/items.rs Outdated
@G36maid G36maid force-pushed the feat/format-on-save-modified-lines branch from 7d9dcc2 to 41b4d7b Compare April 14, 2026 22:56
@G36maid G36maid force-pushed the feat/format-on-save-modified-lines branch 2 times, most recently from 5718b61 to 9c7098a Compare April 15, 2026 00:28
@G36maid
Copy link
Copy Markdown
Contributor Author

G36maid commented Apr 15, 2026

Just split for the whitespaces + ranges into #53942

@G36maid G36maid force-pushed the feat/format-on-save-modified-lines branch 2 times, most recently from f2dc9ea to 9b57ecd Compare April 16, 2026 10:28
Copy link
Copy Markdown
Contributor

@SomeoneToIgnore SomeoneToIgnore left a comment

Choose a reason for hiding this comment

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

Thank you, this looks much cleaner and now I could focus on the impl details better.

Had left a few comments, this seems to need a good pass on lsp_store.rs new code at least.

Comment thread crates/migrator/src/migrations/m_2025_10_02/settings.rs
Comment thread crates/editor/src/editor_tests.rs Outdated
Comment thread crates/editor/src/items.rs Outdated
Comment thread crates/editor/src/items.rs Outdated
Comment thread crates/editor/src/items.rs Outdated
Comment thread crates/project/src/lsp_store.rs
@G36maid G36maid force-pushed the feat/format-on-save-modified-lines branch 3 times, most recently from 46339e6 to 5368336 Compare April 29, 2026 17:33
@SomeoneToIgnore SomeoneToIgnore removed their assignment May 7, 2026
@G36maid G36maid force-pushed the feat/format-on-save-modified-lines branch from cfc6a89 to 45e41d1 Compare May 7, 2026 06:43
@G36maid G36maid force-pushed the feat/format-on-save-modified-lines branch from 7813957 to dbcbf9f Compare May 7, 2026 09:21
@SomeoneToIgnore SomeoneToIgnore added the area:editor Feedback for code editing, formatting, editor iterations, etc label May 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:editor Feedback for code editing, formatting, editor iterations, etc cla-signed The user has signed the Contributor License Agreement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

format_on_save: save only changed lines

3 participants