CodeEditSourceEditor Documentation

repository·main·Indexed 20 days ago

https://github.com/codeeditapp/codeeditsourceeditor

A high-performance, tree-sitter-powered code editor component for macOS applications. It provides an Xcode-like editing experience with support for both SwiftUI and AppKit, featuring syntax highlighting, code completion, find and replace, text diff, and minimaps. The library includes the SourceEditor SwiftUI view, TextViewController for AppKit, and a TextViewCoordinator API for injecting custom behavior and logic into the editor.

Tokens
4.3K
Snippets
8
Records
11
Agent score
71%

What's inside CodeEditSourceEditor

  1. Overview of CodeEditSourceEditor

    main

    CodeEditSourceEditor is an Xcode-inspired code editor view written in Swift. It provides advanced text editing features including syntax highlighting (powered by tree-sitter), code completion, find and replace, text diff, validation, current line highlighting, minimaps, inline messages (warnings/errors), and bracket matching.

    It provides both AppKit and SwiftUI components and optionally relies on CodeEditLanguages for tree-sitter-based syntax highlighting.

  2. How TextView Coordinators work

    main

    A TextViewCoordinator is an abstraction used to push messages from underlying editor components into SwiftUI. This allows you to update UI related to the editor's state (like cursor position) without passing numerous callbacks through the CodeEditSourceEditor initializer.

    Coordinators can also be used to receive detailed text editing notifications by conforming to the TextViewDelegate protocol from the CodeEditTextView package.

    Key Lifecycle Stages:

    1. Initialization: You initialize the coordinator yourself.
    2. Registration: You pass the coordinator to CodeEditSourceEditor.
    3. Preparation: prepareCoordinator(controller:) is called by the editor.
    4. Notification: The coordinator receives events (e.g., text changes, selection changes).
    5. Teardown: When the editor is closed, destroy() is called, and the editor stops referencing the coordinator.
  3. How TextViewCoordinators work for custom behavior

    main

    For complex features that require direct access to the underlying text view or text storage, use the TextViewCoordinator API. By implementing the TextViewCoordinator protocol, you can inject custom behavior into the editor as events occur (e.g., when text changes) without manually managing state or bindings. This is useful for implementing features like custom autocompletion or specialized text transformations.

    To use a coordinator:

    1. Create a class conforming to TextViewCoordinator.
    2. Implement necessary methods like prepareCoordinator(controller:) or textViewDidChangeText(controller:).
    3. Pass an instance of your coordinator into the coordinators array of the SourceEditor view.
    final class AutoCompleteCoordinator: TextViewCoordinator {
        func prepareCoordinator(controller: TextViewController) { }
    
        func textViewDidChangeText(controller: TextViewController) {
            // Access controller.textView or controller.text to perform custom logic
            for cursorPosition in controller.cursorPositions where cursorPosition.range.location >= 5 {
                let location = cursorPosition.range.location
                let previousRange = NSRange(start: location - 5, end: location)
                let string = (controller.text as NSString).substring(with: previousRange)
    
                if string.lowercased() == "hello" {
                    controller.textView.replaceCharacters(in: NSRange(location: location, length: 0), with: " world!")
                }
            }
        }
    }
  4. Explore CodeEditSourceEditor Topics

    main

    The library is organized into several key functional areas:

    Text View

    Core components for rendering and managing the editor, including:

    • SourceEditorView: The primary view interface.
    • SourceEditor: The main editor object.
    • SourceEditorConfiguration: Configuration settings for the editor.
    • SourceEditorState: State management for the editor.
    • TextViewController: Controller for text operations.
    • GutterView: The gutter area (typically for line numbers and breakpoints).

    Themes

    • EditorTheme: Defines the visual appearance of the editor.

    Text Coordinators

    Logic for managing text interactions and synchronization:

    • TextViewCoordinator: Base coordinator for text views.
    • CombineCoordinator: Coordinator utilizing Combine for reactive updates.

    Cursors

    • CursorPosition: Represents the location of the cursor within the text.
  5. Extend editor behavior with TextViewCoordinator

    main

    If you need to access the underlying NSTextView or NSTextStorage directly to implement complex features (like custom autocompletion or text manipulation), use the TextViewCoordinator API.

    By implementing TextViewCoordinator and passing the instance into the coordinators array of SourceEditor (SwiftUI) or TextViewController (AppKit), you can intercept editor events.

    Commonly used methods include:

    • prepareCoordinator(controller:): Setup logic when the coordinator is initialized.
    • textViewDidChangeText(controller:): Triggered when the text content changes. You can access the controller.textView and controller.text to perform manipulations.
    class AutoCompleteCoordinator: TextViewCoordinator {
        func prepareCoordinator(controller: TextViewController) { }
    
        func textViewDidChangeText(controller: TextViewController) {
            for cursorPosition in controller.cursorPositions.reversed() where cursorPosition.range.location >= 5 {
                let location = cursorPosition.range.location
                let previousRange = NSRange(start: location - 5, end: location)
                let string = (controller.text as NSString).substring(with: previousRange)
    
                if string.lowercased() == "hello" {
                    controller.textView.replaceCharacters(in: NSRange(location: location, length: 0), with: " world!")
                }
            }
        }
    }
  6. Use CodeEditSourceEditor in SwiftUI

    main

    The SwiftUI API allows you to embed the editor into your views using the SourceEditor component. It supports two-way bindings for text, editor state (cursor position, scroll position, find panel text), and configuration (theme, font, indentation).

    Key components for SwiftUI integration:

    • $text: A binding to the editor's content.
    • language: The language definition for the editor.
    • configuration: A SourceEditorConfiguration object to set appearance (theme and font) and behavior (indentation options).
    • state: A binding to a SourceEditorState object for tracking cursor and scroll positions.
    • coordinators: An array of TextViewCoordinator instances for injecting custom logic.
    import CodeEditSourceEditor
    
    struct ContentView: View {
        @State var text = "let x = 1.0"
        @State var editorState = SourceEditorState()
        @State var theme = EditorTheme(...)
        @State var font = NSFont.monospacedSystemFont(ofSize: 11, weight: .regular)
        @State var indentOption = .spaces(count: 4)
    
        var body: some View {
            SourceEditor(
                $text,
                language: language,
                configuration: SourceEditorConfiguration(
                    appearance: .init(theme: theme, font: font),
                    behavior: .init(indentOption: indentOption)
                ),
                state: $editorState,
                coordinators: []
            )
        }
    }
  7. Use the SwiftUI API for SourceEditor

    main

    For SwiftUI applications, use the SourceEditor view. It provides a fast, efficient API with two-way bindings for state management.

    Key features include:

    • State Management: Use SourceEditorState to automatically track and control cursor positions, scroll positions, and find panel text via two-way bindings.
    • Performance: For large documents, use an NSTextStorage object instead of a String to avoid unnecessary SwiftUI view updates.
    • Configuration: Customize appearance (theme, font), behavior (indentation), layout (overscroll), and peripherals (minimap) using SourceEditorConfiguration.
    • Extensibility: Inject custom behavior using TextViewCoordinator objects passed into the coordinators parameter.
    import CodeEditSourceEditor
    
    struct ContentView: View {
        @State var text = "let x = 1.0"
        @State var editorState = SourceEditorState()
        @State var theme = EditorTheme(...)
        @State var font = NSFont.monospacedSystemFont(ofSize: 11, weight: .regular)
        @State var indentOption = .spaces(count: 4)
        @State var editorOverscroll = 0.3
        @State var showMinimap = true
        @State var autoCompleteCoordinator = AutoCompleteCoordinator()
    
        var body: some View {
            SourceEditor(
                $text,
                language: .swift,
                configuration: SourceEditorConfiguration(
                    appearance: .init(theme: theme, font: font),
                    behavior: .init(indentOption: indentOption),
                    layout: .init(editorOverscroll: editorOverscroll),
                    peripherals: .init(showMinimap: showMinimap)
                ),
                state: $editorState,
                coordinators: [autoCompleteCoordinator]
            )
        }
    
        class AutoCompleteCoordinator: TextViewCoordinator {
            func prepareCoordinator(controller: TextViewController) { }
    
            func textViewDidChangeText(controller: TextViewController) {
                // Custom logic here
            }
        }
    }
  8. Use the AppKit API with TextViewController

    main

    For AppKit applications, use TextViewController to manage the editor. You can initialize it with content, language settings, and a SourceEditorConfiguration.

    To display the editor:

    1. Initialize a TextViewController.
    2. Add it as a child view controller to your parent controller.
    3. Add the editorController.view to your view hierarchy.
    4. Call editorController.view.viewDidMoveToSuperview() to ensure proper setup.

    TextViewController supports advanced features like custom highlightProviders, undoManager integration, completionDelegate for code suggestions, and jumpToDefinitionDelegate.

    // 1. Initialize the controller
    let editorController = TextViewController(
        string: "let x = 10;",
        language: .swift,
        config: SourceEditorConfiguration(
            appearance: .init(theme: theme, font: font),
            behavior: .init(indentOption: .spaces(count: 4)),
            layout: .init(editorOverscroll: 0.3),
            peripherals: .init(showMinimap: true)
        ),
        cursorPositions: [CursorPosition(line: 0, column: 0)],
        highlightProviders: [],
        undoManager: nil,
        coordinators: [],
        completionDelegate: nil,
        jumpToDefinitionDelegate: nil
    )
    
    // 2. Add to view hierarchy
    final class MyController: NSViewController {
        override func loadView() {
            super.loadView()
            addChild(editorController)
            view.addSubview(editorController.view)
            editorController.view.viewDidMoveToSuperview()
        }
    }
  9. Implement a TextViewCoordinator

    main

    To add custom functionality to the editor, create a class that conforms to the TextViewCoordinator protocol.

    Use prepareCoordinator(controller:) for setup, such as holding a weak reference to the TextViewController or setting up delegates. Always implement destroy() to release resources, nil out weak variables, or remove delegates to prevent memory leaks.

    class MyCoordinator: TextViewCoordinator {
        func prepareCoordinator(controller: TextViewController) {
            // Do any setup, such as keeping a (weak) reference to the controller
        }
    
        func destroy() {
            // Release any resources, `nil` any weak variables, remove delegates, etc.
        }
    }
  10. Handle text and selection changes in a Coordinator

    main

    You can implement specific methods in your TextViewCoordinator to react to editor state changes:

    • textViewDidChangeText(controller:): Called when the text is updated.
    • textViewDidChangeSelection(controller:newPositions:): Called when the cursor or selection changes.
    class MyCoordinator: TextViewCoordinator {
        func prepareCoordinator(controller: TextViewController) { /* ... */ }
    
        func textViewDidChangeText(controller: TextViewController) {
            // Text was updated.
        }
    
        func textViewDidChangeSelection(controller: TextViewController, newPositions: [CursorPosition]) {
            // Selections were changed
        }
    
        func destroy() { /* ... */ }
    }
  11. Conform to TextViewDelegate for detailed text notifications

    main

    If your coordinator conforms to the TextViewDelegate protocol (from the CodeEditTextView package), it will receive forwarded delegate messages from the editor's underlying text view.

    Supported methods:

    • textView(_:willReplaceContentsIn:with:)
    • textView(_:didReplaceContentsIn:with:)

    Note: You will not receive textView(_:shouldReplaceContentsIn:with:) via the coordinator.

    // Requires conformance to TextViewDelegate from CodeEditTextView
    func textView(_ textView: TextView, willReplaceContentsIn range: NSRange, with string: String)
    func textView(_ textView: TextView, didReplaceContentsIn range: NSRange, with string: String)