Skip to content

feat(formatting): wire up textDocument/formatting and rangeFormatting - #441

Merged
16bit-ykiko merged 2 commits into
mainfrom
feat/formatting
May 4, 2026
Merged

16bit-ykiko merged 2 commits into
mainfrom
feat/formatting

Conversation

@16bit-ykiko

@16bit-ykiko 16bit-ykiko commented May 4, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Wire up the existing document_format feature to LSP via stateless workers
  • Add Format kind to stateless worker dispatch, with a lightweight forward_format path in Compiler (no compilation/deps needed — just file path + content)
  • Register textDocument/formatting and textDocument/rangeFormatting handlers with scoped_pause
  • Style lookup uses clang::format::getStyle which walks parent directories for .clang-format, matching clangd's behavior

Test plan

  • 4 unit tests: simple format, range format, idempotent (no edits), include sort
  • 3 integration tests: full document format (verifies applied edits match expected output), range format, already-formatted no-op
  • Capability assertions added to test_capabilities
  • All existing tests pass (554 unit, 170 integration, 2 smoke)

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added document formatting and range formatting capabilities to the LSP server
    • Formatting can target the entire document or a specific range of code
    • Server now advertises formatting support to LSP clients
  • Tests

    • Added comprehensive test coverage for formatting functionality

16bit-ykiko and others added 2 commits May 4, 2026 17:15
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>
@coderabbitai

coderabbitai Bot commented May 4, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR adds LSP document formatting support to the language server by introducing a new BuildKind::Format request type to the worker protocol, implementing formatter handlers in the compiler and stateless worker, and wiring LSP formatting capabilities into the service layer with corresponding integration and unit tests.

Changes

Document Formatting Support

Layer / File(s) Summary
Protocol & Data Shape
src/server/protocol/worker.h
Added BuildKind::Format variant and format_range: LocalSourceRange field to BuildParams for specifying formatting targets.
Core Formatter Logic
src/feature/formatting.cpp
Changed formatting failure logging from LOG_INFO with content to LOG_WARN with only the failure reason.
Worker Formatter Handler
src/server/worker/stateless_worker.cpp
Implemented handle_format that applies optional format_range, calls feature::document_format, and returns edits as result_json; wired into the stateless worker dispatcher.
Compiler Formatter API
src/server/compiler/compiler.h, src/server/compiler/compiler.cpp
Added forward_format(Session&, optional<Range>) method that maps LSP ranges to text offsets via PositionMapper and forwards formatting requests to the worker pool.
LSP Service Integration
src/server/service/lsp_client.cpp
Advertised document_formatting_provider and document_range_formatting_provider capabilities; added handlers for textDocument/documentFormatting and textDocument/rangeFormatting that pause the indexer and delegate to compiler.forward_format.
Test Infrastructure
tests/conftest.py, tests/data/formatting/*
Added formatting test-data directory with .clang-format (LLVM style, 4-space indent, 80-column limit) and main.cpp with an add function; configured compile-database generation.
Unit & Integration Tests
tests/unit/feature/formatting_tests.cpp, tests/integration/features/test_formatting.py, tests/integration/features/test_server.py, tests/integration/utils/client.py
Added unit tests for range formatting, idempotency, and include sorting; added integration tests for document/range formatting with apply_edits helper; extended test client with format_document and format_range methods; updated server capability assertions.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • refactor: improve logging #301: Modifies the same logging statement in src/feature/formatting.cpp (this PR changes log level and message; related PR changes the logging macro itself).

Poem

🐰 Hops through code with formatting cheer,
LSP requests now crystal-clear,
From worker pools to clang's embrace,
Each edit finds its rightful place,
Range or document, indents so neat,
A rabbit's formatting feat! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.93% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: wiring up LSP formatting endpoints (textDocument/formatting and rangeFormatting).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/formatting

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.

❤️ Share
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
tests/integration/features/test_formatting.py (1)

53-60: ⚡ Quick win

Range-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 win

Range-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

📥 Commits

Reviewing files that changed from the base of the PR and between 47ad905 and e5fe793.

📒 Files selected for processing (13)
  • src/feature/formatting.cpp
  • src/server/compiler/compiler.cpp
  • src/server/compiler/compiler.h
  • src/server/protocol/worker.h
  • src/server/service/lsp_client.cpp
  • src/server/worker/stateless_worker.cpp
  • tests/conftest.py
  • tests/data/formatting/.clang-format
  • tests/data/formatting/main.cpp
  • tests/integration/features/test_formatting.py
  • tests/integration/features/test_server.py
  • tests/integration/utils/client.py
  • tests/unit/feature/formatting_tests.cpp

@16bit-ykiko
16bit-ykiko merged commit 3305465 into main May 4, 2026
20 checks passed
@16bit-ykiko
16bit-ykiko deleted the feat/formatting branch May 4, 2026 11:15
@coderabbitai coderabbitai Bot mentioned this pull request May 4, 2026
7 tasks
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.

1 participant