📝 Performant plain text editor for iOS with syntax highlighting, line numbers, invisible characters and much more.

👋 Welcome to Runestone - a performant plain text editor for iOS with code editing features

Runestone uses GitHub's Tree-sitter to parse code to a syntax tree which is used for features that require an understanding of the code in the editor, for example syntax highlighting.

Features

  • Syntax highlighting.
  • Line numbers.
  • Highlight the selected line.
  • Show invisible characters (tabs, spaces and line breaks).
  • Insertion of character pairs, e.g. inserting the trailing quotation mark when inserting the leading.
  • Customization of colors and fonts.
  • Toggle line wrapping on and off.
  • Adjust height of lines.
  • Add a page guide.
  • Add vertical and horizontal overscroll.
  • Highlight ranges in the text view.
  • Search the text using regular expressions.
  • Automatically detects if a file is using spaces or tabs for indentation.

🚀 Getting Started

Please refer to the Getting Started article in the documentation.

📖 Documentation

The documentation of all public types is available at docs.runestone.app. The documentation is generated from the Swift code using Apple's DocC documentation compiler.

🏎 Performance

Runestone was built to be fast. Its good performance is by far mostly thanks to Tree-sitter's incremental parsing and AvalonEdit's approach for managing lines in a document.

When judging the performance of Runestone, it is key to build your app in the release configuration. The optimizations applied by the compiler when using the release configuration becomes very apparent when opening large documents.

📱 Projects

The Runestone framework is used by an app of the same name. The Runestone app is a plain text editor for iPhone and iPad that uses all the features of this framework.

Runestone app icon

Download on the App Store

❤️ Acknowledgments

Owner
Simon Støvring
I tinker with iOS automation and poke at private APIs. Working on Runestone, Scriptable, Jayson, and Data Jar.
Simon Støvring
Comments
  • 🐛「Bug」Can't handle Korean input correctly

    🐛「Bug」Can't handle Korean input correctly

    Describe the bug Can't handle Korean input correctly

    Screenshots

    https://user-images.githubusercontent.com/7934444/166901295-93cb41aa-131d-4289-bab3-af121ad6d3c0.MOV

  • Always insert text when calling `insertText(_:)` programmatically

    Always insert text when calling `insertText(_:)` programmatically

    I came across another subtle difference in behaviour when switching to Runestone: When the TextView is not the first responder, programatically calling insertText with some text didn't do anything, while my previous implementation that used UITextView did append the text.

    This PR changes the behaviour of TextView.insertText(_:) to also append the text at the end. I kept the behaviour of TextInputView.insertText(_:) the same (i.e., it will not insert the text if there's no marked or selected range) in case that this was on purpose.

  • Can't open Package.swift in Xcode 14.*

    Can't open Package.swift in Xcode 14.*

    Describe the bug Downloading the repo from GitHub and double clicking on Package.swift fails to build.

    To Reproduce Download the repo from GitHub, double click Package.swift.

    Expected behavior Builds.

    Screenshots

    image

    I get a similar error when trying to open the Example project:

    image

    Additional context I checked Getting Started but I didn't see any additional steps spelled out. I'm not sure what I'm doing wrong

  • Automatic theme switches when toggling light/dark mode

    Automatic theme switches when toggling light/dark mode

    Is your feature request related to a problem? Please describe. The theme system allows specifying a single theme, which would be optimised for either light or dark mode. There doesn't yet seem a way to either set a single theme that supports both light and dark mode, or specify a theme per interface style.

    Describe the solution you'd like This could be addressed by providing multiple themes, one per UIUserInterfaceStyle, or by providing support for themes that support multiple interface styles.

    Describe alternatives you've considered

    • I've tried adjusting the Color Set in my asset catalog to provide alternative colours for light and dark mode. This seems to work for a subset of colours, but notable not for the background colours. I'm also not sure if that's fully compatible with the internals of Runestone.
    • Detect this on my view controllers, and then set the new theme.

    Additional context I'm happy to provide a PR for this, but I'm not sure if this is desired and, if so, which approach would be the best fit for this framework.

  • Option to disable font ligatures

    Option to disable font ligatures

    Is your feature request related to a problem? Please describe. "The problem" is that Runestone uses font ligatures, so it joins characters like == != <= => <==> together.

    Example: (with JetBrains Mono)

    image

    Describe the solution you'd like An option to disable font ligatures.

    Describe alternatives you've considered /

  • insertText(...) does not update caret position in TextView

    insertText(...) does not update caret position in TextView

    Describe the bug Inserting characters to a TextView programmatically, for example from a keyboard input view with quick access special keys, seems to be done by calling insertText(_ text: String) method on TextView. This adds the characters and they appear, but the caret is not moved after the newly inserted characters but stays where it was, even though the implementation seems to attempt to move the caret.

    Typing more characters using the native keyboard does move the caret after the new text. Also moving the caret using arrow keys does begin moving from the position where the caret was supposed to be. So it seems like the caret position (selectedRange?) has the correct value, but it is not reflected in the TextView after text insertion.

    I also tried to move the caret programmatically, by adjusting selectedRange's location property, but it results in the caret being moved too many steps ahead.

    To Reproduce

    • Call textView.insertText("a") on an existing TextView instance.
    • New characters appear but the caret stays in the same location before the inserted characters.

    Expected behavior Caret should move to a position after the newly inserted characters.

  • Go to line doesn't reveal line into view when keyboard is visible

    Go to line doesn't reveal line into view when keyboard is visible

    Describe the bug Go to line doesn't scroll the line into safe area when virtual keyboard is visible

    To Reproduce

    • Open Example project
    • In MainViewController, add a new action which always runs self?.contentView.textView.goToLine(50)
    • Debug
    • Open a document which contains more than 50 lines, scroll to the very top of the document
    • Run the action
    • Line 50 is revealed to the bottom of the viewport but it's covered by the visible virtual keyboard

    Expected behavior Line 50 should be above the keyboard

    Screenshots

    image

    Lastly thank you for the project, it reminds me of Codemirror and TextMate, both kind of become de factor standard on their own platform.

  • Allow subclassing TextView and overriding certain methods

    Allow subclassing TextView and overriding certain methods

    This is a great library and works really well, and I enjoyed the challenge of wrapping my head around Tree-sitter!

    I'm using Runestone's TextView in a project that allows writing expressions including Xcode-like autocompletion that is anchored to the selected text. That requires the ability to subclass TextView (to intercept certain keystrokes for interacting with the autocompletion list) and to access the underlying UITextInput protocol (to get the rectangle of the selection, to put the autocompletion list next to it).

    For consistency, I made the various methods that are overridden open or that provide UITextInput-like functionality.

  • Crash with Catalyst

    Crash with Catalyst

    Describe the bug When adding a TextView as a subview in a UIViewController in a Catalyst build there's an immediate crash when tapping into the TextView (and making first responder).

    Error is: Runestone/TextInputView.swift:1287: Fatal error: Positions must be of type IndexedPosition looks like position is nil.

    Screenshots image

  • Add isEditable and isSelectable property to TextView

    Add isEditable and isSelectable property to TextView

    Closes #34

    • When isEditable is true, text is still selectable and users are able to copy the text.
    • When isSelectable is true then the user will not be able to do anything to the text.
  • Double digit line numbers truncate with a font size greater or equal to 20

    Double digit line numbers truncate with a font size greater or equal to 20

    Describe the bug Double digit line numbers truncate with a font size greater or equal to 20

    To Reproduce

    1. Open the Example project.
    2. Navigate to OneDarkTheme.swift.
    3. Set the font to a size of 20 or greater.
    4. Run the project
    5. Set theme to One Dark
    6. Enter enough new lines to see 10 rows

    Expected behavior

    • Line numbers don't truncate.
    • Line number gutter increases in width to accommodate the double digits at the larger font size.

    Screenshots image

  • Folding code blocks

    Folding code blocks

    Is your feature request related to a problem? Please describe. Folding code blocks would be a very nice addition 😋

    Describe the solution you'd like Clicking an arrow or plus/minus sign would toggle expanding (unfold) and collapsing (fold) of an user selected code block. This allows the user to manage large amounts of text while viewing only those subsections that are currently of interest.

    Reference https://en.wikipedia.org/wiki/Code_folding

  • Cannot use from Swift Playgrounds

    Cannot use from Swift Playgrounds

    Is your feature request related to a problem? Please describe. Swift Playgrounds (iPad in my case but should be applicable on Mac as well) cannot import packages that have C code included.

    Describe the solution you'd like I haven’t looked deeply into the implementation but I’d hope for either of the following:

    1. A separate package for the editor vs the language parsers
    2. Perhaps the language/C code could be provided via a pre-compiled package instead?

    Again, I may be over-simplifying the solution as I’m not familiar enough to understand the problem atm.

    Describe alternatives you've considered Atm, I have to move the project to Xcode if I want to include this package (which I do), but I’m holding off on the editor bits for now because everything else can be built successfully on iPad.

    Additional context Thank you for open sourcing such a high quality editor. I’ve worked on this kind of thing before and its an truly a huge undertaking, you’ve done an incredible job! I’ve bought your Runestone app as well and its become a huge part of my daily workflow 👌

    It doesn’t have language support for some things (like XML) but I noticed I can still open it as plain text which is great 👍

  • Multicursor support

    Multicursor support

    Multicursor support is difficult if not impossible to implement in touch-based text editors, but since iPads support both keyboard and mouse/trackpad it would be a nice feature to have when, e.g., a Magic Keyboard is attached.

  • Minimum Gutter Width Option

    Minimum Gutter Width Option

    It'd be great to have an option on gutter layout to specify a minimum width. Specifically to solve the text view "jumping" when hitting double (or more) digits. This would be especially nice if code is less than 3 digits.

    repro

  • Curious crash when deallocating `TextInputView`

    Curious crash when deallocating `TextInputView`

    Describe the bug

    I've integrated Runestone into one of my apps, where it gets displayed in a SwiftUI view using UIHostingView. I'm seeing some occasional crashes which seem to come from the OperationQueue in TreeSitterInternalLanguageMode.

    I am not sure how to best address this. Any advice would be appreciated.

    To Reproduce

    No reliably steps to reproduce yet, as it's not deterministic.

    Additional context

    Sample stacktrace from an iPad Pro running iOS 15.5 (19F77), from the main thread:

    Thread 0#0	(null) in objc_release ()
    #1	(null) in -[NSOperationQueue dealloc] ()
    #2	(null) in TreeSitterInternalLanguageMode.__deallocating_deinit ()
    #3	(null) in _swift_release_dealloc ()
    #4	(null) in bool swift::HeapObjectSideTableEntry::decrementStrong<(swift::PerformDeinit)1>(unsigned int) ()
    #5	(null) in IndentController.__deallocating_deinit ()
    #6	(null) in _swift_release_dealloc ()
    #7	(null) in @objc TextInputView.__ivar_destroyer ()
    #8	(null) in object_cxxDestructFromClass(objc_object*, objc_class*) ()
    #9	(null) in objc_destructInstance ()
    #10	(null) in _objc_rootDealloc ()
    #11	(null) in -[UIResponder dealloc] ()
    #12	(null) in -[UIView dealloc] ()
    #13	(null) in @objc TextView.__ivar_destroyer ()
    #14	(null) in object_cxxDestructFromClass(objc_object*, objc_class*) ()
    #15	(null) in objc_destructInstance ()
    #16	(null) in _objc_rootDealloc ()
    #17	(null) in -[UIResponder dealloc] ()
    #18	(null) in -[UIView dealloc] ()
    #19	(null) in -[UIScrollView dealloc] ()
    #20	(null) in @objc PlatformViewHost.__ivar_destroyer ()
    #21	(null) in object_cxxDestructFromClass(objc_object*, objc_class*) ()
    #22	(null) in objc_destructInstance ()
    #23	(null) in _objc_rootDealloc ()
    #24	(null) in -[UIResponder dealloc] ()
    #25	(null) in -[UIView dealloc] ()
    #26	(null) in (anonymous namespace)::destroyGenericBox(swift::HeapObject*) ()
    #27	(null) in _swift_release_dealloc ()
    #28	(null) in partial apply for thunk for @callee_guaranteed (@unowned UnsafePointer<A1>) -> (@unowned Attribute<A>, @error @owned Error) ()
    #29	(null) in _swift_release_dealloc ()
    #30	(null) in swift_arrayDestroy ()
    #31	(null) in _ContiguousArrayStorage.__deallocating_deinit ()
    #32	(null) in _swift_release_dealloc ()
    #33	(null) in swift_arrayDestroy ()
    #34	(null) in _ContiguousArrayStorage.__deallocating_deinit ()
    #35	(null) in _swift_release_dealloc ()
    #36	(null) in swift_arrayDestroy ()
    #37	(null) in _ContiguousArrayStorage.__deallocating_deinit ()
    #38	(null) in _swift_release_dealloc ()
    #39	(null) in DisplayList.ViewUpdater.deinit ()
    #40	(null) in DisplayList.ViewUpdater.__deallocating_deinit ()
    #41	(null) in _swift_release_dealloc ()
    #42	(null) in DisplayList.ViewRenderer.deinit ()
    #43	(null) in DisplayList.ViewRenderer.__deallocating_deinit ()
    #44	(null) in _swift_release_dealloc ()
    #45	(null) in @objc _UIHostingView.__ivar_destroyer ()
    #46	(null) in object_cxxDestructFromClass(objc_object*, objc_class*) ()
    #47	(null) in objc_destructInstance ()
    #48	(null) in _objc_rootDealloc ()
    #49	(null) in -[UIResponder dealloc] ()
    #50	(null) in -[UIView dealloc] ()
    #51	(null) in _UIHostingView.__deallocating_deinit ()
    #52	(null) in @objc _UIHostingView.__deallocating_deinit ()
    #53	(null) in AutoreleasePoolPage::releaseUntil(objc_object**) ()
    #54	(null) in objc_autoreleasePoolPop ()
    #55	(null) in -[UIView dealloc] ()
    #56	(null) in -[UITableViewCell .cxx_destruct] ()
    #57	(null) in object_cxxDestructFromClass(objc_object*, objc_class*) ()
    #58	(null) in objc_destructInstance ()
    #59	(null) in _objc_rootDealloc ()
    #60	(null) in -[UIResponder dealloc] ()
    #61	(null) in -[UIView dealloc] ()
    #62	(null) in -[UITableViewCell dealloc] ()
    #63	(null) in __RELEASE_OBJECTS_IN_THE_ARRAY__ ()
    #64	(null) in -[__NSArrayM dealloc] ()
    #65	(null) in -[__NSOrderedSetM dealloc] ()
    #66	(null) in cow_cleanup ()
    #67	(null) in -[__NSDictionaryM dealloc] ()
    #68	(null) in -[UITableView .cxx_destruct] ()
    #69	(null) in object_cxxDestructFromClass(objc_object*, objc_class*) ()
    #70	(null) in objc_destructInstance ()
    #71	(null) in _objc_rootDealloc ()
    #72	(null) in -[UIResponder dealloc] ()
    #73	(null) in -[UIView dealloc] ()
    #74	(null) in -[UIScrollView dealloc] ()
    #75	(null) in -[UITableView dealloc] ()
    #76	(null) in cow_cleanup ()
    #77	(null) in -[__NSDictionaryM dealloc] ()
    #78	(null) in -[UIViewController .cxx_destruct] ()
    #79	(null) in object_cxxDestructFromClass(objc_object*, objc_class*) ()
    #80	(null) in objc_destructInstance ()
    #81	(null) in _objc_rootDealloc ()
    #82	(null) in -[UIResponder dealloc] ()
    #83	(null) in -[UIViewController dealloc] ()
    #84	(null) in object_cxxDestructFromClass(objc_object*, objc_class*) ()
    #85	(null) in objc_destructInstance ()
    #86	(null) in _objc_rootDealloc ()
    #87	(null) in cow_cleanup ()
    #88	(null) in -[__NSDictionaryM dealloc] ()
    #89	(null) in object_cxxDestructFromClass(objc_object*, objc_class*) ()
    #90	(null) in objc_destructInstance ()
    #91	(null) in _objc_rootDealloc ()
    #92	(null) in object_cxxDestructFromClass(objc_object*, objc_class*) ()
    #93	(null) in objc_destructInstance ()
    #94	(null) in _objc_rootDealloc ()
    #95	(null) in -[UIResponder dealloc] ()
    #96	(null) in -[UIViewController dealloc] ()
    #97	(null) in @objc PlatformViewHost.__ivar_destroyer ()
    #98	(null) in object_cxxDestructFromClass(objc_object*, objc_class*) ()
    #99	(null) in objc_destructInstance ()
    #100	(null) in _objc_rootDealloc ()
    #101	(null) in -[UIResponder dealloc] ()
    #102	(null) in -[UIView dealloc] ()
    #103	(null) in (anonymous namespace)::destroyGenericBox(swift::HeapObject*) ()
    #104	(null) in _swift_release_dealloc ()
    #105	(null) in partial apply for thunk for @callee_guaranteed (@unowned UnsafePointer<A1>) -> (@unowned Attribute<A>, @error @owned Error) ()
    #106	(null) in _swift_release_dealloc ()
    #107	(null) in swift_arrayDestroy ()
    #108	(null) in _ContiguousArrayStorage.__deallocating_deinit ()
    #109	(null) in _swift_release_dealloc ()
    #110	(null) in DisplayList.ViewUpdater.deinit ()
    #111	(null) in DisplayList.ViewUpdater.__deallocating_deinit ()
    #112	(null) in _swift_release_dealloc ()
    #113	(null) in DisplayList.ViewRenderer.deinit ()
    #114	(null) in DisplayList.ViewRenderer.__deallocating_deinit ()
    #115	(null) in _swift_release_dealloc ()
    #116	(null) in @objc _UIHostingView.__ivar_destroyer ()
    #117	(null) in object_cxxDestructFromClass(objc_object*, objc_class*) ()
    #118	(null) in objc_destructInstance ()
    #119	(null) in _objc_rootDealloc ()
    #120	(null) in -[UIResponder dealloc] ()
    #121	(null) in -[UIView dealloc] ()
    #122	(null) in _UIHostingView.__deallocating_deinit ()
    #123	(null) in @objc _UIHostingView.__deallocating_deinit ()
    #124	(null) in AutoreleasePoolPage::releaseUntil(objc_object**) ()
    #125	(null) in objc_autoreleasePoolPop ()
    #126	(null) in _CFAutoreleasePoolPop ()
    #127	(null) in __CFRunLoopPerCalloutARPEnd ()
    #128	(null) in __CFRunLoopRun ()
    #129	(null) in CFRunLoopRunSpecific ()
    #130	(null) in GSEventRunModal ()
    #131	(null) in -[UIApplication _run] ()
    #132	(null) in UIApplicationMain ()
    #133	0x00000001002271a8 in main at /Users/adrian/Development/maparoni/Maparoni/helpers/OSLog+Loggers.swift:18
    
    
  • Anyway to get the TreeSitterTree inside the textView?

    Anyway to get the TreeSitterTree inside the textView?

    Hello, I'm integrating the Runestone framework into my rule editor, and it's awesome, especially the highlight and line number stuffs!

    And after integrating the syntax highlight, I try to do some analysis using the tree parsed by the tree-sitter, and I found the tree is stored in textView privately. So, is there any way to get that tree inside the textView?

This project Orchid-Fst implements a fast text string dictionary search data structure: Finite state transducer (short for FST) in c++ language.This FST C++ open source project has much significant advantages.
This project Orchid-Fst implements a fast text string dictionary search data structure: Finite state transducer (short for FST) in c++ language.This FST C++ open source project has much significant advantages.

Orchid-Fst 1. Project Overview This project Orchid-Fst implements a fast text string dictionary search data structure: Finite state transducer , which

Jul 25, 2022
An intrusive C++17 implementation of a Red-Black-Tree, a Weight Balanced Tree, a Dynamic Segment Tree and much more!

This is Ygg (short for Yggdrasil), a C++17 implementation of several intrusive data structures: several balanced binary search trees: a red-black Tree

Aug 3, 2022
data structures which I've tried to implement in C++, much more incoming 👨‍💻

This repository contains some data structures which I've tried to implement, much more incoming ??‍?? I'm definitely not a C++ expert, so the code is

Dec 21, 2021
This is a curve topology verification tool based on Fast Linking Numbers for Topology Verification of Loopy Structures.
This is a curve topology verification tool based on Fast Linking Numbers for Topology Verification of Loopy Structures.

Fast Linking Numbers This tool, called verifycurves, takes input models that consist of closed-loop curves, and outputs a topology certificate as a .t

Jan 26, 2022
C++ library to create dynamic structures in plain memory of shared-memory segments

Ищи описание на хабре @mrlolthe1st. #define _CRT_SECURE_NO_WARNINGS #include "shared_structures.h" #include <iostream> #include <fstream> #include <ca

Mar 30, 2022
Va1 is a simple character converter. It converts characters into nums, might be used in encryption protocols or as independent algorithm.
Va1 is a simple character converter. It converts characters into nums, might be used in encryption protocols or as independent algorithm.

Va1 What is it? Va1 is a simple character converter. It converts characters into nums, might be used in encryption protocols or as independent algorit

Dec 22, 2021
Like neofetch, but much faster because written in c. Only Linux.
Like neofetch, but much faster because written in c. Only Linux.

fastfetch fastfetch is a neofetch like tool for fetching system information and displaying them in a pretty way. It is written in c to achieve much be

Aug 5, 2022
tree-sitter parser and syntax highlighter for the Dwarf Fortress raw language
tree-sitter parser and syntax highlighter for the Dwarf Fortress raw language

tree-sitter-dfraw A simple language parser and highlighter made with tree-sitter tokyonight nightfly Using with nvim-treesitter Please refer to the ad

Apr 1, 2022
A library to convert Uber's H3 geo-index to LatLng vertices for Kotlin Multiplatform Mobile iOS and Android
A library to convert Uber's H3 geo-index to LatLng vertices for Kotlin Multiplatform Mobile iOS and Android

A library to convert Uber's H3 geo-index to LatLng vertices for Kotlin Multiplatform Mobile iOS and android Features Uber H3 in one interface Common i

Jul 12, 2022
High performance build system for Windows, OSX and Linux. Supporting caching, network distribution and more.

FASTBuild FASTBuild is a build system for Windows, OSX and Linux, supporting distributed compilation and object caching. It is used by many game devel

Aug 8, 2022
A collection of multiple types of lists used during pentesting, collected in one place. List types include usernames, passwords, combos, wordlist and may more..
A collection of multiple types of lists used during pentesting, collected in one place. List types include usernames, passwords, combos, wordlist and may more..

Access list is a collection of multiple types of lists used during pentesting, collected in one place, created by Undercode This list include a collec

Jan 6, 2022
A collection of libraries, data structures, and more that I have created to make coding in C less painful.

ctools A collection of libraries, data structures, and more that I have created to make coding in C less painful. Data structures There are many data

Nov 27, 2021
100daysofDSA - Explore about arrays, linked lists, stacks & queues, graphs, and more to master the foundations of data structures & algorithms!
100daysofDSA - Explore about arrays, linked lists, stacks & queues, graphs, and  more to master the foundations of data structures & algorithms!

Explore about arrays, linked lists, stacks & queues, graphs, and more to master the foundations of data structures & algorithms!

Jul 6, 2022
Nodable is node-able. The goal of Nodable is to provide an original hybrid source code editor, using both textual and nodal paradigm.
Nodable is node-able. The goal of Nodable is to provide an original hybrid source code editor, using both textual and nodal paradigm.

Nodable is node-able ! Introduction: The goal of Nodable is to provide an original hybrid source code editor, using both textual and nodal paradigm. I

Aug 6, 2022
heuristically and dynamically sample (more) uniformly from large decision trees of unknown shape

PROBLEM STATEMENT When writing a randomized generator for some file format in a general-purpose programming language, we can view the resulting progra

Feb 15, 2022
Open-source graph editor, with built-it step-by-step Dijkstra's Algorithm.
Open-source graph editor, with built-it step-by-step Dijkstra's Algorithm.

Visual Dijkstra - Simple visual graph editor, with built-in step-by-step Dijkstra's algorithm Visual Dijkstra is a free and open-source tool, designed

Jul 30, 2022
Flexible level editor

Tiled Map Editor - https://www.mapeditor.org/ About Tiled Tiled is a general purpose tile map editor for all tile-based games, such as RPGs, platforme

Aug 11, 2022
What the Package Does (One Line, Title Case)

inside The goal of insidecpp11 is to do fast point in polygon classification, with cpp11. We are comparing a few ways of implementing this, essentiall

Feb 11, 2022
What the Package Does (One Line, Title Case)

--- output: github_document --- <!-- README.md is generated from README.Rmd. Please edit that file --> ```{r, include = FALSE} knitr::opts_chunk$set

Feb 11, 2022