Skip to content
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
13 changes: 12 additions & 1 deletion Sources/Fluid/Services/ASRService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2982,8 +2982,13 @@ final class ASRService: ObservableObject {
guard !trigger.isEmpty else { continue }

let escapedTrigger = NSRegularExpression.escapedPattern(for: trigger)
// Only apply `\b` word boundaries where the trigger edge is a word
// character. Punctuation-only triggers (e.g. "¿") have no adjacent
// word boundary, so `\b…\b` would never match them.
let leadingBoundary = (trigger.first?.isWordCharacter ?? false) ? "\\b" : ""
let trailingBoundary = (trigger.last?.isWordCharacter ?? false) ? "\\b" : ""
guard let regex = try? NSRegularExpression(
pattern: "\\b" + escapedTrigger + "\\b",
pattern: leadingBoundary + escapedTrigger + trailingBoundary,
options: .caseInsensitive
) else { continue }

Expand Down Expand Up @@ -3131,6 +3136,12 @@ private extension Character {
return false
}
}

/// Matches the regex `\w` class (letters, digits, or underscore) so we only
/// wrap dictionary triggers in `\b` boundaries where they actually apply.
var isWordCharacter: Bool {
self == "_" || self.isLetter || self.isNumber
}
}

// swiftlint:enable type_body_length
Expand Down
4 changes: 2 additions & 2 deletions Sources/Fluid/UI/CustomDictionaryView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,8 @@ struct CustomDictionaryView: View {
}

private var canAddManualReplacement: Bool {
// An empty replacement is allowed: it deletes the trigger from the transcript.
!self.manualTriggers.isEmpty &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve deletion rules in dictionary exports

When this new path allows a user to save trigger -> "", the entry works locally but is silently dropped by the dictionary transfer path: makeExportDocument() uses compactMap(Self.exportReplacement(from:)), and DictionaryTransferService.exportReplacement returns nil when to.isEmpty (lines 240-329). A user who exports/imports or migrates their dictionary after adding a deletion rule like ¿ -> ∅ will lose that rule, so the new feature is not reliably portable.

Useful? React with 👍 / 👎.

!self.manualReplacement.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty &&
self.manualDuplicateTriggers.isEmpty
}

Expand Down Expand Up @@ -2221,8 +2221,8 @@ struct EditDictionaryEntrySheet: View {
}

private var canSave: Bool {
// An empty replacement is allowed: it deletes the trigger from the transcript.
!self.parseTriggers().isEmpty &&
!self.replacement.trimmingCharacters(in: .whitespaces).isEmpty &&
self.duplicateTriggers.isEmpty
}

Expand Down