Repository navigation
feat(formatting): wire up textDocument/formatting and rangeFormatting - #441
Conversation
The formatting feature implementation (clang-format integration) already existed but was not exposed through LSP. This connects it: - Add Format kind to stateless worker dispatch - Add forward_format to Compiler (lightweight — no deps needed) - Register document formatting and range formatting handlers - Advertise capabilities in Initialize response - Add integration tests for format, range format, and no-op format Style lookup uses clang::format::getStyle which walks parent directories for .clang-format, matching clangd's behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use LocalSourceRange::valid() instead of hardcoded sentinel check
- Fix log level (INFO → WARN) and wording for format failures
- Add scoped_pause to formatting handlers (matches completion/sighelp)
- Unify handler indentation style
- Unify serde_raw{"null"} return for worker communication failure
- Verify edit content in integration test (apply_edits → FORMATTED)
- Add capability assertions for formatting in test_capabilities
- Expand unit tests: range format, idempotent, include sort
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR adds LSP document formatting support to the language server by introducing a new ChangesDocument Formatting Support
Sequence DiagramsequenceDiagram
participant Client
participant LSPService as LSP Service
participant Compiler
participant Worker as Stateless Worker
participant Formatter as Formatter Engine
Client->>LSPService: textDocument/documentFormatting or rangeFormatting
LSPService->>LSPService: Locate session
LSPService->>LSPService: Pause indexer
LSPService->>Compiler: forward_format(session, optional range)
Compiler->>Compiler: Map LSP Range to text offsets (PositionMapper)
Compiler->>Compiler: Build BuildParams with BuildKind::Format
Compiler->>Worker: send_stateless(params)
Worker->>Worker: Determine format_range (full doc or specified)
Worker->>Formatter: document_format(content, range)
Formatter->>Formatter: Apply clang-format
Formatter-->>Worker: TextEdit list
Worker-->>Compiler: result_json (serialized edits)
Compiler-->>LSPService: RawValue (edits)
LSPService-->>Client: TextEdit[] or null
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
tests/integration/features/test_formatting.py (1)
53-60: ⚡ Quick winRange-format integration test should assert correctness, not just non-empty edits.
Current assertions can pass even if edits touch the wrong span/content. Apply edits and verify expected scoped outcome.
Suggested test hardening
@@ edits = await client.format_range( uri, Range(start=Position(line=1, character=0), end=Position(line=2, character=0)), ) assert edits is not None assert len(edits) > 0 + result = apply_edits(UNFORMATTED, edits) + assert result != UNFORMATTED + # Outside-range lines should stay unchanged. + assert result.splitlines()[0] == UNFORMATTED.splitlines()[0] + assert result.splitlines()[2] == UNFORMATTED.splitlines()[2]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/integration/features/test_formatting.py` around lines 53 - 60, Replace the weak assertions for client.format_range with a deterministic check: apply the returned edits from client.format_range (Range and Position constructs) to the original document text and assert the resulting text for the formatted span equals the expected string (or that the whole document equals the expected document after edits). Locate the call to client.format_range and the variables edits, Range, Position in tests/integration/features/test_formatting.py, programmatically apply edits to the source buffer, and replace the current asserts (assert edits is not None; assert len(edits) > 0) with assertions that compare the post-apply content to the expected formatted output for the scoped range.tests/unit/feature/formatting_tests.cpp (1)
15-24: ⚡ Quick winRange-format assertion is brittle due to edit-count coupling.
EXPECT_LE(range_edits.size(), full_edits.size())can fail even when behavior is correct, because replacement chunking is formatter-implementation-dependent. Prefer asserting semantic correctness (applied output / scoped impact) instead of comparing edit counts.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/feature/formatting_tests.cpp` around lines 15 - 24, The test RangeFormat should stop comparing edit counts and instead verify semantic correctness: call feature::document_format("main.cpp", code, range) to get range_edits and apply them to the original code (using the same edit-application logic your tests use) then assert the resulting string has the expected formatted segment for the scoped region (e.g., the "int y = 2 ;" line normalized) and that characters outside range remain unchanged; also assert range_edits only produce edits whose offsets lie within LocalSourceRange.begin..end to ensure scoped impact rather than relying on edit count comparison with full_edits.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@tests/integration/features/test_formatting.py`:
- Around line 53-60: Replace the weak assertions for client.format_range with a
deterministic check: apply the returned edits from client.format_range (Range
and Position constructs) to the original document text and assert the resulting
text for the formatted span equals the expected string (or that the whole
document equals the expected document after edits). Locate the call to
client.format_range and the variables edits, Range, Position in
tests/integration/features/test_formatting.py, programmatically apply edits to
the source buffer, and replace the current asserts (assert edits is not None;
assert len(edits) > 0) with assertions that compare the post-apply content to
the expected formatted output for the scoped range.
In `@tests/unit/feature/formatting_tests.cpp`:
- Around line 15-24: The test RangeFormat should stop comparing edit counts and
instead verify semantic correctness: call feature::document_format("main.cpp",
code, range) to get range_edits and apply them to the original code (using the
same edit-application logic your tests use) then assert the resulting string has
the expected formatted segment for the scoped region (e.g., the "int y = 2 ;"
line normalized) and that characters outside range remain unchanged; also assert
range_edits only produce edits whose offsets lie within
LocalSourceRange.begin..end to ensure scoped impact rather than relying on edit
count comparison with full_edits.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 77f3255c-b95c-49c0-bea1-d48ec4083358
📒 Files selected for processing (13)
src/feature/formatting.cppsrc/server/compiler/compiler.cppsrc/server/compiler/compiler.hsrc/server/protocol/worker.hsrc/server/service/lsp_client.cppsrc/server/worker/stateless_worker.cpptests/conftest.pytests/data/formatting/.clang-formattests/data/formatting/main.cpptests/integration/features/test_formatting.pytests/integration/features/test_server.pytests/integration/utils/client.pytests/unit/feature/formatting_tests.cpp
Summary
document_formatfeature to LSP via stateless workersFormatkind to stateless worker dispatch, with a lightweightforward_formatpath inCompiler(no compilation/deps needed — just file path + content)textDocument/formattingandtextDocument/rangeFormattinghandlers withscoped_pauseclang::format::getStylewhich walks parent directories for.clang-format, matching clangd's behaviorTest plan
test_capabilities🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Tests