Support fn+delete shortcut multi-cursor editing (fix #896)

This commit is contained in:
1024jp 2019-01-08 23:25:34 +09:00
parent 7898c4570c
commit 6f5ec4dcfd
3 changed files with 85 additions and 9 deletions

View File

@ -5,6 +5,11 @@ Change Log
3.7.0-beta.3 (unreleasd)
--------------------------
### Fixes
- [beta] Support `fn+delete` / `^D` shortcut multi-cursor editing.
### Fixes
- [beta] Fix an issue where auto-inserted tab did not expand to spaces on auto indent level adjustment.

View File

@ -491,19 +491,80 @@ extension EditorTextView {
/// add insertion point just below the last selected range (^)
@IBAction func selectColumnDown(_ sender: Any?) {
guard
let layoutManager = self.layoutManager,
let textContainer = self.textContainer
else { assertionFailure(); return }
let ranges = self.insertionRanges
let baseRange = ranges.last!
let lowerBound = self.lowerInsertionLocation(of: baseRange.lowerBound)
let upperBound = self.lowerInsertionLocation(of: baseRange.upperBound)
let range = NSRange(lowerBound..<upperBound)
let newRanges = layoutManager.verticalRanges(in: NSRange(ranges.first!.lowerBound..<ranges.last!.upperBound), baseRange: ranges[0], in: textContainer)
let insertionRanges = ranges + [range]
guard let set = self.prepareForSelectionUpdate(insertionRanges) else { return }
guard let set = self.prepareForSelectionUpdate(newRanges) else { return }
self.setSelectedRanges(set.selectedRanges, affinity: .upstream, stillSelecting: false)
self.insertionLocations = set.insertionLocations
self.scrollRangeToVisible(range)
self.scrollRangeToVisible(newRanges.last!)
}
}
extension NSLayoutManager {
func verticalRanges(in range: NSRange, baseRange: NSRange, in textContainer: NSTextContainer) -> [NSRange] {
let glyphRange = self.glyphRange(forCharacterRange: baseRange, actualCharacterRange: nil)
let lowerRect = self.boundingRect(forGlyphRange: NSRange(location: glyphRange.lowerBound, length: 0), in: textContainer)
let upperRect = self.boundingRect(forGlyphRange: NSRange(location: glyphRange.upperBound, length: 0), in: textContainer)
let baseRect = NSRect(x: min(lowerRect.minX, upperRect.minX), y: 0,
width: abs(lowerRect.minX - upperRect.minX), height: 1)
var ranges: [NSRange] = []
var targetRect: NSRect = .zero
self.enumeratelineFragmentUsedRects(in: range) { (rect) in
targetRect = baseRect.offsetBy(dx: 0, dy: rect.midY)
// guard rect.intersects(targetRect) else { return }
ranges.append(self.characterRange(for: targetRect, in: textContainer))
}
assert(!ranges.isEmpty)
targetRect.origin.y = targetRect.maxY + targetRect.height
moof(targetRect)
ranges.append(self.characterRange(for: targetRect, in: textContainer))
moof(ranges)
return ranges
}
private func characterRange(for rect: NSRect, in textContainer: NSTextContainer) -> NSRange {
let lowerGlyphIndex = self.glyphIndex(for: NSPoint(x: rect.minX, y: rect.minY), in: textContainer)
let upperGlyphIndex = self.glyphIndex(for: NSPoint(x: rect.maxX, y: rect.minY), in: textContainer)
return self.characterRange(forGlyphRange: NSRange(lowerGlyphIndex..<upperGlyphIndex), actualGlyphRange: nil)
}
private func enumeratelineFragmentUsedRects(in characterRange: NSRange, body: (_ usedLineRect: NSRect) -> Void) {
let glyphRange = self.glyphRange(forCharacterRange: characterRange, actualCharacterRange: nil)
// enumerate visible line numbers
var glyphIndex = glyphRange.lowerBound
repeat { // process logical lines
var effectiveRange = NSRange.notFound
let rect = self.lineFragmentUsedRect(forGlyphAt: glyphIndex, effectiveRange: &effectiveRange, withoutAdditionalLayout: true)
body(rect)
glyphIndex = effectiveRange.upperBound
} while (glyphIndex < glyphRange.upperBound)
guard glyphRange.upperBound == self.numberOfGlyphs else { return }
body(self.extraLineFragmentUsedRect)
}
}
@ -514,6 +575,16 @@ extension EditorTextView {
extension EditorTextView {
/// delete forward (fn+delete / ^D)
override func deleteForward(_ sender: Any?) {
guard self.hasMultipleInsertions else { return super.deleteForward(sender) }
self.moveForwardAndModifySelection(sender)
self.deleteBackward(sender)
}
/// delete to the end of logical line (^K)
override func deleteToEndOfParagraph(_ sender: Any?) {

View File

@ -80,7 +80,7 @@ extension NSString {
let range = NSRange(location: location + 1, length: 0)
return self.rangeOfComposedCharacterSequences(for: range).upperBound
return self.rangeOfComposedCharacterSequences(for: range).lowerBound
}