Improved the dialogs for local data sources and the Batch Processing assistant (#1096)

Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
Paul KoudelkaandThorsten Sommer authored and GitHub committed 2026-10-27 19:57:04 +01:00
1 parent 149395c1a0
commit f7e6ceb915
45 files changed
+2514 -361

No files matched your search

+46 -15
View File
@@ -25,6 +25,11 @@ internal static class FileExclusions
/// </summary>
public const char FOLDER_SUFFIX = '/';
/// <summary>
/// What AI Studio joins entries with when it writes a list, e.g., after an edit in the exclusion editor.
/// </summary>
public const string ENTRY_SEPARATOR = "; ";
//
// Folders of dependencies and build outputs which are not hidden. Hidden ones, such as .git,
// .venv, or .gradle, are left out by the rule for hidden files and folders anyway. Names which
@@ -65,7 +70,9 @@ internal static class FileExclusions
private const string DISK_IMAGE_FILES = "*.vmdk; *.vdi; *.vhd; *.vhdx; *.qcow2; *.hdd; *.hds; *.iso; *.dmg; *.img";
/// <summary>
/// The list a data source or the batch processing assistant starts with, one group per line.
/// The list a data source or the batch processing assistant starts with. Its line breaks only
/// group the entries for the reader of this code: the exclusion editor shows folders and files in
/// fields of their own.
/// </summary>
public const string DEFAULT_PATTERNS =
DEPENDENCY_AND_BUILD_FOLDERS + "\n" +
@@ -77,17 +84,6 @@ internal static class FileExclusions
DATABASE_FILES + "\n" +
DISK_IMAGE_FILES;
/// <summary>
/// How many lines the default list has, so that a field shows all of it at once.
/// </summary>
public static readonly int DEFAULT_LINE_COUNT = DEFAULT_PATTERNS.Split('\n').Length;
/// <summary>
/// How many lines a field for the list grows to before it scrolls: the default list, with room for
/// a few entries of the user.
/// </summary>
public const int MAX_LINE_COUNT = 12;
private const string OFFICE_LOCK_FILE_PATTERN = "~$*";
/// <summary>
@@ -112,6 +108,8 @@ internal static class FileExclusions
PATH,
RECURSIVE_WILDCARD,
INVALID_CHARACTER,
FILE_IN_FOLDER_LIST,
FOLDER_IN_FILE_LIST,
}
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(FileExclusions).Namespace, nameof(FileExclusions));
@@ -124,20 +122,50 @@ internal static class FileExclusions
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
/// <summary>
/// Splits a list into the entries for folders and those for files, as the exclusion editor and the
/// information about a data source show them.
/// </summary>
public static FileExclusionGroups Group(string? patterns)
{
var entries = Parse(patterns);
return new(
string.Join(ENTRY_SEPARATOR, entries.Where(IsFolderEntry)),
string.Join(ENTRY_SEPARATOR, entries.Where(entry => !IsFolderEntry(entry))));
}
public static bool IsValid(string? patterns) => GetValidationIssue(patterns) is ValidationIssue.NONE;
public static bool IsValidFolders(string? patterns) => GetValidationIssue(patterns, folders: true) is ValidationIssue.NONE;
public static bool IsValidFiles(string? patterns) => GetValidationIssue(patterns, folders: false) is ValidationIssue.NONE;
/// <summary>
/// Validates a list of excluded files and folders. An empty list is valid: it excludes nothing
/// beyond what is built in.
/// </summary>
public static string? Validate(string? patterns) => GetValidationIssue(patterns) switch
public static string? Validate(string? patterns) => DescribeValidationIssue(GetValidationIssue(patterns));
/// <summary>
/// Validates the folder field without guessing whether an entry without a slash was meant as a folder.
/// </summary>
public static string? ValidateFolders(string? patterns) => DescribeValidationIssue(GetValidationIssue(patterns, folders: true));
/// <summary>
/// Validates the file field, directing folder rules to their own field.
/// </summary>
public static string? ValidateFiles(string? patterns) => DescribeValidationIssue(GetValidationIssue(patterns, folders: false));
private static string? DescribeValidationIssue(ValidationIssue issue) => issue switch
{
ValidationIssue.NONE => null,
ValidationIssue.SELECTOR => TB("Selectors such as :text: cannot be excluded. Please use file name patterns such as *.min.js or folder names such as bin/."),
ValidationIssue.PATH => TB("Please write excluded folders as a name without a path, ending with a slash, e.g., node_modules/ or bin/. Such an entry excludes every folder of that name."),
ValidationIssue.RECURSIVE_WILDCARD => TB("Please use only single asterisks as wildcards, e.g., *.min.js or cmake-build-*/."),
ValidationIssue.INVALID_CHARACTER => TB("One of the excluded files or folders contains an invalid character."),
_ => throw new ArgumentOutOfRangeException(nameof(patterns)),
ValidationIssue.FILE_IN_FOLDER_LIST => TB("Folder patterns must end with /, e.g., build/. Enter file patterns such as *.pdf in 'Files'."),
ValidationIssue.FOLDER_IN_FILE_LIST => TB("Enter folder patterns ending with / in 'Folders'. File patterns must not end with /."),
_ => throw new ArgumentOutOfRangeException(nameof(issue)),
};
/// <summary>
@@ -174,7 +202,7 @@ internal static class FileExclusions
private static bool IsFolderEntry(string entry) => entry.EndsWith(FOLDER_SUFFIX);
private static ValidationIssue GetValidationIssue(string? patterns)
private static ValidationIssue GetValidationIssue(string? patterns, bool? folders = null)
{
foreach (var entry in Parse(patterns))
{
@@ -193,6 +221,9 @@ internal static class FileExclusions
if (name.IndexOfAny(FileNamePatterns.INVALID_PATTERN_CHARACTERS) >= 0)
return ValidationIssue.INVALID_CHARACTER;
if (folders is not null && IsFolderEntry(entry) != folders.Value)
return folders.Value ? ValidationIssue.FILE_IN_FOLDER_LIST : ValidationIssue.FOLDER_IN_FILE_LIST;
}
return ValidationIssue.NONE;