A Swift library for efficient highlighting, indenting, and querying the structure of language syntax.

Overview

Build Status License Platforms

Neon

A Swift library for efficient highlighting, indenting, and querying the structure of language syntax.

It features:

  • Minimal text invalidation
  • Support for multiple token sources
  • A hybrid sync/async system for targeting flicker-free styling on keystrokes
  • tree-sitter integration
  • Compatibility with lazy text data reading
  • Flexibility when integrating with a larger text system

It does not feature:

  • A theme system
  • A single View subclass
  • Low complexity

Neon has a strong focus on efficiency and flexibility. These qualities bring some serious complexity. Right now, Neon is a collection of components that can be assembled together as part of a larger text system. It does not include a single component that ties everything together.

I realize that many people are looking for exactly that. But, it's deceptively difficult, as text systems can be phenomenally complicated. I'd love to make easier-to-use parts, and that's a goal. But, it has to be done in a way that does not sacrifice flexibility.

The library is being extracted from the Chime editor. It's a big system, and pulling it out is something we intend to do over time.

Why is this so complicated?

Working with small, static, and syntactically correct documents is one thing. Achieving both high performance and high quality behavior for an editor is totally different. Work needs to be done on every keystroke, and minimizing that work requires an enormous amount of infrastructure and careful design. Before starting, it's worth seriously evaluating your performance and quality needs. You may be able to get away with a much simpler system. A lot of this boils down to size of the document. Remember: most files are small, and small files can make even the most naive implementation feel acceptable.

Some things to consider:

  • Latency to open a file
  • Latency to visible elements highlight
  • Latency to end-of-document highlight
  • Latency on keystroke
  • Precise invalidation on keystroke
  • Highlight quality in the face of invalid syntax
  • Ability to apply progressively higher-quality highlighting
  • Precise indentation calculation

Not all of these might matter you. Neon's components are fairly loosely-coupled, so maybe just one or two parts might be usable without the whole thing.

Language Support

Neon is built around the idea that there can be multiple sources of information about the semantic meaning of the text, all with varying latencies and quality.

  • Language Server Protocol has semantic tokens, which is high quality, but also high latency.
  • tree-sitter is very good quality, and can potentially be low-latency
  • Regex-based systems can have ok quality and low-latency
  • Simpler pattern-matching systems generally have poor quality, but have very low latency

Neon includes built-in suport for tree-sitter via SwiftTreeSitter. Tree-sitter also uses separate compiled parsers for each language. Thanks to tree-sitter-xcframework, you can get access to pre-built binaries for the runtime and some parsers. It also includes the needed query definitions for those languages. This system is compatible with parsers that aren't bundled, but it's definitely more work to use them.

Integration

Neon's components need to react to various events:

  • the text is about to change
  • the text has changed
  • a text change has been processed and is now ready to be styled
  • the visible text has changed
  • the styling has become invalid (ex: the theme has changed)

How and where they come from depends on your text setup. And, not every components needs to know about all of these, so you may be able to get away with less.

A very minimum setup could be produced with just an NSTextStorageDelegate. Monitoring the visible rect of the NSTextView will improve performance.

Achieving guaranteed flicker-free highlighting is more challenging. You need to know when a text change has been processing by enough of the system that styling is possible. This point in the text change lifecycle is not natively supported by NSTextStorage or NSLayoutManager. It requires an NSTextStorage subclass. But, even that isn't quite enough unfortunately, as you still need to precisely control the timing of invalidation and styling. This is where HighlighterInvalidationBuffer comes in. I warned you this was complicated.

Relationship to TextStory

TextStory is a library that contains three very useful components when working with Neon.

  • TSYTextStorage gets you all the text change life cycle hooks without falling into the NSString/String bridging performance traps
  • TextMutationEventRouter makes it easier to route events to the components
  • LazyTextStoringMonitor allows for lazy content reading, which is essential to quickly open large documents

You can definitely use Neon without TextStory. But, I think it may be reasonable to just make Neon depend on TextStory to help simplify usage.

Components

Highligher

This is the main component that coordinates the styling and invalidation of text.

  • Connects to a text view via TextSystemInterface
  • Monitors text changes and view visible state
  • Gets token-level information from a TokenProvider

Note that Highlighter is built to handle a TokenProvider calling its completion block more than one time, potentially replacing or merging with existing styling.

HighlighterInvalidationBuffer

In a traditional NSTextStorage/NSLayoutManager system (TextKit 1), it can be challenging to achieve flicker-free on-keypress highlighting. This class offers a mechanism for buffering invalidations, so you can precisely control how and when actual text style updates occur.

TextContainerSystemInterface (macOS only)

An implementation of the TextSystemInterface protocol for an NSTextContainer-backed NSTextView. This takes care of the interface to NSTextView and NSLayoutManager, but defers Token-style translation (themes) to an external AttributeProvider.

TreeSitterClient

This class is an asynchronous interface to tree-sitter. It provides an UTF-16 code-point (NSString-compatible) API for edits, invalidations, and queries. It can process edits of String objects, or raw bytes. Invalidations are translated to the current content state, even if a queue of edits are still being processed. It is fully-compatible with reading the document content lazily.

  • Monitors text changes
  • Can be used to build a TokenProvider
  • Requires a function that can translate UTF-16 code points to a tree-sitter Point (line + offset)

TreeSitterClient provides APIs that can be both synchronous, asynchronous, or both depending on the state of the system. This kind of interface can be important when optimizing for flicker-free, low-latency highlighting live typing interactions like indenting.

Using it is quite involved - here's a little example:

import SwiftTreeSitter
import tree_sitter_language_resources
import Neon

// step 1: setup

// construct the tree-sitter grammar for the language you are interested
// in working with manually
let unbundledLanguage = Language(language: my_tree_sitter_grammar())

// .. or grab one from tree-sitter-xcframework
let swift = LanguageResource.swift
let language = Language(language: swift.parser)

// construct your highlighting query
// this is a one-time cost, but can be expensive
let url = swift.highlightQueryURL!
let query = try! language.query(contentsOf: url)

// step 2: configure the client

// produce a function that can map UTF16 code points to Point (Line, Offset) structs
let locationToPoint = { Int -> Point? in ... }

let client = TreeSitterClient(language: language, locationToPoint: locationToPoint)

// this function will be called with a minimal set of text ranges
// that have become invalidated due to edits. These ranges
// always correspond to the *current* state of the text content,
// even if TreeSitterClient is currently processing edits in the
// background.
client.invalidationHandler = { set in ... }

// step 3: inform it about content changes
// these APIs match up fairly closely with NSTextStorageDelegate,
// and are compatible with lazy evaluation of the text content

// call this *before* the content has been changed
client.willChangeContent(in: range)

// and call this *after*
client.didChangeContent(to: string, in: range, delta: delta, limit: limit)

// step 4: run queries
// you can execute these queries directly in the invalidationHandler, if desired

// Many tree-sitter highlight queries contain predicates. These are both expensive
// and complex to resolve. This is an optional feature - you can just skip it. Doing
// so makes the process both faster and simpler, but could result in lower-quality
// and even incorrect highlighting.

let provider: TreeSitterClient.TextProvider = { (range, _) -> String? in ... }

client.executeHighlightsQuery(query, in: range, textProvider: provider) { result in
    // Token values will tell you the highlights.scm name and range in your text
}

Suggestions or Feedback

We'd love to hear from you! Get in touch via twitter, an issue, or a pull request.

Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.

Comments
  • Map the visible rect to the text container

    Map the visible rect to the text container

    by subtracting the text container's origin from the visible rect's origin.

    I think this is the right way to map and maybe you hadn't noticed before if the text container offset was very small or zero?

    opened by danielpunkass 2
  • async version of currentTree

    async version of currentTree

    Hi I have this snippet in my code

    extension TreeSitterClient {
        func currentTree() async throws -> Tree? {
            try await withCheckedThrowingContinuation { continuation in
                currentTree() { result in
                    switch result {
                    case .failure: continuation.resume(returning: nil)
                    case .success(let tree):
                        continuation.resume(returning: tree)
                    }
                }
            }
        }
    }
    

    Do you think it would make sense to have this function in the repo? I can open a PR

    opened by kaunteya 1
  • Prevent conversion of TextKit2 NSTextViews to TextKit1

    Prevent conversion of TextKit2 NSTextViews to TextKit1

    Prevent conversion of TextKit2 NSTextViews to TextKit1 when requesting the visibleTextRange. For now if TextKit2 is detected on a text view, just return the whole range.

    opened by danielpunkass 1
  • Optional transformer function in TreeSitterClient

    Optional transformer function in TreeSitterClient

    Inclusions

    • TreeSitterClient can be initialized only with a language parameter. [Non-breaking change]
    treeSitterClient = try! TreeSitterClient(language: language)
    

    • locationTransformer function will be an optional function.[ Breaking change]
    public let locationTransformer: Point.LocationTransformer?
    
    opened by kaunteya 1
  • More readme tweaks

    More readme tweaks

    Some typo fixes and changes that make the advanced examples compile as shown. I think this will aid in adoption as people are trying to figure out how to use the advanced integration.

    opened by danielpunkass 1
Releases(0.5.0)
Owner
Chime
Chime
A quick reference cheat sheet for common, high level topics in Swift.

Swift 3+ Cheat Sheet Want to help improve this? File an issue or open a pull request! :) This is not meant to be a beginner's guide or a detailed disc

rob phillips 957 Dec 12, 2022
🍮 A collection of Swift snippets to be used in Xcode

Swift Snippets ❤️ Support my app ❤️ Push Hero - pure Swift native macOS application to test push notifications PastePal - Pasteboard, note and shortcu

Khoa 156 Dec 24, 2022
An Xcode playground showcasing the new features in Swift 4.0.

What’s new in Swift 4 An Xcode playground showcasing the new features in Swift 4.0. As seen in the What’s New in Swift session at WWDC 2017. Written b

Ole Begemann 1.8k Dec 23, 2022
Notepad - A fully themeable iOS markdown editor with live syntax highlighting.

Notepad is just like any other UITextView, but you need to use the convenience initializer in order to use the themes. To create a new theme, copy one of the existing themes and edit the JSON.

Rudd Fawcett 802 Dec 31, 2022
Simple Application that registers Vapor Leaf's .leaf file type to LaunchServices as html enabling automatic syntax highlighting in Xcode.

Vapor Leaf Extension Update: The Vapor Leaf Extension is now meant to be used with the Xcode Plugin I designed to provide Xcode language support for t

Omran Khoja 12 Jun 18, 2022
CodeEditor - A SwiftUI TextEditor with syntax highlighting using Highlight.js

CodeEditor A SwiftUI TextEditor View with syntax highlighting using Highlight.js. It builds on top of Highlightr which does the wrapping of Highlight.

ZeeZide 269 Dec 30, 2022
TextMate-style syntax highlighting

SyntaxKit SyntaxKit makes TextMate-style syntax highlighting easy. It works on iOS, watchOS, and OS X. SyntaxKit was abstracted from Whiskey. Building

Sam Soffes 466 Sep 9, 2022
A Collection of Tree-Sitter Parsers for Syntax Highlighting

CodeEditLanguages A collection of tree-sitter languages for syntax highlighting. Overview This package includes a binary framework CodeLanguagesContai

CodeEdit 16 Dec 30, 2022
Thingy - A modern device detection and querying library.

Thingy A modern device detection and querying library. Features Swift 5 support Modern syntax Documentation Device detection Device family detection D

Bojan Dimovski 58 Oct 5, 2022
Effortless emoji-querying in Swift

EmojiKit EmojiKit is a simple emoji-querying framework in Swift. It is used in Paste, an Emoji Search app in the App Store. Installation If you’re usi

Dasmer Singh 93 Nov 12, 2022
Developed with use Swift language. As a third party library used SDWebImage. JSON parsing using URLSession with TMDB API. This app provide by the Core Data structure.

Capstone Project ?? About Developed with use Swift language. As a third party library used SDWebImage. JSON parsing using URLSession with TMDB API. Ad

Ensar Batuhan Unverdi 9 Aug 22, 2022
This project brings FlatBuffers (an efficient cross platform serialization library) to Swift.

FlatBuffersSwift Motivation This project brings FlatBuffers (an efficient cross platform serialization library) to Swift. One minute introduction Ther

Maxim Zaks 567 Dec 17, 2022
An extremely high-performance, lightweight, and energy-efficient pure Swift async web image loader with memory and disk caching for iOS and  Watch.

KFSwiftImageLoader KFSwiftImageLoader is an extremely high-performance, lightweight, and energy-efficient pure Swift async web image loader with memor

Kiavash Faisali 343 Oct 29, 2022
Use 1600+ icons (and more!) from FontAwesome and Google Material Icons in your swift/iOS project in an easy and space-efficient way!

Swicon Use 1600+ icons from FontAwesome and Google Material Icons in your iOS project in an easy and space-efficient way! The built-in icons are from

Zhibo 39 Nov 3, 2022
A handy collection of Swift method and Tools to build project faster and more efficient.

SwifterKnife is a collection of Swift extension method and some tools that often use in develop project, with them you might build project faster and

李阳 4 Dec 29, 2022
The most power-efficient and lightweight iOS location manager for Swift and ObjC

IngeoSDK for iOS Overview IngeoSDK is a power-efficient location manager for iOS (Swift and Objective-C), which extends and improves CoreLocation. It

IngeoSDK 99 Feb 28, 2022
A repository for showcasing my knowledge of the Swift programming language, and continuing to learn the language.

Learning Swift (programming language) I know very little about programming in the Swift programming language. This document will go over all my knowle

Sean P. Myrick V19.1.7.2 2 Nov 8, 2022
Localization of the application with ability to change language "on the fly" and support for plural form in any language.

L10n-swift is a simple framework that improves localization in swift app, providing cleaner syntax and in-app language switching. Overview ?? Features

Adrian Bobrowski 287 Dec 24, 2022
A repository for showcasing my knowledge of the Objective-C++ programming language, and continuing to learn the language.

Learning Objective-C-Plus-Plus I hardly know anything about the Objective-C++ programming language. This document will go over all of my knowledge of

Sean P. Myrick V19.1.7.2 3 Nov 8, 2022
A repository for showcasing my knowledge of the Objective-C programming language, and continuing to learn the language.

Learning Objective-C I hardly know anything about the Objective-C programming language. This document will go over all of my knowledge of the Objectiv

Sean P. Myrick V19.1.7.2 3 Nov 8, 2022