Neon

repository·main·Indexed 19 days ago

https://github.com/chimehq/neon

A 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.

Tokens
2K
Snippets
4
Records
10
Agent score
64%

What's inside Neon

  1. Overview of Neon

    main
    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.
  2. What is RangeState and its core components?

    main

    RangeState is the foundation of Neon, designed for efficient, on-demand processing of range-based content. It uses a HybridSyncAsyncValueProvider to 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.
  3. How three-phase highlighting works in Neon

    main

    Neon supports overlaying token data from multiple sources to balance latency and quality. This is typically implemented using a ThreePhaseTextSystemStyler.

    A common pattern is:

    1. First pass (Fallback): A fast pattern-matching system (e.g., regex) for guaranteed low latency.
    2. Second pass (Primary): A high-quality parser like tree-sitter which provides better accuracy.
    3. Third pass (Secondary): High-latency, high-accuracy data like Language Server Protocol (LSP) semantic tokens to augment existing highlighting.
  4. Map text data to styles using TokenProvider

    main

    To determine which styles apply to specific parts of the text, Neon uses the TokenProvider type. 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 TreeSitterClient type. This integration is available and can be used within TextViewHighlighter to automate the mapping of tokens to styles.

  5. How Neon, RangeState, and TreeSitterClient work together

    main

    Neon is composed of three distinct layers:

    1. 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.
    2. 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).
    3. 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.
  6. Integrate Neon with TextKit (NSTextView/UITextView)

    main

    Neon provides several components for integrating with standard Apple text systems:

    • TextViewHighlighter: A simple bridge between NSTextView/UITextView and TreeSitterClient.
    • TextViewSystemInterface: Implements the TextSystemInterface protocol for standard text views.
    • LayoutManagerSystemInterface, TextLayoutManagerSystemInterface, and TextStorageSystemInterface: Specialized implementations for TextKit 1 and 2.

    Note: For flicker-free highlighting on keystrokes in TextKit 1, it is recommended to use an NSTextStorage subclass like TSYTextStorage (from the TextStory library) to precisely control the timing of invalidation and styling via RangeInvalidationBuffer.

  7. Install Neon via Swift Package Manager

    main

    Add Neon to your Package.swift dependencies. Note that you may need to explicitly include the TreeSitterClient and RangeState products 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"),
            ]
        ),
    ]
  8. Integrate Neon with a text view

    main

    Neon 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:

    1. A TextSystemInterface protocol implementation to apply styles to the text.
    2. Manual notification to Neon's components regarding changes to text content and visibility.

    For a quick setup with NS/UITextView, you can use TextViewHighlighter, which handles most of these integration details automatically, though it offers less flexibility and performance than a custom implementation.

    ``TextViewHighlighter``
  9. Perform static highlighting with TreeSitterClient

    main

    You can use TreeSitterClient.highlight to asynchronously produce an AttributedString for a given string using a specific language configuration and a TokenAttributeProvider.

    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 }
    )
  10. Use TreeSitterClient for interactive highlighting

    main

    To use TreeSitterClient for real-time highlighting, you must configure a TreeSitterClient.Configuration that provides ways to access your text content, its length, and handles invalidations. You then use the client to query highlights within a specific NSRange using a TokenAttributeProvider.

    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
    )!