一个用于多光标支持的 Swift 库
特性
NSTextView
和 UITextView
的支持警告
仍处于早期阶段。特别是延迟评估仍在开发中。
dependencies: [
.package(url: "https://github.com/ChimeHQ/IBeam", branch: "main")
]
MultiCursorState
类型接受两种事件来管理光标状态:InputOperation
和 CursorOperation
。
InputOperation
类型模拟影响选择和文本状态的用户操作。这与 NSResponder
中的选择器密切相关。CursorOperation
类型模拟影响活动光标数量的操作。MultiCursorState
实例的客户端输入这两种类型的操作,并且该状态管理查询并将更改中继到其 TextSystemInterface
实例以执行这些操作。
为了支持大量光标,MultiCursorState
使用了一些技巧。 特别是,如果可以这样做而不会影响可见的用户状态,它可能会延迟、组合或以其他方式重新排序操作。 这些对于性能至关重要,但是您始终可以使用 ensureOperationsProcessed
方法强制一个完全最新的系统。
IBeam 需要提供一个底层文本系统的接口。执行此操作所需的功能并非易事,尤其是在“范围”和“文本位置”的概念完全通用时。
如果您只是想将其连接到 AppKit/UIKit,可以使用 IBeamTextViewSystem。 它使用 Ligature 有效地实现所需的功能。 而且,由于该库在内部使用 Glyph 实现,因此它与 TextKit 1 和 2 兼容。
这是一项相当大的工作,但它不包含在此库中,原因有三:
如果您需要或想要实现自定义系统,请查看 TextSystemInterface
协议。它提供了很大的灵活性,尤其是在您的系统如何应用文本更改方面。
如果您使用的是 macOS 14.0 或更高版本,则可以使用 TextViewIndicatorState
类型来管理光标视图。
这是一个使用 TextSystemCursorCoordinator
和 IBeamTextViewSystem
的示例,它们将所有内容连接在一起以用于 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,也不愿您因为空格而犹豫。
参与此项目即表示您同意遵守贡献者行为准则。