You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NSTextView能否实现多插入点?自定义多选择移动行为方案

自定义NSTextView多选择/多插入点行为

我尝试修改NSTextView的多选择行为,具体是重写moveLeft:方法,让它不会将所有选区折叠到第一个位置,而是保留每个选区的起始位置。最初的实现代码如下:

@objc override func moveLeft(_ sender: Any?) {
    if selectedRanges.count < 2 {
        super.moveLeft(sender)
        return
    }
    let ranges = selectedRanges.map { NSValue(range: NSMakeRange($0.rangeValue.location, 0)) }
    setSelectedRanges(ranges, affinity: .downstream, stillSelecting: false)
}

但修改后仍有其他场景会触发默认的选区行为。请问:长度为0的选区仅支持单选择场景吗?有没有无需自行实现完整文本系统的解决方案?


编辑:查阅官方文档后发现相关规定:

数组中的对象是已排序、不重叠、不连续的,且(除单个选区的情况外)必须是非零长度的

我这里提供一个临时解决方案供参考:

class MultiCaretView: NSTextView {
    
    // 追踪多个选区,包括零长度的情况
    private var caretRanges: [NSValue]? = nil
    
    var multipleCarets: Bool { caretRanges != nil }
    var multipleSelections: Bool { selectedRanges.count > 1 }

    // 停止处理多插入点的操作
    @objc override func moveDown(_ sender: Any?) {
        caretRanges = nil
        super.moveDown(sender)
    }

    @objc override func moveUp(_ sender: Any?) {
        caretRanges = nil
        super.moveUp(sender)
    }

    // 处理多插入点向右移动
    @objc override func moveRight(_ sender: Any?) {
        guard multipleCarets || multipleSelections else { return super.moveRight(sender) }
        let selection = caretRanges ?? selectedRanges
        
        func mvRight(_ range: NSRange) -> NSRange {
            return if range.length == 0 {
                NSMakeRange(range.location + 1, 0)
            } else {
                NSMakeRange(range.location + range.length, 0)
            }
        }
        
        caretRanges = selection.map { NSValue(range: mvRight($0.rangeValue)) }
        setSelectedRanges(caretRanges!, affinity: .downstream, stillSelecting: false)
        needsDisplay = true
    }

    @objc override func moveRightAndModifySelection(_ sender: Any?) {
        guard multipleCarets || multipleSelections else {
            return super.moveRightAndModifySelection(sender)
        }
        let selection = caretRanges ?? selectedRanges
        
        func mvRightAndModifySelection(_ range: NSRange) -> NSRange {
            NSMakeRange(range.location, range.length + 1)
        }

        caretRanges = selection.map { NSValue(range: mvRightAndModifySelection($0.rangeValue)) }
        setSelectedRanges(caretRanges!, affinity: .downstream, stillSelecting: false)
    }
    
    // 其他方向的操作可依此类推...
    
    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)
        // 强制绘制自定义多插入点(无闪烁效果)
        guard let caretRanges else { return }
        guard let layoutManager else { return }
        guard let textContainer else { return }
        for range in caretRanges {
            if range.rangeValue.length == 0 {
                let glyphRange = layoutManager.glyphRange(
                    forCharacterRange: range.rangeValue,
                    actualCharacterRange: nil
                )
                let rect = layoutManager.boundingRect(
                    forGlyphRange: glyphRange,
                    in: textContainer
                )
                let caret = NSBezierPath(rect: NSInsetRect(rect, -1, 0))
                insertionPointColor.setFill()
                caret.fill()
            }
        }
    }
}

多插入点应用场景示例

比如可以通过少量按键快速修改YAML格式:

原始内容:

yellow: '#b58900'
orange: '#cb4b16'
red:    '#dc322f'

修改后内容:

yellow:  &yellow  '#b58900'
orange:  &orange  '#cb4b16'
red:     &red     '#dc322f'

内容的提问来源于stack exchange,提问作者user1007047

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 15:24:51