diff --git a/Studio/CelesteStudio/Editing/Editor.cs b/Studio/CelesteStudio/Editing/Editor.cs index ca34ab06..9b6df7c6 100644 --- a/Studio/CelesteStudio/Editing/Editor.cs +++ b/Studio/CelesteStudio/Editing/Editor.cs @@ -845,7 +845,7 @@ private void FormatLines(IEnumerable rows) { } else if (CommandLine.TryParse(line, out var commandLine)) { string commandName; if (StyleConfig.Current.ForceCorrectCommandCasing && - CommunicationWrapper.Commands.FirstOrDefault(cmd => string.Equals(cmd.Name, commandLine.Command, StringComparison.OrdinalIgnoreCase)) is { } command && !string.IsNullOrEmpty(command.Name)) + CommunicationWrapper.Commands.FirstOrDefault(cmd => string.Equals(cmd.Name, commandLine.Command, StringComparison.OrdinalIgnoreCase)) is var command && !string.IsNullOrEmpty(command.Name)) { commandName = command.Name; } else { diff --git a/Studio/CelesteStudio/Tool/ProjectFileFormatter.cs b/Studio/CelesteStudio/Tool/ProjectFileFormatter.cs index 749597af..274091ee 100644 --- a/Studio/CelesteStudio/Tool/ProjectFileFormatter.cs +++ b/Studio/CelesteStudio/Tool/ProjectFileFormatter.cs @@ -1,10 +1,13 @@ +using CelesteStudio.Communication; using CelesteStudio.Editing; using CelesteStudio.Util; using Eto.Drawing; using Eto.Forms; +using StudioCommunication; using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading.Tasks; namespace CelesteStudio.Tool; @@ -16,9 +19,14 @@ public class ProjectFileFormatterDialog : Eto.Forms.Dialog { private string projectRoot; private readonly CheckBox editRoomIndices; + private readonly CheckBox editCommands; + private readonly NumericStepper startingIndex; private readonly DropDown roomIndexType; + private readonly CheckBox forceCorrectCasing; + private readonly TextBox argumentSeparator; + private ProjectFileFormatterDialog() { Title = $"Project File Formatter v{Version}"; Icon = Assets.AppIcon; @@ -55,16 +63,21 @@ private ProjectFileFormatterDialog() { // Auto-room-indexing editRoomIndices = new CheckBox { Width = rowWidth, Checked = true }; - startingIndex = new NumericStepper { MinValue = 0, DecimalPlaces = 0, Width = rowWidth }; + startingIndex = new NumericStepper { MinValue = 0, DecimalPlaces = 0, Width = rowWidth, Value = StyleConfig.Current.RoomLabelStartingIndex ?? 0 }; roomIndexType = new DropDown { Items = { new ListItem { Text = "Only current File", Key = nameof(AutoRoomIndexing.CurrentFile) }, new ListItem { Text = "Including Read-commands", Key = nameof(AutoRoomIndexing.IncludeReads) }, }, - SelectedKey = nameof(AutoRoomIndexing.CurrentFile), + SelectedKey = StyleConfig.Current.RoomLabelIndexing?.ToString() ?? nameof(AutoRoomIndexing.CurrentFile), Width = rowWidth }; + // Command formatting + editCommands = new CheckBox { Width = rowWidth, Checked = true }; + forceCorrectCasing = new CheckBox { Width = rowWidth, Checked = true }; + argumentSeparator = new TextBox { Width = rowWidth, Text = StyleConfig.Current.CommandArgumentSeparator ?? ", " }; + var autoRoomIndexingLayout = new DynamicLayout { DefaultSpacing = new Size(10, 10) }; { autoRoomIndexingLayout.BeginVertical(); @@ -78,6 +91,19 @@ private ProjectFileFormatterDialog() { autoRoomIndexingLayout.AddCentered(new Label { Text = "Room Indexing Type" }); autoRoomIndexingLayout.Add(roomIndexType); } + var commandLayout = new DynamicLayout { DefaultSpacing = new Size(10, 10) }; + { + commandLayout.BeginVertical(); + commandLayout.BeginHorizontal(); + + commandLayout.AddCentered(new Label { Text = "Force Correct Casing" }); + commandLayout.Add(forceCorrectCasing); + + commandLayout.EndBeginHorizontal(); + + commandLayout.AddCentered(new Label { Text = "Argument Separator" }); + commandLayout.Add(argumentSeparator); + } DefaultButton = new Button((_, _) => Format()) { Text = "&Format" }; AbortButton = new Button((_, _) => Close()) { Text = "&Cancel" }; @@ -88,7 +114,7 @@ private ProjectFileFormatterDialog() { Content = new StackLayout { Padding = 10, Spacing = 10, - HorizontalContentAlignment = HorizontalAlignment.Center, + HorizontalContentAlignment = HorizontalAlignment.Left, Items = { new StackLayout { Spacing = 10, @@ -96,6 +122,7 @@ private ProjectFileFormatterDialog() { VerticalContentAlignment = VerticalAlignment.Center, Items = { new Label { Text = "Select Project Root Folder" }, projectRootButton } }, + new StackLayout { Spacing = 10, Orientation = Orientation.Horizontal, @@ -104,6 +131,14 @@ private ProjectFileFormatterDialog() { }, // NOTE: The only reason Scrollables are used, is because they provide a border new Scrollable { Content = autoRoomIndexingLayout, Padding = 5 }.FixBorder(), + + new StackLayout { + Spacing = 10, + Orientation = Orientation.Horizontal, + VerticalContentAlignment = VerticalAlignment.Center, + Items = { new Label { Text = "Format Commands" }, editCommands } + }, + new Scrollable { Content = commandLayout, Padding = 5 }.FixBorder(), } }; Resizable = false; @@ -117,9 +152,14 @@ private void Format() { string[] files = Directory.GetFiles(projectRoot, "*.tas", new EnumerationOptions { RecurseSubdirectories = true, AttributesToSkip = FileAttributes.Hidden }); bool formatRoomIndices = Application.Instance.Invoke(() => editRoomIndices.Checked == true); + bool formatCommands = Application.Instance.Invoke(() => editCommands.Checked == true); + bool includeReads = Application.Instance.Invoke(() => roomIndexType.SelectedKey == nameof(AutoRoomIndexing.IncludeReads)); int startIndex = (int)Application.Instance.Invoke(() => startingIndex.Value); + bool forceCase = Application.Instance.Invoke(() => forceCorrectCasing.Checked == true); + string separator = Application.Instance.Invoke(() => argumentSeparator.Text); + int totalTasks = 0, finishedTasks = 0; Label progressLabel; @@ -165,9 +205,11 @@ private void Format() { try { if (formatRoomIndices) { - //await Task.Delay((int)Random.Shared.NextInt64(5_000)).ConfigureAwait(false); await UpdateRoomLabelIndices(file, startIndex, includeReads).ConfigureAwait(false); } + if (formatCommands) { + await UpdateCommands(file, forceCase, separator).ConfigureAwait(false); + } Console.WriteLine($"Successfully reformatted '{file}'"); } catch (Exception ex) { @@ -221,11 +263,11 @@ private async Task UpdateRoomLabelIndices(string filePath, int startIndex, bool string[]? lines = null; if (filePath != editor.Document.FilePath) { - await Studio.Instance.Editor.RefactorSemaphore.WaitAsync().ConfigureAwait(false); + await editor.RefactorSemaphore.WaitAsync().ConfigureAwait(false); if (!Editor.FileCache.TryGetValue(filePath, out lines)) { Editor.FileCache[filePath] = lines = await File.ReadAllLinesAsync(filePath).ConfigureAwait(false); } - Studio.Instance.Editor.RefactorSemaphore.Release(); + editor.RefactorSemaphore.Release(); } using var __ = editor.Document.Update(raiseEvents: false); @@ -268,5 +310,53 @@ private async Task UpdateRoomLabelIndices(string filePath, int startIndex, bool } } + private async Task UpdateCommands(string filePath, bool forceCase, string separator) { + var editor = Studio.Instance.Editor; + + if (filePath == editor.Document.FilePath) { + using var __ = editor.Document.Update(raiseEvents: false); + + for (int row = 0; row < editor.Document.Lines.Count; row++) { + if (!CommandLine.TryParse(editor.Document.Lines[row], out var commandLine)) { + continue; + } + + string commandName; + if (forceCase && CommunicationWrapper.Commands.FirstOrDefault(cmd => string.Equals(cmd.Name, commandLine.Command, StringComparison.OrdinalIgnoreCase)) is var command && !string.IsNullOrEmpty(command.Name)) { + commandName = command.Name; + } else { + commandName = commandLine.Command; + } + + editor.Document.ReplaceLine(row, string.Join(separator, [commandName, ..commandLine.Arguments])); + } + } else { + await editor.RefactorSemaphore.WaitAsync().ConfigureAwait(false); + if (!Editor.FileCache.TryGetValue(filePath, out string[]? lines)) { + Editor.FileCache[filePath] = lines = await File.ReadAllLinesAsync(filePath).ConfigureAwait(false); + } + editor.RefactorSemaphore.Release(); + + for (int row = 0; row < lines.Length; row++) { + if (!CommandLine.TryParse(lines[row], out var commandLine)) { + continue; + } + + string commandName; + if (forceCase && CommunicationWrapper.Commands.FirstOrDefault(cmd => string.Equals(cmd.Name, commandLine.Command, StringComparison.OrdinalIgnoreCase)) is var command && !string.IsNullOrEmpty(command.Name)) { + commandName = command.Name; + } else { + commandName = commandLine.Command; + } + + lines[row] = string.Join(separator, [commandName, ..commandLine.Arguments]); + } + + await editor.RefactorSemaphore.WaitAsync().ConfigureAwait(false); + await File.WriteAllTextAsync(filePath, Document.FormatLinesToText(lines)).ConfigureAwait(false); + editor.RefactorSemaphore.Release(); + } + } + public static void Show() => new ProjectFileFormatterDialog().ShowModal(); }