Neon
repository·main·Indexed 19 days ago
https://github.com/chimehq/neonA high-performance Swift library for efficient, flexible, and content-based text styling. Neon enables multi-phase, flicker-free syntax highlighting by sitting between text systems like TextKit and semantic token providers such as Tree-sitter or LSP. It consists of three layers: RangeState for core range-based content processing, Neon for managing text styling and system integrations, and TreeSitterClient for parsing code using tree-sitter grammars.
What's inside Neon
- Neon is a Swift library designed for efficient and flexible content-based text styling. While it originated from the syntax highlighting system of Chime, it has evolved into a general-purpose content state management system. It is ideal for maintaining state based on linear ranges of data, even when that data is not managed by TextKit.
What is RangeState and its core components?
mainRangeState is the foundation of Neon, designed for efficient, on-demand processing of range-based content. It uses a
HybridSyncAsyncValueProviderto allow both low-latency synchronous paths for small documents and asynchronous paths for large ones.Key types include:
RangeProcessor: Performs on-demand processing (e.g., parsing).RangeValidator: Manages validation of content.RangeInvalidationBuffer: Consolidates invalidations to be applied optimally.SinglePhaseRangeValidator: Uses a single data source.ThreePhaseRangeValidator: Uses primary, fallback, and secondary data sources for multi-layered highlighting.
How three-phase highlighting works in Neon
mainNeon supports overlaying token data from multiple sources to balance latency and quality. This is typically implemented using a
ThreePhaseTextSystemStyler.A common pattern is:
- First pass (Fallback): A fast pattern-matching system (e.g., regex) for guaranteed low latency.
- Second pass (Primary): A high-quality parser like
tree-sitterwhich provides better accuracy. - Third pass (Secondary): High-latency, high-accuracy data like Language Server Protocol (LSP) semantic tokens to augment existing highlighting.
Map text data to styles using TokenProvider
mainTo determine which styles apply to specific parts of the text, Neon uses the
TokenProvidertype. This is typically driven by a parser that assigns semantic meaning to text ranges.If you are performing semantic analysis on source code, you can use Tree-sitter via the
TreeSitterClienttype. This integration is available and can be used withinTextViewHighlighterto automate the mapping of tokens to styles.How Neon, RangeState, and TreeSitterClient work together
mainNeon is composed of three distinct layers:
- RangeState: The lowest-level component. It provides the core building blocks for range-based content processing (parsing, validation, and invalidation) using a hybrid synchronous/asynchronous execution model. It is content-independent.
- Neon: The top-level module for managing text styling. It is text-system agnostic and manages how semantic tokens are applied to text. It includes integration components for AppKit and UIKit (like
TextViewHighlighter). - TreeSitterClient: A hybrid sync/async interface to
SwiftTreeSitter. It handles the actual parsing of code using tree-sitter grammars and provides an API for queries and edits.
Integrate Neon with TextKit (NSTextView/UITextView)
mainNeon provides several components for integrating with standard Apple text systems:
TextViewHighlighter: A simple bridge betweenNSTextView/UITextViewandTreeSitterClient.TextViewSystemInterface: Implements theTextSystemInterfaceprotocol for standard text views.LayoutManagerSystemInterface,TextLayoutManagerSystemInterface, andTextStorageSystemInterface: Specialized implementations for TextKit 1 and 2.
Note: For flicker-free highlighting on keystrokes in TextKit 1, it is recommended to use an
NSTextStoragesubclass likeTSYTextStorage(from theTextStorylibrary) to precisely control the timing of invalidation and styling viaRangeInvalidationBuffer.Install Neon via Swift Package Manager
mainAdd Neon to your
Package.swiftdependencies. Note that you may need to explicitly include theTreeSitterClientandRangeStateproducts depending on your target requirements.dependencies: [ .package(url: "https://github.com/ChimeHQ/Neon", branch: "main") ], targets: [ .target( name: "MyTarget", dependencies: [ "Neon", .product(name: "TreeSitterClient", package: "Neon"), .product(name: "RangeState", package: "Neon"), ] ), ]Integrate Neon with a text view
mainNeon is text system-independent and does not provide its own AppKit, UIKit, or SwiftUI views. To use Neon, you must provide your own text view and implement an interface for Neon to interact with it.
Integration requires two parts:
- A
TextSystemInterfaceprotocol implementation to apply styles to the text. - Manual notification to Neon's components regarding changes to text content and visibility.
For a quick setup with
NS/UITextView, you can useTextViewHighlighter, which handles most of these integration details automatically, though it offers less flexibility and performance than a custom implementation.``TextViewHighlighter``- A
Perform static highlighting with TreeSitterClient
mainYou can use
TreeSitterClient.highlightto asynchronously produce anAttributedStringfor a given string using a specific language configuration and aTokenAttributeProvider.let languageConfig = try LanguageConfiguration( tree_sitter_swift(), name: "Swift" ) let attrProvider: TokenAttributeProvider = { token in return [.foregroundColor: NSColor.red] } let highlightedSource = try await TreeSitterClient.highlight( string: source, attributeProvider: attrProvider, rootLanguageConfig: languageConfig, languageProvider: { _ in nil } )Use TreeSitterClient for interactive highlighting
mainTo use
TreeSitterClientfor real-time highlighting, you must configure aTreeSitterClient.Configurationthat provides ways to access your text content, its length, and handles invalidations. You then use the client to query highlights within a specificNSRangeusing aTokenAttributeProvider.import Neon import SwiftTreeSitter import TreeSitterClient import TreeSitterSwift // 1. Setup Language let languageConfig = try LanguageConfiguration( tree_sitter_swift(), name: "Swift" ) // 2. Configure Client let clientConfig = TreeSitterClient.Configuration( languageProvider: { identifier in nil }, contentSnapshotProvider: { [textView] length in .init(string: textView.string) }, lengthProvider: { [textView] in textView.string.utf16.count }, invalidationHandler: { set in /* handle invalidations */ }, locationTransformer: { location in nil } ) // 3. Initialize Client let client = try TreeSitterClient( rootLanguageConfig: languageConfig, configuration: clientConfig ) // 4. Query Highlights let source = textView.string let provider = source.predicateTextProvider let highlights = try client.highlights( in: NSRange(0..<24), provider: provider, mode: .required )!