I want to implement the following feature: In my NSTextView subclass I listen on option + arrow up and option + arrow down. Whenever these shortcuts are triggered the textviews selection should expand/collapse in the following manner:
- Cursor
- Current Word under cursor
- Text under cursor up to white space
- Current Sentence under cursor
- Current Paragraph under cursor
- Whole TextView content
My implementation seems to work however whenever I expand the selection I "overwrite" the cursor position and therefore I can't collapse the selection correctly again. Here is an example of what I want to achieve: https://caret.io/feat/feat-extend.gif
My current solution relies on finding the next bigger selection from the current selection and looks like this:
private func selectNextTextBlock() {
let location = insertionLocation ?? selectedRange().location
let wr = wordRange(at: location)
let wgr = wordGroupRange(at: location)
let sr = sentenceRange(at: location)
let pr = paragraphRange(at: location)
if self.selectedRange().length == 0 {
self.setSelectedRangesWithUndo([wr])
} else if self.selectedRange().length >= wr.length && self.selectedRange().length < wgr.length {
self.setSelectedRangesWithUndo([wgr])
} else if self.selectedRange().length >= wgr.length && self.selectedRange().length < sr.length {
self.setSelectedRangesWithUndo([sr])
} else if self.selectedRange().length >= sr.length && self.selectedRange().length < pr.length {
setSelectedRangesWithUndo([pr])
} else {
setSelectedRangesWithUndo([NSRange(location: 0, length: string.count)])
}
}
private func selectPreviousTextBlock() {
#warning("implement?")
}
private func wordRange(at location: Int) -> NSRange {
let proposedWordRange = super.selectionRange(forProposedRange: NSRange(location: location, length: 0), granularity: .selectByWord)
guard proposedWordRange.contains(location) else { return proposedWordRange }
// treat `.` and `:` as word delimiter
return (self.string as NSString).rangeOfCharacter(until: CharacterSet(charactersIn: ".:"), at: location, range: proposedWordRange)
}
private func wordGroupRange(at location: Int) -> NSRange {
let proposedWordRange = super.selectionRange(forProposedRange: NSRange(location: location, length: 0), granularity: .selectByParagraph)
guard proposedWordRange.contains(location) else { return proposedWordRange }
// treat `whitespaces and newlines` as word delimiter
return (self.string as NSString).rangeOfCharacter(until: CharacterSet.whitespacesAndNewlines, at: location, range: proposedWordRange)
}
private func sentenceRange(at location: Int) -> NSRange {
let proposedWordRange = super.selectionRange(forProposedRange: NSRange(location: location, length: 0), granularity: .selectByParagraph)
guard proposedWordRange.contains(location) else { return proposedWordRange }
// treat `.` and `:` as paragraph delimiter
var range = (self.string as NSString).rangeOfCharacter(until: CharacterSet(charactersIn: ".:"), at: location, range: proposedWordRange)
range.length += 1
return range
}
private func paragraphRange(at location: Int) -> NSRange {
return super.selectionRange(forProposedRange: NSRange(location: location, length: 0), granularity: .selectByParagraph)
}
Any hints for me on how to implement selectPreviousTextBlock
? Since the user can change the cursor position manually I can't guarantee to save the correct latest cursor position in selectNextTextBlock