Open
Conversation
Keep TextView's public init clean (text, selection, options, plugins only). Introduce TextViewWithGutter<GutterContent> as a dedicated view type for editors with custom per-line gutters. Gutter styling (background color, separator) uses environment-based view modifiers (.gutterBackground, .gutterSeparator) following the library's existing TextViewModifier pattern. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Demonstrates TextViewWithGutter API with per-line content: - Word count display (instead of line numbers) - Toggleable bookmark icon (bookmark/bookmark.fill) - Overhanging breakpoint badge with shadow (activated by tapping number) - Gutter styling via .gutterBackground() and .gutterSeparator() modifiers Activated via toolbar toggle button (list.star icon). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove clipsToBounds from container to allow content overhang (e.g. breakpoint badges with shadows extending past gutter edge) - Remove CATiledLayer — use regular CALayer instead. CATiledLayer renders asynchronously which breaks NSHostingView event delivery - Add subview to container BEFORE setting frame so NSHostingView has a window and can properly lay out SwiftUI content - Use identifier-based view management instead of remove-all/recreate pattern — line views are tagged and pruned by visibility - Separator uses its own identifier to avoid being pruned with line views Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace closure properties (onToggleBookmark, onToggleBreakpoint) with @binding parameters in CustomGutterLineView. The preview thunking system wraps expressions in __designTimeSelection<T> which can't reconcile () -> Void vs @mainactor () -> Void from SwiftUI View's actor isolation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use BreakpointShape (Union path from Figma) instead of RoundedRectangle for breakpoint badge — arrow-right tab shape matching Xcode breakpoints - Increase bookmark icon from 9pt to 11pt to match design proportions - Breakpoint badge replaces entire gutter row content (not alongside bookmark) - Fix separator width: 2pt to match Figma border-r-2 - Fix word count font weight: .medium (was .regular) to match SF Pro Rounded - Add XCLocalSwiftPackageReference to project.pbxproj so clean builds can resolve STTextView package dependencies (STTextKitPlus, CoreTextSwift) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use fragmentView.frame directly for custom gutter line positioning instead of cellFrame from STGutterCalculations — the latter adds typographicBounds.origin.y offset designed for baseline-aligned line numbers, which shifts NSHostingView content down. Place gutter separator behind line views so overhanging content (breakpoint badges) draws in front of the separator. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use fixed-width frame on word count label so the bookmark icon stays in the same position regardless of whether a count is shown. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Update Union shape to new Figma export sized at 28×15 - Bookmark icon stays visible when breakpoint is active - Number stays in same position (same font/frame as word count) - Badge shape extends rightward past gutter separator - HStack spacing matches Figma gap (6px) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Badge no longer extends past the gutter separator. This ensures consistent rendering in both Xcode previews and the live app, avoiding clipping differences between environments. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Offset badge by 4pt to compensate trailing padding, placing the pointed tip at the separator line without crossing it. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Set clipsToBounds=false on gutter line views so overhanging content renders consistently in both Xcode previews and the live app. Restores the badge overhang that demonstrates custom gutter content can extend beyond gutter bounds. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
STCustomGutterContainerView was missing the FB21059465 workaround that STGutterView already applies: when NSScrollView has automatic content insets (e.g. a toolbar), horizontal floating subviews do not respect those insets and render at the wrong vertical position. Adding the same layout() override shifts frame.origin.y by -topContentInset so the custom gutter aligns with the text lines. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a paragraph wraps across multiple lines, the gutter line view was sized to fragmentView.frame.size.height (the entire paragraph height). NSHostingView is non-flipped, so inside the flipped gutter container its y=0 is at the frame bottom — SwiftUI content rendered anchored to the bottom of the tall frame instead of the top. Fix: size each line view to textLineFragment.typographicBounds.height (just the first visual line) so NSHostingView content aligns to the top where the line begins. For extra line fragments where typographicBounds.height may be invalid (FB15131180), fall back to the previous line fragment's height or typingLineHeight. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without mouse event overrides, clicks in the gutter that land on the container itself (or in gaps between line views) propagated up the responder chain and reached STTextView, moving the insertion point and highlighting lines. Override mouseDown/mouseDragged/mouseUp to consume events — interactive SwiftUI subviews still receive their own events via normal hit-testing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…better flexibility and integration with SwiftUI
There was a problem hiding this comment.
Pull request overview
Adds a new “custom gutter” system that lets consumers supply per-visible-line gutter views (AppKit via a data source, SwiftUI via a @ViewBuilder wrapper), independent of the built-in line number gutter.
Changes:
- Introduces AppKit custom gutter plumbing on
STTextView(width, data source, container, colors, separator) and layouts custom gutter views during gutter layout. - Adds SwiftUI
TextViewWithGutterplus environment-driven style modifiers and a coordinator-kept data source adapter. - Updates the SwiftUI sample app to toggle between built-in line numbers and the custom gutter demo UI.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| TextEdit.SwiftUI/TextEdit.SwiftUI.xcodeproj/project.pbxproj | Adds local Swift package reference and adjusts signing settings for the sample project. |
| TextEdit.SwiftUI/ContentView.swift | Demo UI for toggling built-in line numbers vs custom gutter and per-line gutter content. |
| Sources/STTextViewSwiftUIAppKit/TextView.swift | Adds TextViewWithGutter, gutter environment keys/modifiers, and representable wiring to AppKit. |
| Sources/STTextViewAppKit/STTextView.swift | Adds custom gutter properties and uses custom gutter width for content offset/content sizing in some paths. |
| Sources/STTextViewAppKit/STTextView+Gutter.swift | Runs custom gutter layout alongside built-in gutter; implements floating container + per-line view creation/pruning + separator. |
| Sources/STTextViewAppKit/STGutterLineViewDataSource.swift | New protocol for providing per-line gutter views. |
| Sources/STTextViewAppKit/Gutter/STGutterView.swift | Makes delegate publicly settable. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
… add cleanup - Fix updateTextContainerSize, intrinsicContentSize, and sizeToFit to fall back to customGutterWidth (consistent with setFrameSize and updateContentSizeIfNeeded) - Scope gutterBackground/gutterSeparator modifiers to TextViewWithGutter so they don't appear on plain TextView - Clear custom gutter state in updateNSView when gutter is disabled - Fix misleading doc comment on layoutCustomGutterLineViews Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TextViewEnvironmentModifier conforms to TextViewModifier, not TextViewWithGutter, so chaining .gutterBackground().gutterSeparator() breaks when modifiers are scoped to TextViewWithGutter only. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add 8 tests for custom gutter sizing: sizeToFit, intrinsicContentSize, consistency, cleanup, long content, data source queries, and container lifecycle (addresses PR review comment on missing test coverage) - Fix doc comment on customGutterWidth referencing renamed property gutterLineViewProvider → gutterLineViewDataSource Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
romanr
commented
Mar 6, 2026
Author
romanr
left a comment
There was a problem hiding this comment.
review comments implemented/resolved
typographicBounds.height only includes font metrics (~17pt), not lineSpacing. This caused gutter labels to be half the visual line height, misaligning them with text content. Using fragmentView.frame height includes lineSpacing for correct 1:1 alignment.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a new gutter system that lets consumers supply a custom NSView for each visible line, independent of the built-in line number gutter. Designed for use cases like breakpoint indicators, diagnostic markers, or code folding controls.
AppKit layer (
STTextView.swift,STTextView+Gutter.swift):STTextView:customGutterWidth,gutterLineViewProvider,customGutterBackgroundColor,customGutterSeparatorColor,customGutterSeparatorWidth,customGutterContainerViewlayoutGutter()now runs custom gutter layout alongside (but independent of) the built-in gutterSTCustomGutterContainerView) — flipped, non-opaque, consumes mouse events to prevent click-throughcontentView.frame.origin.x) falls back tocustomGutterWidthwhen no built-in gutter is presentSwiftUI layer (
TextView.swift):TextViewWithGutter<GutterContent>view with@ViewBuilderAPI — wraps SwiftUI views inNSHostingViewand passes through as the provider closure.gutterBackground(_:),.gutterSeparator(color:width:)TextViewRepresentableextended with gutter properties, wired throughmakeNSView/updateNSView