// // AutocompleteService.swift // bitchat // // Handles autocomplete suggestions for mentions and commands // This is free and unencumbered software released into the public domain. // import Foundation /// Manages autocomplete functionality for chat final class AutocompleteService { private let mentionRegex = try? NSRegularExpression(pattern: "@([\\p{L}0-9_]*)$", options: []) /// 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 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)) 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).lowercased() let suggestions = peers .filter { $0.lowercased().hasPrefix(prefix) } .sorted() .prefix(5) .map { "@\($0)" } return suggestions.isEmpty ? nil : (Array(suggestions), fullRange) } private func needsArgument(command: String) -> Bool { switch command { case "/who", "/clear": return false default: return true } } }