Build Status Platforms Documentation Matrix

IBeam

一个用于多光标支持的 Swift 库

特性

警告

仍处于早期阶段。特别是延迟评估仍在开发中。

集成

dependencies: [
    .package(url: "https://github.com/ChimeHQ/IBeam", branch: "main")
]

概念

MultiCursorState 类型接受两种事件来管理光标状态:InputOperationCursorOperation

InputOperation 类型模拟影响选择和文本状态的用户操作。这与 NSResponder 中的选择器密切相关。CursorOperation 类型模拟影响活动光标数量的操作。MultiCursorState 实例的客户端输入这两种类型的操作,并且该状态管理查询并将更改中继到其 TextSystemInterface 实例以执行这些操作。

为了支持大量光标,MultiCursorState 使用了一些技巧。 特别是,如果可以这样做而不会影响可见的用户状态,它可能会延迟、组合或以其他方式重新排序操作。 这些对于性能至关重要,但是您始终可以使用 ensureOperationsProcessed 方法强制一个完全最新的系统。

实现文本系统

IBeam 需要提供一个底层文本系统的接口。执行此操作所需的功能并非易事,尤其是在“范围”和“文本位置”的概念完全通用时。

如果您只是想将其连接到 AppKit/UIKit,可以使用 IBeamTextViewSystem。 它使用 Ligature 有效地实现所需的功能。 而且,由于该库在内部使用 Glyph 实现,因此它与 TextKit 1 和 2 兼容。

这是一项相当大的工作,但它不包含在此库中,原因有三:

如果您需要或想要实现自定义系统,请查看 TextSystemInterface 协议。它提供了很大的灵活性,尤其是在您的系统如何应用文本更改方面。

如果您使用的是 macOS 14.0 或更高版本,则可以使用 TextViewIndicatorState 类型来管理光标视图。

用法

这是一个使用 TextSystemCursorCoordinatorIBeamTextViewSystem 的示例,它们将所有内容连接在一起以用于 NSTextView。不幸的是,需要一个子类,但它相当简单。

这也使用了 KeyCodes 库,以简化修饰键的检查。

import AppKit

import KeyCodes
import IBeam

extension KeyModifierFlags {
    var addingCursor: Bool {
        subtracting(.numericPad) == [.control, .shift]
    }
}

open class MultiCursorTextView: NSTextView {
    private lazy var coordinator = TextSystemCursorCoordinator(
        textView: self,
        system: IBeamTextViewSystem(textView: self)
    )

    public var operationProcessor: (InputOperation) -> Void = { _ in }
    public var cursorOperationHandler: (CursorOperation<NSRange>) -> Void = { _ in }

    override public init(frame frameRect: NSRect, textContainer: NSTextContainer?) {
        super.init(frame: frameRect, textContainer: textContainer)

        self.operationProcessor = coordinator.processOperation
        self.cursorOperationHandler = coordinator.mutateCursors
    }

    required public init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

extension MultiCursorTextView {
    open override func insertText(_ input: Any, replacementRange: NSRange) {
        // also should handle replacementRange values

        let attrString: AttributedString

        switch input {
        case let string as String:
            let container = AttributeContainer(typingAttributes)

            attrString = AttributedString(string, attributes: container)
        case let string as NSAttributedString:
            attrString = AttributedString(string)
        default:
            fatalError("This API should be called with NSString or NSAttributedString only")
        }

        operationProcessor(.insertText(attrString))
    }

    open override func doCommand(by selector: Selector) {
        if let op = InputOperation(selector: selector) {
            operationProcessor(op)
            return
        }

        super.doCommand(by: selector)
    }

    // this enable correct routing for the mouse down
    open override func menu(for event: NSEvent) -> NSMenu? {
        if event.keyModifierFlags?.addingCursor == true {
            return nil
        }

        return super.menu(for: event)
    }

    open override func mouseDown(with event: NSEvent) {
        guard event.keyModifierFlags?.addingCursor == true else {
            super.mouseDown(with: event)
            return
        }

        let point = convert(event.locationInWindow, from: nil)
        let index = characterIndexForInsertion(at: point)
        let range = NSRange(index..<index)

        cursorOperationHandler(.add(range))
    }

    open override func keyDown(with event: NSEvent) {
        let flags = event.keyModifierFlags?.subtracting(.numericPad) ?? []
        let key = event.keyboardHIDUsage

        switch (flags, key) {
        case ([.control, .shift], .keyboardUpArrow):
            cursorOperationHandler(.addAbove)
        case ([.control, .shift], .keyboardDownArrow):
            cursorOperationHandler(.addBelow)
        default:
            super.keyDown(with: event)
        }
    }
}

当然,您也可以自定义所有内容。 如果您想使用自定义 TextSystemInterface 实现,或者只是想更好地控制视图与其光标的交互方式,则这是必需的。

贡献与协作

我很乐意听到您的意见!问题或拉取请求都很好。 Matrix 空间Discord 可用于实时帮助,但我强烈倾向于以文档的形式回答。您也可以在 mastodon 上找到我。

我更喜欢协作,并且如果您有类似的项目,我很乐意找到合作方式。

我喜欢使用制表符进行缩进以提高可访问性。但是,我宁愿您使用您想要的系统并提出 PR,也不愿您因为空格而犹豫。

参与此项目即表示您同意遵守贡献者行为准则