CommandSearchControlHelper.cs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. using OneOf;
  2. using OneOf.Types;
  3. using PixiEditor.Helpers;
  4. using PixiEditor.Models.Commands;
  5. using PixiEditor.Models.Commands.Search;
  6. using PixiEditor.ViewModels;
  7. using SkiaSharp;
  8. using System.IO;
  9. using System.Text.RegularExpressions;
  10. namespace PixiEditor.Views.UserControls.CommandSearch;
  11. #nullable enable
  12. internal static class CommandSearchControlHelper
  13. {
  14. public static (List<SearchResult> results, List<string> warnings) ConstructSearchResults(string query)
  15. {
  16. List<SearchResult> newResults = new();
  17. List<string> warnings = new();
  18. warnings.Add("haha warning");
  19. if (string.IsNullOrWhiteSpace(query))
  20. {
  21. // show all recently opened
  22. newResults.AddRange(ViewModelMain.Current.FileSubViewModel.RecentlyOpened
  23. .Select(file => (SearchResult)new FileSearchResult(file.FilePath)
  24. {
  25. SearchTerm = query
  26. }));
  27. return (newResults, warnings);
  28. }
  29. var controller = CommandController.Current;
  30. // add matching colors
  31. MaybeParseColor(query).Switch(
  32. (SKColor color) =>
  33. {
  34. newResults.Add(new ColorSearchResult(color)
  35. {
  36. SearchTerm = query
  37. });
  38. },
  39. (Error _) => warnings.Add("Invalid color"),
  40. static (None _) => { }
  41. );
  42. // add matching commands
  43. newResults.AddRange(
  44. controller.Commands
  45. .Where(x => x.Description.Contains(query, StringComparison.OrdinalIgnoreCase))
  46. .OrderByDescending(x => x.Description.Contains($" {query} ", StringComparison.OrdinalIgnoreCase))
  47. .Take(12)
  48. .Select(command => new CommandSearchResult(command)
  49. {
  50. SearchTerm = query,
  51. Match = Match(command.Description, query)
  52. }));
  53. // add matching files
  54. newResults.AddRange(MaybeParseFilePaths(query));
  55. // add matching recent files
  56. newResults.AddRange(
  57. ViewModelMain.Current.FileSubViewModel.RecentlyOpened
  58. .Where(x => x.FilePath.Contains(query))
  59. .Select(file => new FileSearchResult(file.FilePath)
  60. {
  61. SearchTerm = query,
  62. Match = Match(file.FilePath, query)
  63. }));
  64. return (newResults, warnings);
  65. }
  66. private static Match Match(string text, string searchTerm) =>
  67. Regex.Match(text, $"(.*)({Regex.Escape(searchTerm ?? string.Empty)})(.*)", RegexOptions.IgnoreCase);
  68. private static IEnumerable<SearchResult> MaybeParseFilePaths(string query)
  69. {
  70. var filePath = query.Trim(' ', '"', '\'');
  71. if (filePath.StartsWith("~"))
  72. filePath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), filePath[1..]);
  73. if (!Path.IsPathFullyQualified(filePath))
  74. return Enumerable.Empty<SearchResult>();
  75. GetDirectory(filePath, out var directory, out var name);
  76. var files = Directory.EnumerateFiles(directory)
  77. .Where(x => SupportedFilesHelper.IsExtensionSupported(Path.GetExtension(x)));
  78. if (name is not (null or ""))
  79. {
  80. files = files.Where(x => x.Contains(name, StringComparison.OrdinalIgnoreCase));
  81. }
  82. return files
  83. .Select(static file => Path.GetFullPath(file))
  84. .Select(path => new FileSearchResult(path)
  85. {
  86. SearchTerm = name,
  87. Match = Match($".../{Path.GetFileName(path)}", name ?? "")
  88. });
  89. }
  90. private static bool GetDirectory(string path, out string directory, out string file)
  91. {
  92. if (Directory.Exists(path))
  93. {
  94. directory = path;
  95. file = string.Empty;
  96. return true;
  97. }
  98. directory = Path.GetDirectoryName(path) ?? @"C:\";
  99. file = Path.GetFileName(path);
  100. return Directory.Exists(directory);
  101. }
  102. private static OneOf<SKColor, Error, None> MaybeParseColor(string query)
  103. {
  104. if (query.StartsWith('#'))
  105. {
  106. if (!SKColor.TryParse(query, out var color))
  107. return new Error();
  108. return color;
  109. }
  110. else if (query.StartsWith("rgb") || query.StartsWith("rgba"))
  111. {
  112. Match match = Regex.Match(query, @"rgba?\(? *(?<r>\d{1,3}) *, *(?<g>\d{1,3}) *, *(?<b>\d{1,3})(?: *, *(?<a>\d{1,3}))?");
  113. if (match.Success)
  114. {
  115. var maybeColor = ParseRGB(match);
  116. return maybeColor is null ? new Error() : maybeColor.Value;
  117. }
  118. else if (query.StartsWith("rgb(") || query.StartsWith("rgba("))
  119. {
  120. return new Error();
  121. }
  122. }
  123. return new None();
  124. }
  125. private static SKColor? ParseRGB(Match match)
  126. {
  127. bool invalid = !(
  128. byte.TryParse(match.Groups["r"].ValueSpan, out var r) &
  129. byte.TryParse(match.Groups["g"].ValueSpan, out var g) &
  130. byte.TryParse(match.Groups["b"].ValueSpan, out var b)
  131. );
  132. if (invalid)
  133. return null;
  134. var aText = match.Groups["a"].Value;
  135. byte a = 255;
  136. if (!string.IsNullOrEmpty(aText) && !byte.TryParse(aText, out a))
  137. return null;
  138. return new SKColor(r, g, b, a);
  139. }
  140. }