Skip to content

A SwiftUI Code Editor Package for iOS and macOS built around TreeSitter

License

Notifications You must be signed in to change notification settings

blaineam/KeyStone

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

98 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Keystone

A powerful, cross-platform code editor component for SwiftUI
Syntax highlighting β€’ Line numbers β€’ Bracket matching β€’ Find & Replace β€’ And more!

iOS macOS Swift SPM License CI


✨ Used In Production

Keystone powers the code editing features in Enter Space β€” a powerful cloud storage client for iOS and macOS that supports SFTP, WebDAV, S3, and 50+ cloud providers.


πŸš€ Features

Core Editor

  • Syntax Highlighting β€” Support for 15+ programming languages including Swift, Python, JavaScript, TypeScript, HTML, CSS, JSON, and more via TreeSitter
  • Line Numbers β€” Configurable line number gutter with current line highlighting
  • Bracket Matching β€” Automatic detection and highlighting of matching brackets, parentheses, and braces
  • Character Pair Insertion β€” Auto-insert closing quotes, brackets, and parentheses
  • Line Wrapping β€” Toggle between wrapped and horizontal scrolling modes
  • Current Line Highlighting β€” Visual indicator for the line containing the cursor

Text Intelligence

  • Line Ending Detection β€” Automatically detects LF, CRLF, or CR line endings
  • Line Ending Conversion β€” Convert between different line ending formats
  • Indentation Detection β€” Detects whether the file uses tabs or spaces
  • Tab Key Support β€” Configurable tab behavior (insert tab or spaces)

Visual Customization

  • 11 Built-in Themes β€” Including System (adaptive), Xcode Light/Dark, GitHub Light, Solarized Light, One Light, Tomorrow, Monokai, Dracula, Nord, and Gruvbox Dark
  • Configurable Font Size β€” Adjustable editor font size (8-32pt)
  • Line Height β€” Adjustable line spacing multiplier (1.0x to 2.0x)
  • Invisible Characters β€” Optional display of tabs, spaces, and line breaks

Advanced Features

  • Find & Replace β€” Full-featured find and replace with regex support, case sensitivity, and whole word matching
  • Code Folding β€” Collapse and expand code regions based on syntax structure
  • Undo/Redo History β€” Full undo/redo support
  • TreeSitter Integration β€” Advanced syntax analysis with incremental parsing for real-time highlighting
  • Comment Toggle β€” Toggle line/block comments with Cmd+/ (macOS) or toolbar button

UI Components

  • Status Bar β€” Shows cursor position, line count, line ending type, and indentation settings
  • Settings View β€” Pre-built settings UI for all editor configuration options
  • Symbol Keyboard (iOS) β€” Accessory keyboard with programming symbols and a Tab key

πŸ“‹ Requirements

Requirement Version
iOS 17.0+
macOS 14.0+
Swift 5.9+
Xcode 15.0+

πŸ“¦ Installation

Swift Package Manager

Add Keystone to your project using Swift Package Manager:

  1. In Xcode, go to File β†’ Add Package Dependencies...
  2. Enter the repository URL:
    https://github.com/blaineam/KeyStone
    
  3. Select your version rules and click Add Package

Or add it to your Package.swift:

dependencies: [
    .package(url: "https://github.com/blaineam/KeyStone", from: "2.0.0")
]

Then add "Keystone" as a dependency to your target:

.target(
    name: "YourApp",
    dependencies: ["Keystone"]
)

⚑ Quick Start

Basic Usage

import SwiftUI
import Keystone

struct ContentView: View {
    @State private var code = """
    func greet(name: String) {
        print("Hello, \\(name)!")
    }

    greet(name: "World")
    """
    @StateObject private var config = KeystoneConfiguration()

    var body: some View {
        KeystoneEditor(
            text: $code,
            language: .swift,
            configuration: config
        )
    }
}

With Status Bar

import SwiftUI
import Keystone

struct EditorWithStatusBar: View {
    @State private var code = "// Your code here"
    @State private var cursorPosition = CursorPosition()
    @StateObject private var config = KeystoneConfiguration()

    var body: some View {
        VStack(spacing: 0) {
            KeystoneEditor(
                text: $code,
                language: .swift,
                configuration: config,
                onCursorChange: { position in
                    cursorPosition = position
                }
            )

            EditorStatusBar(
                cursorPosition: cursorPosition,
                lineCount: code.components(separatedBy: "\n").count,
                configuration: config,
                hasUnsavedChanges: false
            )
        }
    }
}

Built-in Toolbar with Settings

KeystoneEditor includes a built-in toolbar with undo/redo, find/replace, line numbers, word wrap, invisible characters, and a settings button that opens the full settings panel.

import SwiftUI
import Keystone

struct EditorView: View {
    @State private var code = "// Your code here"
    @StateObject private var config = KeystoneConfiguration()
    @StateObject private var findReplace = FindReplaceManager()

    var body: some View {
        KeystoneEditor(
            text: $code,
            language: .swift,
            configuration: config,
            findReplaceManager: findReplace
        )
    }
}

The toolbar provides access to:

  • Undo/Redo β€” With full undo history
  • Find & Replace β€” Toggle the find/replace bar
  • Go to Line β€” Jump to specific line:column
  • Line Numbers β€” Toggle visibility
  • Word Wrap β€” Toggle line wrapping
  • Invisible Characters β€” Show tabs/spaces
  • Settings β€” Opens the full settings sheet with themes, indentation, line endings, and more

βš™οΈ Configuration

KeystoneConfiguration

The main configuration object for the editor. It's an ObservableObject that can be shared and persisted.

let config = KeystoneConfiguration()

// Appearance
config.fontSize = 14.0                    // Font size in points (8-32)
config.lineHeightMultiplier = 1.4         // Line spacing multiplier (1.0-2.0)
config.showLineNumbers = true             // Show/hide line number gutter
config.highlightCurrentLine = true        // Highlight the current line
config.showInvisibleCharacters = false    // Show tabs, spaces, line breaks
config.lineWrapping = true                // Enable/disable line wrapping

// Behavior
config.autoInsertPairs = true             // Auto-insert closing brackets/quotes
config.highlightMatchingBrackets = true   // Highlight matching bracket pairs
config.tabKeyInsertsTab = true            // Tab key inserts tab vs spaces

// Indentation
config.indentation = IndentationSettings(type: .spaces, width: 4)

// Line Endings
config.lineEnding = .lf                   // LF, CRLF, or CR

// Theme
config.theme = .monokai                   // Syntax highlighting theme

Available Themes

Theme Description
KeystoneTheme.system System-aware adaptive dark/light theme
KeystoneTheme.xcodeLight Xcode Light
KeystoneTheme.xcodeDark Xcode Dark
KeystoneTheme.githubLight GitHub Light
KeystoneTheme.solarizedLight Solarized Light
KeystoneTheme.oneLight Atom One Light
KeystoneTheme.tomorrowLight Tomorrow Light
KeystoneTheme.monokai Classic Monokai dark theme
KeystoneTheme.dracula Dracula dark theme
KeystoneTheme.nord Nord dark theme
KeystoneTheme.gruvboxDark Gruvbox Dark

Creating Custom Themes

let customTheme = KeystoneTheme(
    keyword: Color(hex: "#c678dd"),
    type: Color(hex: "#e5c07b"),
    string: Color(hex: "#98c379"),
    comment: Color(hex: "#5c6370"),
    number: Color(hex: "#d19a66"),
    function: Color(hex: "#61afef"),
    tag: Color(hex: "#e06c75"),
    attribute: Color(hex: "#d19a66"),
    operator: Color(hex: "#56b6c2"),
    property: Color(hex: "#e5c07b")
)

🌐 Supported Languages

Language Extensions
Swift .swift
Python .py
JavaScript .js, .mjs, .cjs
TypeScript .ts, .tsx
C .c, .h
C++ .cpp, .hpp, .cc, .cxx
Go .go
Rust .rs
Ruby .rb
HTML .html, .htm
CSS .css, .scss, .sass
JSON .json
YAML .yaml, .yml
Markdown .md, .markdown
Shell .sh, .bash, .zsh
Config .conf, .ini
Plain Text .txt

πŸ“– API Reference

KeystoneEditor

The main editor view component with built-in toolbar and settings.

public struct KeystoneEditor: View {
    public init(
        text: Binding<String>,
        language: KeystoneLanguage = .plainText,
        configuration: KeystoneConfiguration,
        findReplaceManager: FindReplaceManager,
        cursorPosition: Binding<CursorPosition>? = nil,
        scrollToCursor: Binding<Bool>? = nil,
        showGoToLine: Binding<Bool>? = nil,
        isTailFollowEnabled: Binding<Bool>? = nil,
        onCursorChange: ((CursorPosition) -> Void)? = nil,
        onScrollChange: ((CGFloat) -> Void)? = nil,
        onTextChange: ((String) -> Void)? = nil,
        onToggleTailFollow: (() -> Void)? = nil,
        onConvertLineEndings: ((LineEnding) -> Void)? = nil,
        onConvertIndentation: ((IndentationSettings) -> Void)? = nil
    )
}

The editor includes a built-in toolbar with settings button. When conversion callbacks are provided, they are called after the built-in conversion is complete (useful for marking documents as unsaved).

EditorStatusBar

A status bar showing cursor position and file settings.

public struct EditorStatusBar: View {
    public init(
        cursorPosition: CursorPosition,
        lineCount: Int,
        configuration: KeystoneConfiguration,
        hasUnsavedChanges: Bool = false,
        onSettingsTap: (() -> Void)? = nil
    )
}

EditorSettingsView

A pre-built settings panel for editor configuration. Platform-optimized (Form on iOS, GroupBox on macOS).

public struct EditorSettingsView: View {
    public init(
        configuration: KeystoneConfiguration,
        isPresented: Binding<Bool>,
        onConvertLineEndings: ((LineEnding) -> Void)? = nil,
        onConvertIndentation: ((IndentationSettings) -> Void)? = nil
    )
}

Note: When using KeystoneEditor, settings are accessible via the built-in toolbar. Use EditorSettingsView directly only if you need standalone settings (e.g., in a preferences window).

SymbolKeyboard (iOS only)

A keyboard accessory with programming symbols.

public struct SymbolKeyboard: View {
    public init(
        indentString: String = "    ",
        onSymbol: @escaping (String) -> Void
    )
}

Utility Types

// Cursor position information
public struct CursorPosition {
    public var line: Int           // 1-based line number
    public var column: Int         // 1-based column number
    public var offset: Int         // Character offset from start
    public var selectionLength: Int // Number of selected characters
}

// Line ending types
public enum LineEnding {
    case lf      // Unix/macOS (\n)
    case crlf    // Windows (\r\n)
    case cr      // Classic Mac (\r)

    static func detect(in text: String) -> LineEnding
    static func convert(_ text: String, to ending: LineEnding) -> String
}

// Indentation settings
public struct IndentationSettings {
    public var type: IndentationType  // .tabs or .spaces
    public var width: Int             // Number of spaces (1-8)
    public var indentString: String   // The actual indent string

    static func detect(in text: String) -> IndentationSettings
}

πŸ—οΈ Architecture

Keystone/
β”œβ”€β”€ Configuration/
β”‚   β”œβ”€β”€ KeystoneConfiguration.swift
β”‚   └── KeystoneTheme.swift
β”œβ”€β”€ Features/
β”‚   β”œβ”€β”€ CodeFoldingManager.swift
β”‚   β”œβ”€β”€ FindReplaceManager.swift
β”‚   └── UndoHistory.swift
β”œβ”€β”€ Platform/
β”‚   └── PlatformTypes.swift
β”œβ”€β”€ Syntax/
β”‚   β”œβ”€β”€ KeystoneLanguage.swift
β”‚   β”œβ”€β”€ SyntaxHighlighter.swift
β”‚   └── TreeSitterHighlighter.swift
β”œβ”€β”€ Types/
β”‚   β”œβ”€β”€ BracketMatching.swift
β”‚   β”œβ”€β”€ CursorPosition.swift
β”‚   β”œβ”€β”€ Indentation.swift
β”‚   └── LineEnding.swift
└── Views/
    β”œβ”€β”€ KeystoneEditor.swift
    β”œβ”€β”€ KeystoneTextView.swift
    β”œβ”€β”€ EditorStatusBar.swift
    β”œβ”€β”€ EditorSettingsView.swift
    └── SymbolKeyboard.swift

πŸ™ Acknowledgments

Keystone was heavily inspired by the excellent Runestone library by Simon B. StΓΈvring. Keystone builds upon Runestone's foundation with a focus on feature expansion and full cross-platform support for both iOS and macOS.


πŸ“„ License

Keystone is available under the MIT License. See the LICENSE file for more information.


🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

About

A SwiftUI Code Editor Package for iOS and macOS built around TreeSitter

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages