0

I am building a simple Mac App that has a NSTextView with very long string (15mb worth of text). I want to find a specific substring (a timestamp) and then scroll down to the location of the substring in the NSTextView.

Right now I am able to find the index of where the substring is but it is weirdly formatted. I used the top answer here: Index of a substring in a string with Swift

@IBOutlet weak var logTextView: NSScrollView!
@IBOutlet var logTextBox: NSTextView!

let searchString = "2018-03-29 19:10:17"
let baseString = logTextBox.string

let findIndex = baseString.endIndex(of: searchString)!
print(findIndex)


// Function I got from the stack overflow link
extension StringProtocol where Index == String.Index {
func index<T: StringProtocol>(of string: T, options: String.CompareOptions = []) -> Index? {
    return range(of: string, options: options)?.lowerBound
}
func endIndex<T: StringProtocol>(of string: T, options: String.CompareOptions = []) -> Index? {
    return range(of: string, options: options)?.upperBound
}
func indexes<T: StringProtocol>(of string: T, options: String.CompareOptions = []) -> [Index] {
    var result: [Index] = []
    var start = startIndex
    while start < endIndex, let range = range(of: string, options: options, range: start..<endIndex) {
        result.append(range.lowerBound)
        start = range.lowerBound < range.upperBound ? range.upperBound : index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? endIndex
    }
    return result
}
func ranges<T: StringProtocol>(of string: T, options: String.CompareOptions = []) -> [Range<Index>] {
    var result: [Range<Index>] = []
    var start = startIndex
    while start < endIndex, let range = range(of: string, options: options, range: start..<endIndex) {
        result.append(range)
        start = range.lowerBound < range.upperBound  ? range.upperBound : index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? endIndex
    }
    return result
}
}
gooberboobbutt
  • 797
  • 2
  • 8
  • 19
  • What is your question? "but it is weirdly formatted" Meaning? – Larme Apr 19 '18 at 18:35
  • It returns Index(_compoundOffset: 2773784, _cache: Swift.String.Index._Cache.utf16). I haven't really made much progress on this. My question is "I have a NSTextView with a very long string. How do I find a substring within the string and then scroll down to the location of the substring" – gooberboobbutt Apr 19 '18 at 18:39

1 Answers1

3

NSTextView is a subclass of NSText, and NSText defines a scrollRangeToVisible(NSRange) method. So you can use that for the scrolling part, but it requires an NSRange.

There's a poorly-documented NSRange initializer that can convert a Range to an NSRange. So:

let haystack = logTextBox.string
let needle = "2018-03-29 19:10:17"
if let needleRange = haystack.range(of: needle) {
    logTextBox.scrollRangeToVisible(NSRange(needleRange, in: haystack))
}
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • Hey Rob, how would I make it so if the timestamp didn't exist, it wouldn't do anything? – gooberboobbutt Apr 19 '18 at 19:31
  • 1
    Pretty sure the code in my answer does nothing if the needle isn't found in the haystack. – rob mayoff Apr 19 '18 at 19:51
  • Strange, when the needle isn't found in the haystack, the NSTextView no longer scrolls. After if I try to find a different needle within the haystack the app gets the never ending rotating beach ball. – gooberboobbutt Apr 19 '18 at 19:53
  • 1
    What is the stack trace of the main thread when you get the pinwheel? – rob mayoff Apr 19 '18 at 20:03
  • Not sure how to check that but it certainly crashed. To replicate the crash, I select a timestamp that isn't in the log, and then scrolling stops working (I think it scrolled too far down), and then I choose a previous timestamp and then the beachball pops up. To elaborate further, I will have 4 large log files opened up in different text views, and there is a NSPopUpButton that has timestamps that corresponds to times in one of the log files. When I select the timestamp I want all the textviews to scroll to the timestamp (or the nearest corresponding time) – gooberboobbutt Apr 19 '18 at 20:32
  • 1
    I guess from “and then scrolling stops working” that it is hung at that point. Pause in the debugger and look at the stack trace. – rob mayoff Apr 19 '18 at 20:35
  • log2TextBox.scrollRangeToVisible(NSRange(needleRange, in: haystack)) – gooberboobbutt Apr 19 '18 at 20:37
  • 0__CFStringIsGenderModifierCluster – gooberboobbutt Apr 19 '18 at 20:56
  • Rob, user error. Fixed the crashing, but it seems to still be scrolling even if the timestamp isn't there. Working on that right now – gooberboobbutt Apr 19 '18 at 21:00