mirror of
https://github.com/coteditor/CotEditor.git
synced 2024-09-11 11:25:57 +03:00
Support fn+delete shortcut multi-cursor editing (fix #896)
This commit is contained in:
parent
7898c4570c
commit
6f5ec4dcfd
@ -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.
|
||||
|
@ -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?) {
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user