using System.Text; namespace EchoHub.Client.UI.Helpers; /// /// Recognizes a dragged-and-dropped file (or files) that a terminal delivers into the input as an /// absolute path. Terminals differ: some paste the whole path at once, others send it character by /// character; either way this checks whether the current input text resolves to existing file(s). /// public static class DroppedFileParser { /// /// Cheap pre-check so callers only stat the filesystem when the input plausibly holds a path: /// a quoted path, a Windows drive path (X:\/X:/), a UNC path (\\), or a /// POSIX absolute path (/). Normal chat text never starts this way. /// public static bool LooksLikePath(string text) { var t = text.TrimStart(); if (t.Length < 3) return false; if (t[0] is '"' or '/') return true; if (t.StartsWith(@"\\", StringComparison.Ordinal)) return true; return char.IsLetter(t[0]) && t[1] == ':' && (t[2] == '\\' || t[2] == '/'); } /// /// Returns true when resolves to one or more existing files. /// Handles a single path (quoted or not, possibly containing spaces) and multiple /// space-separated (optionally quoted) paths. is injectable /// for testing; production passes . /// public static bool TryGetFiles(string text, out List files, Func? fileExists = null) { fileExists ??= File.Exists; files = []; var trimmed = text.Trim(); if (trimmed.Length < 3 || trimmed.Length > 4096 || trimmed.Contains('\n')) return false; // Single path, possibly quoted and/or containing spaces. var unquoted = StripQuotes(trimmed); if (Path.IsPathFullyQualified(unquoted) && fileExists(unquoted)) { files.Add(unquoted); return true; } // Multiple files: space-separated tokens, each optionally quoted. foreach (var token in TokenizeQuoted(trimmed)) { if (!Path.IsPathFullyQualified(token) || !fileExists(token)) { files.Clear(); return false; } files.Add(token); } return files.Count > 0; } private static string StripQuotes(string s) => s.Length >= 2 && ((s[0] == '"' && s[^1] == '"') || (s[0] == '\'' && s[^1] == '\'')) ? s[1..^1] : s; private static IEnumerable TokenizeQuoted(string input) { var current = new StringBuilder(); var quote = '\0'; foreach (var c in input) { if (quote != '\0') { if (c == quote) quote = '\0'; else current.Append(c); } else if (c is '"' or '\'') { quote = c; } else if (c == ' ') { if (current.Length > 0) { yield return current.ToString(); current.Clear(); } } else { current.Append(c); } } if (current.Length > 0) yield return current.ToString(); } }