Skip to content

Commit 0370691

Browse files
committed
Refactor LegacyEditorTextView
1 parent fa7f6b4 commit 0370691

File tree

3 files changed

+136
-133
lines changed

3 files changed

+136
-133
lines changed

CotEditor/Sources/EditorTextView.swift

+1-4
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,7 @@ class EditorTextView: NSTextView, Themable, CurrentLineHighlighting, MultiCursor
9191

9292
didSet {
9393
self.needsUpdateInsertionIndicators = true
94-
95-
if ProcessInfo.processInfo.operatingSystemVersion.majorVersion < 14 {
96-
self.updateInsertionPointTimer()
97-
}
94+
self.updateInsertionPointTimer()
9895
}
9996
}
10097
var selectionOrigins: [Int] = []

CotEditor/Sources/NSLayoutManager.swift

+18-16
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
//
99
// ---------------------------------------------------------------------------
1010
//
11-
// © 2018-2023 1024jp
11+
// © 2018-2024 1024jp
1212
//
1313
// Licensed under the Apache License, Version 2.0 (the "License");
1414
// you may not use this file except in compliance with the License.
@@ -229,6 +229,7 @@ extension NSLayoutManager {
229229
///
230230
/// - Parameter characterIndex: The character index.
231231
/// - Returns: One-pixel-width rects to draw insertion point in the layout manager coordinate.
232+
@available(macOS, deprecated: 14)
232233
final func insertionPointRects(at characterIndex: Int) -> [NSRect] {
233234

234235
guard
@@ -247,27 +248,13 @@ extension NSLayoutManager {
247248
}
248249

249250

250-
/// Returns the character indexes for the insertion points in the same line fragment of the given character index in display order.
251-
///
252-
/// - Parameter characterIndex: The character index of one character within the line fragment.
253-
/// - Returns: An array contains character indexes in display order.
254-
private func lineFragmentInsertionPointIndexes(forCharacterAt characterIndex: Int) -> [Int] {
255-
256-
let count = self.getLineFragmentInsertionPoints(forCharacterAt: characterIndex, alternatePositions: false, inDisplayOrder: true, positions: nil, characterIndexes: nil)
257-
var characterIndexes = [Int](repeating: 0, count: count)
258-
self.getLineFragmentInsertionPoints(forCharacterAt: characterIndex, alternatePositions: false, inDisplayOrder: true, positions: nil, characterIndexes: &characterIndexes)
259-
260-
return characterIndexes
261-
}
262-
263-
264251
/// Returns a rect to draw insertion point for the given character index.
265252
///
266253
/// - Parameters:
267254
/// - characterIndex: The character index.
268255
/// - alternate: If `true`, the secondary insertion point rect for split cursor will be returned.
269256
/// - Returns: An one-pixel-width rect to draw the insertion point in the layout manager coordinate, or `nil` if no alternate insertion point is provided.
270-
private func insertionPointRect(at characterIndex: Int, alternate: Bool) -> NSRect? {
257+
final func insertionPointRect(at characterIndex: Int, alternate: Bool = false) -> NSRect? {
271258

272259
assert(characterIndex >= 0)
273260

@@ -294,12 +281,27 @@ extension NSLayoutManager {
294281

295282
return NSRect(x: lineFragment.minX + position, y: lineFragment.minY, width: 1, height: lineFragment.height)
296283
}
284+
285+
286+
/// Returns the character indexes for the insertion points in the same line fragment of the given character index in display order.
287+
///
288+
/// - Parameter characterIndex: The character index of one character within the line fragment.
289+
/// - Returns: An array contains character indexes in display order.
290+
private func lineFragmentInsertionPointIndexes(forCharacterAt characterIndex: Int) -> [Int] {
291+
292+
let count = self.getLineFragmentInsertionPoints(forCharacterAt: characterIndex, alternatePositions: false, inDisplayOrder: true, positions: nil, characterIndexes: nil)
293+
var characterIndexes = [Int](repeating: 0, count: count)
294+
self.getLineFragmentInsertionPoints(forCharacterAt: characterIndex, alternatePositions: false, inDisplayOrder: true, positions: nil, characterIndexes: &characterIndexes)
295+
296+
return characterIndexes
297+
}
297298
}
298299

299300

300301
private extension UserDefaults {
301302

302303
/// Whether the user enables the system-wide "Use split cursor" option in System Settings > Keyboard > Text Input > Input Source.
304+
@available(macOS, deprecated: 14)
303305
var useSplitCursor: Bool { self.bool(forKey: "NSUseSplitCursor") }
304306
}
305307

CotEditor/Sources/NSTextView+MultiCursor.swift

+117-113
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
//
99
// ---------------------------------------------------------------------------
1010
//
11-
// © 2018-2023 1024jp
11+
// © 2018-2024 1024jp
1212
//
1313
// Licensed under the Apache License, Version 2.0 (the "License");
1414
// you may not use this file except in compliance with the License.
@@ -29,13 +29,14 @@ import AppKit
2929

3030
var insertionLocations: [Int] { get set }
3131
var selectionOrigins: [Int] { get set }
32-
33-
var insertionPointTimer: (any DispatchSourceTimer)? { get set }
34-
var insertionPointOn: Bool { get set }
3532
var isPerformingRectangularSelection: Bool { get }
3633

3734
@available(macOS 14, *)
3835
var insertionIndicators: [NSTextInsertionIndicator] { get set }
36+
37+
@available(macOS, deprecated: 14)
38+
var insertionPointTimer: (any DispatchSourceTimer)? { get set }
39+
var insertionPointOn: Bool { get set }
3940
}
4041

4142

@@ -58,13 +59,6 @@ extension MultiCursorEditing {
5859
}
5960

6061

61-
/// Whether the receiver needs to draw insertion points by itself.
62-
var needsDrawInsertionPoints: Bool {
63-
64-
self.insertionPointTimer?.isCancelled == false
65-
}
66-
67-
6862
/// Inserts the same string at multiple ranges.
6963
///
7064
/// - Parameters:
@@ -314,20 +308,6 @@ extension MultiCursorEditing {
314308
}
315309

316310

317-
/// Enables or disables `insertionPointTimer` according to the selection state.
318-
@available(macOS, deprecated: 14)
319-
func updateInsertionPointTimer() {
320-
321-
if #available(macOS 14, *) { return }
322-
323-
if self.isPerformingRectangularSelection || (!self.insertionLocations.isEmpty && self.selectedRanges.allSatisfy({ !$0.rangeValue.isEmpty })) {
324-
self.enableOwnInsertionPointTimer()
325-
} else {
326-
self.insertionPointTimer?.cancel()
327-
}
328-
}
329-
330-
331311
/// Adds new insertion points just above/below to the current insertions.
332312
///
333313
/// - Parameter affinity: The direction to add new ones; `.downstream` to add above, otherwise `.upstream`.
@@ -410,6 +390,8 @@ extension MultiCursorEditing {
410390

411391
guard !self.insertionLocations.isEmpty || !self.insertionIndicators.isEmpty else { return }
412392

393+
guard let layoutManager = self.layoutManager else { return assertionFailure() }
394+
413395
let properInsertionLocations = (self.isPerformingRectangularSelection && self.selectedRange.isEmpty) ? [self.selectedRange.location] : []
414396
let insertionLocations = (self.insertionLocations + properInsertionLocations)
415397

@@ -418,7 +400,8 @@ extension MultiCursorEditing {
418400
let isActive = (self.window?.firstResponder == self && NSApp.isActive)
419401

420402
self.insertionIndicators = insertionLocations
421-
.compactMap { self.insertionPointRects(at: $0).first } // ignore split cursors
403+
.compactMap { layoutManager.insertionPointRect(at: $0) } // ignore split cursors
404+
.map { $0.offset(by: self.textContainerOrigin) }
422405
.map { rect in
423406
if let indicator = indicators.popFirst() {
424407
indicator.frame = rect
@@ -433,53 +416,13 @@ extension MultiCursorEditing {
433416
}
434417

435418
// remove remaining indicators
436-
for indicator in indicators {
437-
indicator.removeFromSuperview()
438-
}
419+
indicators.forEach { $0.removeFromSuperview() }
439420
}
440421
}
441422

442423

443-
444-
/// Workaround subclass to let NSTextView uses the new NSTextInsertionIndicator (FB12964810).
445-
@available(macOS, deprecated: 14, message: "Just remove this subclass and also all the codes related to insertion point drawing.")
446-
final class LegacyEditorTextView: EditorTextView {
447-
448-
override func drawInsertionPoint(in rect: NSRect, color: NSColor, turnedOn flag: Bool) {
449-
450-
super.drawInsertionPoint(in: rect, color: color, turnedOn: flag)
451-
452-
// draw sub insertion rects
453-
self.insertionLocations
454-
.flatMap { self.insertionPointRects(at: $0) }
455-
.forEach { super.drawInsertionPoint(in: $0, color: color, turnedOn: flag) }
456-
}
457-
}
458-
459-
460-
461424
extension NSTextView {
462425

463-
/// Calculates rect for insertion point at `index`.
464-
///
465-
/// - Parameter index: The character index where the insertion point will locate.
466-
/// - Returns: Rect where insertion point filled.
467-
final func insertionPointRects(at index: Int) -> [NSRect] {
468-
469-
guard let layoutManager = self.layoutManager else { assertionFailure(); return [] }
470-
471-
let scale = self.scale
472-
return layoutManager.insertionPointRects(at: index)
473-
.map { $0.offset(by: self.textContainerOrigin) }
474-
.map { rect in
475-
NSRect(x: (rect.minX * scale).rounded(.down) / scale,
476-
y: rect.minY,
477-
width: 1 / scale,
478-
height: rect.height)
479-
}
480-
}
481-
482-
483426
/// Finds the location for the insertion point where one (visual) line above to the given insertion point location.
484427
///
485428
/// - Parameter index: The character index of the reference insertion point.
@@ -524,52 +467,6 @@ extension NSTextView {
524467

525468
// MARK: Private
526469

527-
private struct BlinkPeriod {
528-
529-
var on: Int
530-
var off: Int
531-
}
532-
533-
534-
private extension UserDefaults {
535-
536-
var textInsertionPointBlinkPeriod: BlinkPeriod {
537-
538-
let onPeriod = self.integer(forKey: "NSTextInsertionPointBlinkPeriodOn")
539-
let offPeriod = self.integer(forKey: "NSTextInsertionPointBlinkPeriodOff")
540-
541-
return BlinkPeriod(on: (onPeriod > 0) ? onPeriod : 500,
542-
off: (offPeriod > 0) ? offPeriod : 500)
543-
}
544-
}
545-
546-
547-
private extension MultiCursorEditing {
548-
549-
/// Enables insertion point blink timer to draw insertion points forcibly.
550-
@available(macOS, deprecated: 14)
551-
private func enableOwnInsertionPointTimer() {
552-
553-
guard self.insertionPointTimer?.isCancelled ?? true else { return }
554-
555-
let period = UserDefaults.standard.textInsertionPointBlinkPeriod
556-
557-
let timer = DispatchSource.makeTimerSource(queue: .main)
558-
timer.schedule(deadline: .now())
559-
timer.setEventHandler { [unowned self] in
560-
self.insertionPointOn.toggle()
561-
let interval = self.insertionPointOn ? period.on : period.off
562-
timer.schedule(deadline: .now() + .milliseconds(interval))
563-
self.setNeedsDisplay(self.visibleRect, avoidAdditionalLayout: true)
564-
}
565-
timer.resume()
566-
567-
self.insertionPointTimer?.cancel()
568-
self.insertionPointTimer = timer
569-
}
570-
}
571-
572-
573470
private extension NSLayoutManager {
574471

575472
/// Returns the bounds between upper and lower bounds of the given `range` in horizontal axis.
@@ -629,3 +526,110 @@ private extension NSLayoutManager {
629526
return rects.unique
630527
}
631528
}
529+
530+
531+
532+
// MARK: - LegacyEditorTextView
533+
534+
/// Workaround subclass to let NSTextView uses the new NSTextInsertionIndicator (FB12964810).
535+
@available(macOS, deprecated: 14, message: "Just remove this subclass and also all the codes related to insertion point drawing.")
536+
final class LegacyEditorTextView: EditorTextView {
537+
538+
override func drawInsertionPoint(in rect: NSRect, color: NSColor, turnedOn flag: Bool) {
539+
540+
super.drawInsertionPoint(in: rect, color: color, turnedOn: flag)
541+
542+
// draw sub insertion rects
543+
self.insertionLocations
544+
.flatMap { self.insertionPointRects(at: $0) }
545+
.forEach { super.drawInsertionPoint(in: $0, color: color, turnedOn: flag) }
546+
}
547+
}
548+
549+
550+
@available(macOS, deprecated: 14)
551+
extension MultiCursorEditing {
552+
553+
/// Whether the receiver needs to draw insertion points by itself.
554+
var needsDrawInsertionPoints: Bool {
555+
556+
self.insertionPointTimer?.isCancelled == false
557+
}
558+
559+
560+
/// Enables or disables `insertionPointTimer` according to the selection state.
561+
func updateInsertionPointTimer() {
562+
563+
if #available(macOS 14, *) { return }
564+
565+
if self.isPerformingRectangularSelection || (!self.insertionLocations.isEmpty && self.selectedRanges.allSatisfy({ !$0.rangeValue.isEmpty })) {
566+
self.enableOwnInsertionPointTimer()
567+
} else {
568+
self.insertionPointTimer?.cancel()
569+
}
570+
}
571+
572+
573+
/// Calculates rect for insertion point at `index`.
574+
///
575+
/// - Parameter index: The character index where the insertion point will locate.
576+
/// - Returns: Rect where insertion point filled.
577+
func insertionPointRects(at index: Int) -> [NSRect] {
578+
579+
guard let layoutManager = self.layoutManager else { assertionFailure(); return [] }
580+
581+
let scale = self.scale
582+
return layoutManager.insertionPointRects(at: index)
583+
.map { $0.offset(by: self.textContainerOrigin) }
584+
.map { rect in
585+
NSRect(x: (rect.minX * scale).rounded(.down) / scale,
586+
y: rect.minY,
587+
width: 1 / scale,
588+
height: rect.height)
589+
}
590+
}
591+
592+
593+
/// Enables insertion point blink timer to draw insertion points forcibly.
594+
private func enableOwnInsertionPointTimer() {
595+
596+
guard self.insertionPointTimer?.isCancelled ?? true else { return }
597+
598+
let period = UserDefaults.standard.textInsertionPointBlinkPeriod
599+
600+
let timer = DispatchSource.makeTimerSource(queue: .main)
601+
timer.schedule(deadline: .now())
602+
timer.setEventHandler { [unowned self] in
603+
self.insertionPointOn.toggle()
604+
let interval = self.insertionPointOn ? period.on : period.off
605+
timer.schedule(deadline: .now() + .milliseconds(interval))
606+
self.setNeedsDisplay(self.visibleRect, avoidAdditionalLayout: true)
607+
}
608+
timer.resume()
609+
610+
self.insertionPointTimer?.cancel()
611+
self.insertionPointTimer = timer
612+
}
613+
}
614+
615+
616+
@available(macOS, deprecated: 14)
617+
private struct BlinkPeriod {
618+
619+
var on: Int
620+
var off: Int
621+
}
622+
623+
624+
@available(macOS, deprecated: 14)
625+
private extension UserDefaults {
626+
627+
var textInsertionPointBlinkPeriod: BlinkPeriod {
628+
629+
let onPeriod = self.integer(forKey: "NSTextInsertionPointBlinkPeriodOn")
630+
let offPeriod = self.integer(forKey: "NSTextInsertionPointBlinkPeriodOff")
631+
632+
return BlinkPeriod(on: (onPeriod > 0) ? onPeriod : 500,
633+
off: (offPeriod > 0) ? offPeriod : 500)
634+
}
635+
}

0 commit comments

Comments
 (0)