Skip to content
Merged
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: 3 additions & 1 deletion App/BlurtiOS/BlurtiOSCore/KeyboardModel+Phase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ extension KeyboardModel {
SharedStore.remove(forKey: BlurtShared.Key.result)
// Joined against the live text before the cursor, not the press-time
// snapshot: the user may have typed since.
proxy.insertText(InsertionSeparator.withLeadingSeparator(result.text, after: proxy.documentContextBeforeInput))
let inserted = InsertionSeparator.withLeadingSeparator(result.text, after: proxy.documentContextBeforeInput)
proxy.insertText(inserted)
lastInsertion = inserted
resultLandedAt = Date()
// Not typing, so not the term field's to claim.
if termDraft != nil { termHostBaseline = proxy.documentContextBeforeInput ?? "" }
Expand Down
13 changes: 12 additions & 1 deletion App/BlurtiOS/BlurtiOSCore/KeyboardModel+Typing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,28 @@ extension KeyboardModel {
/// highlighted: the picture follows at once, not on the host's next
/// notification.
private func typedIntoHost() {
lastInsertion = nil
readSelection()
}

/// Delete right after a dictation takes back all of it — but only while the
/// caret still sits at its end with nothing highlighted. A moved caret, a
/// selection, or a host that shows less context than the dictation's length
/// falls back to deleting one character.
package func deleteBackward() {
UIDevice.current.playInputClick()
if termDraft != nil {
_ = termDraft?.popLast()
updateShift()
return
}
proxy?.deleteBackward()
if let inserted = lastInsertion, let proxy, (proxy.selectedText ?? "").isEmpty,
proxy.documentContextBeforeInput?.hasSuffix(inserted) == true
{
for _ in inserted { proxy.deleteBackward() }
} else {
proxy?.deleteBackward()
}
typedIntoHost()
}

Expand Down
3 changes: 3 additions & 0 deletions App/BlurtiOS/BlurtiOSCore/KeyboardModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ package final class KeyboardModel {
@ObservationIgnored package var gate = DictationKeyGate()
@ObservationIgnored package let clockStart = ContinuousClock.now
@ObservationIgnored package var lastResultID: UUID?
/// What the last dictation put in the field, separator included, so one
/// delete takes the whole of it back. Any keystroke into the host clears it.
@ObservationIgnored package var lastInsertion: String?
@ObservationIgnored private var heartbeat: Task<Void, Never>?
@ObservationIgnored package var lastSpaceAt: ContinuousClock.Instant?
@ObservationIgnored package var noticeDwell: Task<Void, Never>?
Expand Down
39 changes: 39 additions & 0 deletions App/BlurtiOS/BlurtiOSTests/ProtocolTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,45 @@ struct KeyboardModelProtocolTests {
#expect(proxy.before == "Hi one four")
}

@Test("delete right after a dictation takes all of it back; otherwise one character")
func deleteTakesBackDictation() {
let suite = ScratchSuite()
defer { suite.tearDown() }
let rig = KeyboardRig()
let model = rig.model
let proxy = rig.proxy
func deliver(_ text: String) {
SharedStore.write(
DictationResult(id: UUID(), text: text, deliveredAt: Date(), recipient: nil),
forKey: BlurtShared.Key.result)
model.resultArrived()
}
proxy.before = "Hi"
deliver("there friend")
#expect(proxy.before == "Hi there friend")
model.deleteBackward()
#expect(proxy.before == "Hi")
// Only once: the next delete is a character again.
model.deleteBackward()
#expect(proxy.before == "H")

// Typing after the dictation makes delete a character again.
deliver("again")
model.type("!")
model.deleteBackward()
#expect(proxy.before == "H again")

// So does a caret that no longer sits at the dictation's end, or a selection.
deliver("moved")
proxy.before += "x"
model.deleteBackward()
#expect(proxy.before == "H again moved")
deliver("chosen")
proxy.selected = "chosen"
model.deleteBackward()
#expect(proxy.before == "H again moved chose")
}

@Test("the term field never claims a cursor move or a selection")
func termFieldIgnoresCursorMoves() {
let suite = ScratchSuite()
Expand Down
Loading