Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend TextProcessing with receive/lose focus callbacks #352

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Proton/Sources/Swift/Editor/EditorView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1451,10 +1451,14 @@ extension EditorView: RichTextViewDelegate {
}

func richTextView(_ richTextView: RichTextView, didReceiveFocusAt range: NSRange) {
let executableProcessors = textProcessor?.filteringExecutableOn(editor: self) ?? []
executableProcessors.forEach { $0.didReceiveFocus(editor: self) }
AggregateEditorViewDelegate.editor(self, didReceiveFocusAt: range)
}

func richTextView(_ richTextView: RichTextView, didLoseFocusFrom range: NSRange) {
let executableProcessors = textProcessor?.filteringExecutableOn(editor: self) ?? []
executableProcessors.forEach { $0.didLoseFocus(editor: self) }
AggregateEditorViewDelegate.editor(self, didLoseFocusFrom: range)
}

Expand Down
12 changes: 12 additions & 0 deletions Proton/Sources/Swift/TextProcessors/TextProcessing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,16 @@ public protocol TextProcessing {
/// - newRange: Current range after the change
func selectedRangeChanged(editor: EditorView, oldRange: NSRange?, newRange: NSRange?)

/// Notifies the processor when `editor` receives focus.
/// - Note: This function is also called when focus moves between `EditorView`'s nested editors.
/// - Parameter editor: `EditorView` which received focus
func didReceiveFocus(editor: EditorView)

/// Notifies the processor when `editor` loses focus.
/// - Note: This function is also called when focus moves between `EditorView`'s nested editors.
/// - Parameter editor: `EditorView` which lost focus
func didLoseFocus(editor: EditorView)

/// Invoked after the text has been processed in the `Editor`.
/// - Parameter editor: EditorView in which text is changed.
func didProcess(editor: EditorView)
Expand Down Expand Up @@ -134,4 +144,6 @@ public extension TextProcessing {
func shouldProcess(_ editorView: EditorView, shouldProcessTextIn range: NSRange, replacementText text: String) -> Bool { return true }
func willProcessEditing(editor: EditorView, editedMask: NSTextStorage.EditActions, range editedRange: NSRange, changeInLength delta: Int) { }
func didProcessEditing(editor: EditorView, editedMask: NSTextStorage.EditActions, range editedRange: NSRange, changeInLength delta: Int) { }
func didReceiveFocus(editor: EditorView) { }
func didLoseFocus(editor: EditorView) { }
}
32 changes: 32 additions & 0 deletions Proton/Tests/Editor/EditorViewDelegateTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,38 @@ class EditorViewDelegateTests: XCTestCase {
try assertKeyPress(.tab, replacementText: "\t")
}

func testNotifiesTextProcessorsOnDidReceiveFocus() {
let expectation = functionExpectation()
let mockProcessor = MockTextProcessor()
mockProcessor.onDidReceiveFocus = { _ in
expectation.fulfill()
}
let editor = EditorView()
editor.textProcessor?.register(mockProcessor)
let richTextView = editor.richTextView
let richTextViewDelegate = richTextView.richTextViewDelegate

richTextViewDelegate?.richTextView(richTextView, didReceiveFocusAt: .zero)

waitForExpectations(timeout: 1.0)
}

func testNotifiesTextProcessorsOnDidLoseFocus() {
let expectation = functionExpectation()
let mockProcessor = MockTextProcessor()
mockProcessor.onDidLoseFocus = { _ in
expectation.fulfill()
}
let editor = EditorView()
editor.textProcessor?.register(mockProcessor)
let richTextView = editor.richTextView
let richTextViewDelegate = richTextView.richTextViewDelegate

richTextViewDelegate?.richTextView(richTextView, didLoseFocusFrom: .zero)

waitForExpectations(timeout: 1.0)
}

private func assertKeyPress(_ key: EditorKey, replacementText: String, file: StaticString = #file, line: UInt = #line) throws {
let delegateExpectation = functionExpectation()
let delegate = MockEditorViewDelegate()
Expand Down
10 changes: 10 additions & 0 deletions Proton/Tests/TextProcessors/Mocks/MockTextProcessor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class MockTextProcessor: TextProcessing {
var onKeyWithModifier: ((EditorView, EditorKey, UIKeyModifierFlags, NSRange) -> Void)?
var onProcessInterrupted: ((EditorView, NSRange) -> Void)?
var onSelectedRangeChanged: ((EditorView, NSRange?, NSRange?) -> Void)?
var onDidReceiveFocus: ((EditorView) -> Void)?
var onDidLoseFocus: ((EditorView) -> Void)?
var onDidProcess: ((EditorView) -> Void)?
var onShouldProcess: ((EditorView, NSRange, String) -> Bool)?

Expand Down Expand Up @@ -72,6 +74,14 @@ class MockTextProcessor: TextProcessing {
onSelectedRangeChanged?(editor, oldRange, newRange)
}

func didReceiveFocus(editor: EditorView) {
onDidReceiveFocus?(editor)
}

func didLoseFocus(editor: EditorView) {
onDidLoseFocus?(editor)
}

func didProcess(editor: EditorView) {
onDidProcess?(editor)
}
Expand Down
22 changes: 22 additions & 0 deletions Proton/Tests/TextProcessors/TextProcessorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,28 @@ class TextProcessorTests: XCTestCase {
waitForExpectations(timeout: 1.0)
}

func testPreventsDidReceiveFocusOnSetAttributedText() throws {
let expectation = expectation(description: "Should not invoke didReceiveFocus")
expectation.isInverted = true
try assertProcessorInvocationOnSetAttributedText(expectation, isRunOnSettingText: false) { mockProcessor in
mockProcessor.onDidReceiveFocus = { _ in
expectation.fulfill()
}
}
waitForExpectations(timeout: 1.0)
}

func testPreventsDidLoseFocusOnSetAttributedText() throws {
let expectation = expectation(description: "Should not invoke didLoseFocus")
expectation.isInverted = true
try assertProcessorInvocationOnSetAttributedText(expectation, isRunOnSettingText: false) { mockProcessor in
mockProcessor.onDidLoseFocus = { _ in
expectation.fulfill()
}
}
waitForExpectations(timeout: 1.0)
}

func testInvokesTextProcessor() {
let testExpectation = functionExpectation()
let editor = EditorView()
Expand Down
Loading