-
-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Find feature. Setup NSTextFinderClient.
- Loading branch information
1 parent
6a12e46
commit da5e436
Showing
11 changed files
with
257 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// Created by Marcin Krzyzanowski | ||
// https://github.com/krzyzanowskim/STTextView/blob/main/LICENSE.md | ||
|
||
import Cocoa | ||
|
||
extension NSTextContentManager { | ||
|
||
var documentString: String { | ||
var result: String = "" | ||
result.reserveCapacity(1024 * 4) | ||
|
||
enumerateTextElements(from: nil, options: []) { textElement in | ||
if let textParagraph = textElement as? NSTextParagraph { | ||
result += textParagraph.attributedString.string | ||
} | ||
|
||
return true | ||
} | ||
return result | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
// Created by Marcin Krzyzanowski | ||
// https://github.com/krzyzanowskim/STTextView/blob/main/LICENSE.md | ||
|
||
import Cocoa | ||
|
||
final class STTextFinderClient: NSObject, NSTextFinderClient { | ||
|
||
weak var textView: STTextView? | ||
|
||
private var textContentManager: NSTextContentManager? { | ||
textView?.textContentStorage | ||
} | ||
|
||
var string: String { | ||
textView?.string ?? "" | ||
} | ||
|
||
func stringLength() -> Int { | ||
string.count | ||
} | ||
|
||
var isSelectable: Bool { | ||
textView?.isSelectable ?? false | ||
} | ||
|
||
public var allowsMultipleSelection: Bool { | ||
false | ||
} | ||
|
||
public var firstSelectedRange: NSRange { | ||
guard let firstTextSelectionRange = textView?.textLayoutManager.textSelections.first?.textRanges.first, | ||
let textContentManager = textContentManager else { | ||
return NSRange() | ||
} | ||
|
||
return NSRange(firstTextSelectionRange, in: textContentManager) | ||
} | ||
|
||
public var selectedRanges: [NSValue] { | ||
set { | ||
guard let textContentManager = textContentManager, | ||
let textLayoutManager = textView?.textLayoutManager as? STTextLayoutManager else { | ||
assertionFailure() | ||
return | ||
} | ||
|
||
let textRanges = newValue.map(\.rangeValue).compactMap { | ||
NSTextRange($0, in: textContentManager) | ||
} | ||
|
||
textLayoutManager.textSelections = [NSTextSelection(textRanges, affinity: .downstream, granularity: .character)] | ||
textView?.updateSelectionHighlights() | ||
} | ||
|
||
get { | ||
guard let textContentManager = textContentManager, | ||
let textLayoutManager = textView?.textLayoutManager, | ||
!textLayoutManager.textSelections.isEmpty | ||
else { | ||
return [] | ||
} | ||
|
||
return textLayoutManager.textSelections.flatMap(\.textRanges).compactMap({ NSRange($0, in: textContentManager) }).map({ NSValue(range: $0) }) | ||
} | ||
} | ||
|
||
var isEditable: Bool { | ||
textView?.isEditable ?? false | ||
} | ||
|
||
func scrollRangeToVisible(_ range: NSRange) { | ||
guard let textContentManager = textContentManager, | ||
let textRange = NSTextRange(range, in: textContentManager) | ||
else { | ||
return | ||
} | ||
textView?.scrollToSelection(NSTextSelection(range: textRange, affinity: .downstream, granularity: .character)) | ||
} | ||
|
||
var visibleCharacterRanges: [NSValue] { | ||
guard let viewportTextRange = textView?.textLayoutManager.textViewportLayoutController.viewportRange, | ||
let textContentManager = textContentManager else { | ||
return [] | ||
} | ||
|
||
return [NSRange(viewportTextRange, in: textContentManager)].map({ NSValue(range: $0) }) | ||
} | ||
|
||
func rects(forCharacterRange range: NSRange) -> [NSValue]? { | ||
guard let textContentManager = textContentManager, | ||
let textRange = NSTextRange(range, in: textContentManager) | ||
else { | ||
return nil | ||
} | ||
|
||
var rangeRects: [CGRect] = [] | ||
textView?.textLayoutManager.enumerateTextSegments(in: textRange, type: .standard, options: .rangeNotRequired, using: { _, rect, _, _ in | ||
rangeRects.append(rect.pixelAligned) | ||
return true | ||
}) | ||
|
||
return rangeRects.map({ NSValue(rect: $0) }) | ||
} | ||
|
||
func contentView(at index: Int, effectiveCharacterRange outRange: NSRangePointer) -> NSView { | ||
outRange.pointee = NSRange(location: 0, length: stringLength()) | ||
return textView! | ||
} | ||
|
||
func drawCharacters(in range: NSRange, forContentView view: NSView) { | ||
guard let textView = view as? STTextView, | ||
let textRange = NSTextRange(range, in: textView.textContentStorage), | ||
let context = NSGraphicsContext.current?.cgContext | ||
else { | ||
assertionFailure() | ||
return | ||
} | ||
|
||
if let layoutFragment = textView.textLayoutManager.textLayoutFragment(for: textRange.location) { | ||
layoutFragment.draw(at: layoutFragment.layoutFragmentFrame.origin, in: context) | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// Created by Marcin Krzyzanowski | ||
// https://github.com/krzyzanowskim/STTextView/blob/main/LICENSE.md | ||
|
||
import Cocoa | ||
|
||
extension STTextView { | ||
|
||
@objc func performFindPanelAction(_ sender: Any?) { | ||
performTextFinderAction(sender) | ||
} | ||
|
||
@objc open override func performTextFinderAction(_ sender: Any?) { | ||
guard let menuItem = sender as? NSMenuItem else { | ||
assertionFailure("Unexpected caller") | ||
return | ||
} | ||
|
||
|
||
textFinder.performAction(NSTextFinder.Action(rawValue: menuItem.tag)!) | ||
} | ||
|
||
} |
42 changes: 42 additions & 0 deletions
42
Sources/STTextView/STTextView+NSUserInterfaceValidations.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// Created by Marcin Krzyzanowski | ||
// https://github.com/krzyzanowskim/STTextView/blob/main/LICENSE.md | ||
|
||
import Cocoa | ||
|
||
extension STTextView: NSMenuItemValidation { | ||
|
||
public func validateMenuItem(_ menuItem: NSMenuItem) -> Bool { | ||
validateUserInterfaceItem(menuItem) | ||
} | ||
|
||
} | ||
|
||
extension STTextView: NSUserInterfaceValidations { | ||
|
||
public func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool { | ||
switch item.action { | ||
case #selector(undo(_:)): | ||
let result = allowsUndo ? undoManager?.canUndo ?? false : false | ||
|
||
// NSWindow does that like this, here (as debugged) | ||
if let undoManager = undoManager { | ||
(item as? NSMenuItem)?.title = undoManager.undoMenuItemTitle | ||
} | ||
|
||
return result | ||
case #selector(redo(_:)): | ||
let result = allowsUndo ? undoManager?.canRedo ?? false : false | ||
|
||
// NSWindow does that like this, here (as debugged) | ||
if let undoManager = undoManager { | ||
(item as? NSMenuItem)?.title = undoManager.redoMenuItemTitle | ||
} | ||
return result | ||
case #selector(performFindPanelAction(_:)), #selector(performTextFinderAction(_:)): | ||
return textFinder.validateAction(NSTextFinder.Action(rawValue: item.tag)!) | ||
default: | ||
return true | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.