Claude's attempt at wrapping code#8
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds an optional “wrap long lines” rendering mode to DiffView (both split and unified layouts), and extends visual snapshot coverage to prevent regressions in wrapped layouts.
Changes:
- Added
DiffView.wrap(reactive) plus wrapped versions of unified/split composition that render a folded (wrapped) diff layout. - Introduced
FoldedLineContentvisual to render wrapped code lines with an inline gutter and continuation marker (↪). - Added snapshot tests and new/updated SVG snapshots for wrapped + existing modes; updated the example app to toggle wrap.
Reviewed changes
Copilot reviewed 4 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/textual_diff_view/_diff_view.py |
Implements wrap mode, new folding visual, and wrapped split/unified composition paths. |
tests/test_snapshots.py |
Adds 4 snapshot tests covering wrap mode with/without annotations in split/unified. |
tests/__snapshots__/test_snapshots/test_diff_view_wrap_unified.svg |
New snapshot baseline for unified wrapped mode. |
tests/__snapshots__/test_snapshots/test_diff_view_wrap_unified_annotations.svg |
New snapshot baseline for unified wrapped mode with annotations. |
tests/__snapshots__/test_snapshots/test_diff_view_unified.svg |
Updated snapshot reflecting rendering/styling changes. |
tests/__snapshots__/test_snapshots/test_diff_view_unified_annotations.svg |
Updated snapshot reflecting rendering/styling changes. |
tests/__snapshots__/test_snapshots/test_diff_view_defaults.svg |
Updated snapshot reflecting rendering/styling changes. |
tests/__snapshots__/test_snapshots/test_diff_view_annotations.svg |
Updated snapshot reflecting rendering/styling changes. |
examples/tdiff.py |
Adds a keybinding and data-binding for toggling wrap in the demo app. |
CLAUDE.md |
Adds repository/tooling/architecture notes for Claude Code. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def get_selection(self, selection: Selection) -> tuple[str, str] | None: | ||
| visual = self._render() | ||
| if isinstance(visual, LineContent): | ||
| if isinstance(visual, FoldedLineContent): | ||
| text = "\n".join( | ||
| getattr(visual, "_visual_code_lines", []) | ||
| ) | ||
| elif isinstance(visual, LineContent): |
There was a problem hiding this comment.
DiffCode.get_selection() builds the extraction text for FoldedLineContent from _visual_code_lines (code-only), but FoldedLineContent.render_strips() assigns selection metadata offsets across the combined gutter+code strips. This mismatch can cause copied selections to be shifted/incorrect (and/or make gutter selectable) depending on how Textual computes selection spans. Consider either: (1) generating the extraction text to match the same gutter+code text used for offsets, or (2) adjusting segment meta offsets so only the code portion contributes to selection coordinates (and the gutter has no selectable offsets), keeping extraction code-only.
| meta: dict[str, object] = {"offset": (0, y)} | ||
| segments = [ | ||
| Segment(text, rich_style + RichStyle.from_meta(meta) if rich_style else rich_style) | ||
| for text, rich_style, _ in hatch.render_segments() | ||
| ] |
There was a problem hiding this comment.
In the content is None branch, segment metadata offsets are not updated per segment (all segments get the same offset: (0, y)). This will break hit-testing/selection mapping across the row because x never advances. Build the hatch segments with an explicit loop (like LineContent.render_strips) updating x and meta["offset"] for each segment.
| meta: dict[str, object] = {"offset": (0, y)} | |
| segments = [ | |
| Segment(text, rich_style + RichStyle.from_meta(meta) if rich_style else rich_style) | |
| for text, rich_style, _ in hatch.render_segments() | |
| ] | |
| segments = [] | |
| x = 0 | |
| meta: dict[str, object] = {"offset": (x, y)} | |
| for text, rich_style, _ in hatch.render_segments(): | |
| if rich_style is not None: | |
| meta["offset"] = (x, y) | |
| segments.append( | |
| Segment(text, rich_style + RichStyle.from_meta(meta)) | |
| ) | |
| else: | |
| segments.append(Segment(text, rich_style)) | |
| x += len(text) |
Add code wrapping