From 2be79766cf9f997872bb1de5045abc9f7a3493ac Mon Sep 17 00:00:00 2001 From: Ayush7614 Date: Sun, 2 Aug 2026 01:15:11 +0530 Subject: [PATCH 1/2] Compile mention autocomplete regex through SafeRegex. try? NSRegularExpression silently disabled @-mentions on compile failure; SafeRegex logs and returns a never-matching pattern instead, matching MessageFormattingEngine. --- bitchat/Services/AutocompleteService.swift | 46 +++++++++++----------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/bitchat/Services/AutocompleteService.swift b/bitchat/Services/AutocompleteService.swift index 6c55f34e..b6483c8d 100644 --- a/bitchat/Services/AutocompleteService.swift +++ b/bitchat/Services/AutocompleteService.swift @@ -2,7 +2,7 @@ // AutocompleteService.swift // bitchat // -// Handles autocomplete suggestions for mentions and commands +// Handles autocomplete suggestions for mentions // This is free and unencumbered software released into the public domain. // @@ -10,49 +10,49 @@ import Foundation /// Manages autocomplete functionality for chat final class AutocompleteService { - private let mentionRegex = try? NSRegularExpression(pattern: "@([\\p{L}0-9_]*)$", options: []) + /// Compiled via `SafeRegex` so a bad pattern logs and disables matching + /// instead of silently returning nil forever (`try?` did the latter). + private let mentionRegex = SafeRegex.compile("@([\\p{L}0-9_]*)$") /// Get autocomplete suggestions for current text func getSuggestions(for text: String, peers: [String], cursorPosition: Int) -> (suggestions: [String], range: NSRange?) { let textToPosition = String(text.prefix(cursorPosition)) - - // Check for mention autocomplete + + // Mentions only — slash-command suggestions are owned by + // `CommandSuggestionsView` / the composer, not this service. if let (mentionSuggestions, mentionRange) = getMentionSuggestions(textToPosition, peers: peers) { return (mentionSuggestions, mentionRange) } - - // Don't handle command autocomplete here - ContentView handles it with better UI - // if let (commandSuggestions, commandRange) = getCommandSuggestions(textToPosition) { - // return (commandSuggestions, commandRange) - // } - + return ([], nil) } - + /// Apply selected suggestion to text func applySuggestion(_ suggestion: String, to text: String, range: NSRange) -> String { guard let textRange = Range(range, in: text) else { return text } - + var replacement = suggestion - + // Add space after command if it takes arguments if suggestion.hasPrefix("/") && needsArgument(command: suggestion) { replacement += " " } - + return text.replacingCharacters(in: textRange, with: replacement) } - + // MARK: - Private Methods - + private func getMentionSuggestions(_ text: String, peers: [String]) -> ([String], NSRange)? { - guard let regex = mentionRegex else { return nil } - let nsText = text as NSString - let matches = regex.matches(in: text, options: [], range: NSRange(location: 0, length: nsText.length)) - + let matches = mentionRegex.matches( + in: text, + options: [], + range: NSRange(location: 0, length: nsText.length) + ) + guard let match = matches.last else { return nil } - + let fullRange = match.range(at: 0) let captureRange = match.range(at: 1) let prefix = nsText.substring(with: captureRange).normalizedNickname.lowercased() @@ -62,10 +62,10 @@ final class AutocompleteService { .sorted() .prefix(5) .map { "@\($0)" } - + return suggestions.isEmpty ? nil : (Array(suggestions), fullRange) } - + private func needsArgument(command: String) -> Bool { switch command { case "/who", "/clear": From b8f495d3623d5bbbf6e222d28bf5c0d90dd2bffe Mon Sep 17 00:00:00 2001 From: Ayush7614 Date: Mon, 10 Aug 2026 17:01:48 +0530 Subject: [PATCH 2/2] Route mention autocomplete through shared SafeRegex pattern. Add MessageFormattingEngine.Patterns.mentionAutocomplete and use it from AutocompleteService so mention matching compiles through the same fail-closed helper as message formatting. --- bitchat/Services/AutocompleteService.swift | 6 ++---- bitchat/Services/MessageFormattingEngine.swift | 3 +++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/bitchat/Services/AutocompleteService.swift b/bitchat/Services/AutocompleteService.swift index b6483c8d..3e739e74 100644 --- a/bitchat/Services/AutocompleteService.swift +++ b/bitchat/Services/AutocompleteService.swift @@ -10,9 +10,7 @@ import Foundation /// Manages autocomplete functionality for chat final class AutocompleteService { - /// Compiled via `SafeRegex` so a bad pattern logs and disables matching - /// instead of silently returning nil forever (`try?` did the latter). - private let mentionRegex = SafeRegex.compile("@([\\p{L}0-9_]*)$") + private typealias Patterns = MessageFormattingEngine.Patterns /// Get autocomplete suggestions for current text func getSuggestions(for text: String, peers: [String], cursorPosition: Int) -> (suggestions: [String], range: NSRange?) { @@ -45,7 +43,7 @@ final class AutocompleteService { private func getMentionSuggestions(_ text: String, peers: [String]) -> ([String], NSRange)? { let nsText = text as NSString - let matches = mentionRegex.matches( + let matches = Patterns.mentionAutocomplete.matches( in: text, options: [], range: NSRange(location: 0, length: nsText.length) diff --git a/bitchat/Services/MessageFormattingEngine.swift b/bitchat/Services/MessageFormattingEngine.swift index 88a32e3c..1578f16b 100644 --- a/bitchat/Services/MessageFormattingEngine.swift +++ b/bitchat/Services/MessageFormattingEngine.swift @@ -43,6 +43,9 @@ final class MessageFormattingEngine { static let mention = SafeRegex.compile("@([\\p{L}0-9_]+(?:#[a-fA-F0-9]{4})?)") + /// Trailing partial `@nickname` at the composer cursor — autocomplete only. + static let mentionAutocomplete = SafeRegex.compile("@([\\p{L}0-9_]*)$") + static let cashu = SafeRegex.compile("\\bcashu[AB][A-Za-z0-9._-]{40,}\\b") static let bolt11 = SafeRegex.compile("(?i)\\bln(bc|tb|bcrt)[0-9][a-z0-9]{50,}\\b")