diff --git a/Examples/AI/AI.csproj b/Examples/AI/AI.csproj deleted file mode 100644 index 6361cd36df..0000000000 --- a/Examples/AI/AI.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - Exe - enable - latest - - - - - - - - - diff --git a/Examples/AI/ChatView.cs b/Examples/AI/ChatView.cs deleted file mode 100644 index d6b30f244b..0000000000 --- a/Examples/AI/ChatView.cs +++ /dev/null @@ -1,283 +0,0 @@ -using System.Text; -using GitHub.Copilot.SDK; -using Terminal.Gui.App; -using Terminal.Gui.Drawing; -using Terminal.Gui.Input; -using Terminal.Gui.ViewBase; -using Terminal.Gui.Views; - -namespace AI; - -/// -/// An inline Window with conversation history, input field, status bar, and slash commands. -/// -internal sealed class ChatView : Window -{ - private readonly IApplication _app; - private readonly CopilotClient _client; - private string _model; - private readonly Markdown _conversationView; - private readonly StringBuilder _conversationText = new (); - private readonly TextField _inputField; - private readonly View _inputIndicator; - private readonly SpinnerView _spinner; - private readonly StatusBar _statusBar; - private CopilotSession? _session; - private bool _isStreaming; - - public int ExitCode { get; } = 0; - - public ChatView (IApplication app, CopilotClient client, string model) - { - _app = app; - _client = client; - _model = model; - - Title = $"Copilot Chat ({model})"; - Width = Dim.Fill (); - Height = Dim.Auto (); - Border.LineStyle = LineStyle.Rounded; - Border.Thickness = new Thickness (0, 3, 0, 0); - Border.Settings = BorderSettings.Gradient | BorderSettings.Title; - - _spinner = new SpinnerView - { - AutoSpin = false, - Style = new SpinnerStyle.FingerDance (), - SyncWithTerminal = true, - Height = 1, - Width = 5, - Visible = false - }; - Shortcut spinnerShortcut = new () { CommandView = _spinner, MouseHighlightStates = MouseState.None, Enabled = false }; - Shortcut quitShortcut = new (Application.GetDefaultKey (Command.Quit), "Quit", RequestStop); - - _statusBar = new StatusBar { AlignmentModes = AlignmentModes.IgnoreFirstOrLast, SchemeName = SchemeName, BorderStyle = LineStyle.None }; - _statusBar.Add (spinnerShortcut, quitShortcut); - - _conversationView = new Markdown - { - Width = Dim.Fill (), Height = Dim.Auto (minimumContentDim: 1, maximumContentDim: Dim.Func (_ => GetMaxConversationHeight ())) - }; - - _inputIndicator = new View - { - Text = $"{Glyphs.RightArrow}", - Y = Pos.Bottom (_conversationView), - Width = 2, - Height = 3, - Enabled = false - }; - _inputIndicator.Border.LineStyle = LineStyle.Dotted; - _inputIndicator.Border.Thickness = new Thickness (0, 1, 0, 1); - - _inputField = new TextField { X = Pos.Right (_inputIndicator), Y = Pos.Top (_inputIndicator), Width = Dim.Fill () }; - _inputField.Border.LineStyle = _inputIndicator.Border.LineStyle; - _inputField.Border.Thickness = new Thickness (0, 1, 0, 1); - - _inputField.Autocomplete?.SuggestionGenerator = new SlashCommandSuggestionGenerator (); - _inputField.Accepted += OnInputAccepted; - - Add (_conversationView, _inputIndicator, _inputField, _statusBar); - _inputField.SetFocus (); - - return; - - int GetMaxConversationHeight () - { - int screenHeight = _app.Driver?.Screen.Height ?? 40; - int windowAdornments = GetAdornmentsThickness ().Vertical; - int siblingHeight = _inputIndicator!.Frame.Height + _statusBar.Frame.Height; - - return Math.Max (1, screenHeight - windowAdornments - siblingHeight); - } - } - - private async void OnInputAccepted (object? sender, EventArgs e) - { - string text = _inputField.Text.Trim (); - - if (string.IsNullOrEmpty (text) || _isStreaming) - { - return; - } - - _inputField.Text = string.Empty; - - if (text.StartsWith ('/')) - { - HandleSlashCommand (text); - - return; - } - - _isStreaming = true; - _spinner.AutoSpin = true; - _spinner.Visible = true; - _inputField.Enabled = false; - - AppendToConversation ($"\n{Glyphs.BlackCircle} You: {text}\n\n{Glyphs.Diamond} Copilot: "); - - try - { - _session ??= await _client.CreateSessionAsync (new SessionConfig - { - Model = _model, Streaming = true, OnPermissionRequest = PermissionHandler.ApproveAll - }); - - TaskCompletionSource done = new (); - - using IDisposable subscription = _session.On (evt => - { - switch (evt) - { - case AssistantMessageDeltaEvent delta: - _app.Invoke (() => AppendToConversation (delta.Data.DeltaContent)); - - break; - - case SessionIdleEvent: - _app.Invoke (() => AppendToConversation ("\n")); - done.TrySetResult (); - - break; - - case SessionErrorEvent err: - _app.Invoke (() => AppendToConversation ($"\n[Error: {err.Data.Message}]\n")); - done.TrySetResult (); - - break; - } - }); - - await _session.SendAsync (new MessageOptions { Prompt = text }); - await done.Task; - } - catch (Exception ex) - { - AppendToConversation ($"\n[Error: {ex.Message}]\n"); - } - finally - { - _isStreaming = false; - _spinner.Visible = false; - _spinner.AutoSpin = false; - _inputField.Enabled = true; - _inputField.SetFocus (); - } - } - - private async Task ValidateAndSwitchModel (string newModel) - { - _inputField.Enabled = false; - - try - { - CopilotSession testSession = await _client.CreateSessionAsync (new SessionConfig - { - Model = newModel, - Streaming = true, - OnPermissionRequest = PermissionHandler.ApproveAll - }); - - if (_session is { }) - { - await _session.DisposeAsync (); - } - - _session = testSession; - _model = newModel; - - _app.Invoke (() => - { - Title = $"Copilot Chat ({newModel})"; - AppendToConversation (" \u2713\n"); - _inputField.Enabled = true; - _inputField.SetFocus (); - }); - } - catch (Exception ex) - { - _app.Invoke (() => - { - AppendToConversation ($"\n{Glyphs.Diamond} Failed: {ex.Message}\n Keeping model: {_model}\n"); - _inputField.Enabled = true; - _inputField.SetFocus (); - }); - } - } - - private void AppendToConversation (string text) - { - _conversationText.Append (text); - _conversationView.Text = _conversationText.ToString (); - } - - private void HandleSlashCommand (string command) - { - string cmd = command.TrimStart ('/').ToLowerInvariant (); - string [] parts = cmd.Split (' ', 2); - - switch (parts [0]) - { - case "quit" or "exit": - RequestStop (); - - break; - - case "clear": - _conversationText.Clear (); - _conversationView.Text = string.Empty; - AppendToConversation ($"{Glyphs.Diamond} Conversation cleared.\n"); - - break; - - case "model": - if (parts.Length > 1 && !string.IsNullOrWhiteSpace (parts [1])) - { - string newModel = parts [1].Trim (); - AppendToConversation ($"\n{Glyphs.Diamond} Switching to {newModel}..."); - _ = ValidateAndSwitchModel (newModel); - } - else - { - AppendToConversation ($"\n{Glyphs.Diamond} Current model: {_model}\n Usage: /model \n"); - } - - break; - - case "help": - AppendToConversation ($"\n{Glyphs.Diamond} Commands:\n" - + " /help Show this help\n" - + " /clear Clear conversation\n" - + " /model Switch model\n" - + " /compact Summarize conversation\n" - + " /quit Exit chat\n"); - - break; - - case "compact": - AppendToConversation ($"\n{Glyphs.Diamond} Compacting conversation...\n"); - - // TODO: Send conversation summary request to model - - break; - - default: - AppendToConversation ($"\n{Glyphs.Diamond} Unknown command: /{parts [0]}. Type /help for commands.\n"); - - break; - } - } - - /// - protected override void Dispose (bool disposing) - { - if (disposing) - { - _session?.DisposeAsync ().AsTask ().GetAwaiter ().GetResult (); - } - - base.Dispose (disposing); - } -} \ No newline at end of file diff --git a/Examples/AI/Program.cs b/Examples/AI/Program.cs deleted file mode 100644 index 7b20494068..0000000000 --- a/Examples/AI/Program.cs +++ /dev/null @@ -1,57 +0,0 @@ -// AI — A Terminal.Gui inline-mode CLI powered by the GitHub Copilot SDK. -// -// Requires: GitHub Copilot CLI installed and authenticated (gh extension install github/gh-copilot) - -using System.CommandLine; -using System.CommandLine.Help; -using AI; -using GitHub.Copilot.SDK; -using Terminal.Gui.App; - -Option modelOption = new ("--model") { Description = "The Copilot model to use.", DefaultValueFactory = _ => "claude-opus-4.6" }; -modelOption.Aliases.Add ("-m"); - -Argument promptArgument = new ("prompt") { Description = "Prompt text. If omitted, interactive chat mode starts.", DefaultValueFactory = _ => null }; -promptArgument.Arity = ArgumentArity.ZeroOrOne; - -RootCommand rootCommand = new ("Terminal.Gui inline-mode Copilot chat") { modelOption, promptArgument }; - -// Capture parsed values — SetAction runs synchronously, so we store and act after. -var parsedModel = "claude-opus-4.6"; -string? parsedPrompt = null; - -rootCommand.SetAction (context => - { - parsedModel = context.GetRequiredValue (modelOption); - parsedPrompt = context.GetValue (promptArgument); - }); - -ParseResult parseResult = rootCommand.Parse (args); - -if (parseResult.Errors.Count > 0 || parseResult.Action is HelpAction) -{ - parseResult.Invoke (); - - return parseResult.Errors.Count > 0 ? 1 : 0; -} - -parseResult.Invoke (); - -// ── Start Copilot SDK and run ──────────────────────────────────────────────── - -await using CopilotClient client = new (); -await client.StartAsync (); - -Application.AppModel = AppModel.Inline; -IApplication app = Application.Create ().Init (); - -if (parsedPrompt is { }) -{ - return SingleTurnView.Run (app, client, parsedModel, parsedPrompt); -} - -ChatView chatView = new (app, client, parsedModel); -app.Run (chatView); -app.Dispose (); - -return chatView.ExitCode; diff --git a/Examples/AI/SingleTurnView.cs b/Examples/AI/SingleTurnView.cs deleted file mode 100644 index 1fb7970870..0000000000 --- a/Examples/AI/SingleTurnView.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System.Text; -using GitHub.Copilot.SDK; -using Terminal.Gui.App; -using Terminal.Gui.Drawing; -using Terminal.Gui.ViewBase; -using Terminal.Gui.Views; - -namespace AI; - -/// -/// Handles single-turn mode: streams one answer inline, then exits. -/// -internal sealed class SingleTurnView : Window -{ - private readonly IApplication _app; - private readonly CopilotClient _client; - private readonly string _model; - private readonly string _prompt; - private readonly Markdown _responseView; - - public SingleTurnView (IApplication app, CopilotClient client, string model, string prompt) - { - _app = app; - _client = client; - _model = model; - _prompt = prompt; - - Title = $"Copilot ({model})"; - Width = Dim.Fill (); - Height = Dim.Auto (); - Border.LineStyle = LineStyle.Rounded; - - _responseView = new Markdown { Width = Dim.Fill (), Height = Dim.Auto () }; - - Add (_responseView); - } - - /// - protected override void OnIsRunningChanged (bool newIsRunning) - { - base.OnIsRunningChanged (newIsRunning); - - if (newIsRunning) - { - _ = streamResponse (); - } - } - - private async Task streamResponse () - { - try - { - await using CopilotSession session = await _client.CreateSessionAsync (new SessionConfig - { - Model = _model, - Streaming = true, - OnPermissionRequest = PermissionHandler.ApproveAll - }); - - StringBuilder responseText = new (); - TaskCompletionSource done = new (); - - session.On (evt => - { - switch (evt) - { - case AssistantMessageDeltaEvent delta: - responseText.Append (delta.Data.DeltaContent); - - _app.Invoke (() => { _responseView.Text = responseText.ToString (); }); - - break; - - case SessionIdleEvent: - done.TrySetResult (); - - break; - - case SessionErrorEvent err: - _app.Invoke (() => { _responseView.Text = $"Error: {err.Data.Message}"; }); - done.TrySetResult (); - - break; - } - }); - - await session.SendAsync (new MessageOptions { Prompt = _prompt }); - await done.Task; - } - catch (Exception ex) - { - _app.Invoke (() => _responseView.Text = $"Error: {ex.Message}"); - } - - // Give the UI a moment to render the final update, then exit - _app.Invoke (RequestStop); - } - - public static int Run (IApplication app, CopilotClient client, string model, string prompt) - { - SingleTurnView view = new (app, client, model, prompt); - app.Run (view); - app.Dispose (); - - return 0; - } -} \ No newline at end of file diff --git a/Examples/AI/SlashCommandSuggestionGenerator.cs b/Examples/AI/SlashCommandSuggestionGenerator.cs deleted file mode 100644 index 729c0a9318..0000000000 --- a/Examples/AI/SlashCommandSuggestionGenerator.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Text; -using Terminal.Gui.Views; - -namespace AI; - -/// -/// Suggestion generator that provides slash command completions when the input starts with '/'. -/// -internal sealed class SlashCommandSuggestionGenerator : ISuggestionGenerator -{ - private static readonly List _commands = ["/help", "/clear", "/model", "/compact", "/quit", "/exit"]; - - /// - public IEnumerable GenerateSuggestions (AutocompleteContext context) - { - List line = context.CurrentLine.Select (c => c.Grapheme).ToList (); - string lineText = string.Join ("", line).TrimEnd (); - - if (lineText.Length == 0 || lineText [0] != '/') - { - return []; - } - - int typedLength = Math.Min (context.CursorPosition, lineText.Length); - string typed = lineText [..typedLength]; - - context.CursorPosition = 0; - - return _commands.Where (c => c.StartsWith (typed, StringComparison.OrdinalIgnoreCase) && !c.Equals (typed, StringComparison.OrdinalIgnoreCase)) - .Select (c => new Suggestion (typed.Length, c)) - .ToList (); - } - - /// - public bool IsWordChar (string text) - { - if (string.IsNullOrEmpty (text)) - { - return false; - } - - Rune r = text.EnumerateRunes ().First (); - - return Rune.IsLetterOrDigit (r) || r == new Rune ('/'); - } -} \ No newline at end of file diff --git a/Examples/UICatalog/Scenarios/Editor.cs b/Examples/UICatalog/Scenarios/Editor.cs index 4408a09725..e9b4a3fd64 100644 --- a/Examples/UICatalog/Scenarios/Editor.cs +++ b/Examples/UICatalog/Scenarios/Editor.cs @@ -365,7 +365,7 @@ private bool SaveAs () _app?.Run (sd); bool canceled = sd.Canceled; string path = sd.Path; - string fileName = sd.FileName; + string fileName = sd.FileName ?? string.Empty; sd.Dispose (); if (canceled) diff --git a/Examples/UICatalog/Scenarios/Notepad.cs b/Examples/UICatalog/Scenarios/Notepad.cs index b1a579a72a..988e4764d5 100644 --- a/Examples/UICatalog/Scenarios/Notepad.cs +++ b/Examples/UICatalog/Scenarios/Notepad.cs @@ -1,4 +1,5 @@ // ReSharper disable AccessToDisposedClosure + #nullable enable namespace UICatalog.Scenarios; @@ -146,7 +147,7 @@ public bool SaveAs () _lastDirectory = Path.GetDirectoryName (Path.GetFullPath (fd.Path)); tab.File = new FileInfo (fd.Path); - tab.Title = fd.FileName; + tab.Title = fd.FileName ?? throw new InvalidOperationException (); tab.Save (); fd.Dispose (); @@ -205,7 +206,20 @@ private void Close (Tabs tabs, OpenedFile tabToClose) private void Open () { - OpenDialog open = new () { Title = "Open", AllowsMultipleSelection = true }; + OpenDialog open = new () + { + Title = "Open", + AllowsMultipleSelection = true, + AllowedTypes = + [ + new AllowedType ("Markdown", ".md", ".markdown"), + new AllowedType ("Text", ".txt", ".csv", ".tsv"), + new AllowedType ("Code", ".c", ".h", ".js", ".cs", ".json", ".yml"), + new AllowedTypeAny () + ], + MustExist = true, + OpenMode = OpenMode.File + }; if (_lastDirectory is { }) { diff --git a/Terminal.Gui/FileServices/FileSystemTreeBuilder.cs b/Terminal.Gui/FileServices/FileSystemTreeBuilder.cs index 46ce46f338..e6237a1569 100644 --- a/Terminal.Gui/FileServices/FileSystemTreeBuilder.cs +++ b/Terminal.Gui/FileServices/FileSystemTreeBuilder.cs @@ -1,5 +1,4 @@ -#nullable disable -using System.IO.Abstractions; +using System.IO.Abstractions; namespace Terminal.Gui.FileServices; @@ -16,7 +15,7 @@ public class FileSystemTreeBuilder : ITreeBuilder, IComparer Sorter { get; set; } /// - public int Compare (IFileSystemInfo x, IFileSystemInfo y) + public int Compare (IFileSystemInfo? x, IFileSystemInfo? y) { if (x is IDirectoryInfo && y is not IDirectoryInfo) { @@ -28,12 +27,7 @@ public int Compare (IFileSystemInfo x, IFileSystemInfo y) return 1; } - if (x is { } && y is { }) - { - return string.Compare (x.Name, y.Name, StringComparison.Ordinal); - } - - return 0; + return string.Compare (x?.Name, y?.Name, StringComparison.Ordinal); } /// @@ -47,12 +41,7 @@ public bool CanExpand (IFileSystemInfo toExpand) return false; } - if (IsReparsePoint (toExpand)) - { - return false; - } - - return TryGetChildren (toExpand).Any (); + return !IsReparsePoint (toExpand) && TryGetChildren (toExpand).Any (); } /// @@ -62,16 +51,16 @@ private IEnumerable TryGetChildren (IFileSystemInfo entry) { if (entry is IFileInfo) { - return Enumerable.Empty (); + return []; } // Prevent traversal cycles through symlinks/junctions/mount points. if (IsReparsePoint (entry)) { - return Enumerable.Empty (); + return []; } - var dir = (IDirectoryInfo)entry; + IDirectoryInfo dir = (IDirectoryInfo)entry; try { diff --git a/Terminal.Gui/FileServices/IFileOperations.cs b/Terminal.Gui/FileServices/IFileOperations.cs index 920f3e947f..06fd639fd0 100644 --- a/Terminal.Gui/FileServices/IFileOperations.cs +++ b/Terminal.Gui/FileServices/IFileOperations.cs @@ -25,7 +25,7 @@ public interface IFileOperations /// /// Ensure you use a try/catch block with appropriate error handling (e.g. showing a /// - IFileSystemInfo New (IApplication? app, IFileSystem fileSystem, IDirectoryInfo inDirectory); + IFileSystemInfo? New (IApplication? app, IFileSystem fileSystem, IDirectoryInfo inDirectory); /// Specifies how to handle file/directory rename attempts in . /// @@ -35,5 +35,5 @@ public interface IFileOperations /// /// Ensure you use a try/catch block with appropriate error handling (e.g. showing a /// - IFileSystemInfo Rename (IApplication? app, IFileSystem fileSystem, IFileSystemInfo toRename); + IFileSystemInfo? Rename (IApplication? app, IFileSystem fileSystem, IFileSystemInfo toRename); } diff --git a/Terminal.Gui/ViewBase/Helpers/StackExtensions.cs b/Terminal.Gui/ViewBase/Helpers/StackExtensions.cs index e76788029a..3df4d43d0d 100644 --- a/Terminal.Gui/ViewBase/Helpers/StackExtensions.cs +++ b/Terminal.Gui/ViewBase/Helpers/StackExtensions.cs @@ -1,244 +1,160 @@ -#nullable disable -namespace Terminal.Gui.ViewBase; +namespace Terminal.Gui.ViewBase; /// Extension of helper to work with specific public static class StackExtensions { - /// Check if the stack object contains the value to find. - /// The stack object type. /// The stack object. - /// Value to find. - /// The comparison object. - /// true If the value was found.false otherwise. - public static bool Contains (this Stack stack, T valueToFind, IEqualityComparer comparer = null) - { - comparer = comparer ?? EqualityComparer.Default; - - foreach (T obj in stack) - { - if (comparer.Equals (obj, valueToFind)) - { - return true; - } - } - - return false; - } - - /// Find all duplicates stack objects values. /// The stack object type. - /// The stack object. - /// The comparison object. - /// The duplicates stack object. - public static Stack FindDuplicates (this Stack stack, IEqualityComparer comparer = null) + extension (Stack stack) { - comparer = comparer ?? EqualityComparer.Default; - - Stack dup = new (); - T [] stackArr = stack.ToArray (); - - for (var i = 0; i < stackArr.Length; i++) + /// Check if the stack object contains the value to find. + /// Value to find. + /// The comparison object. + /// true If the value was found.false otherwise. + public bool Contains (T valueToFind, IEqualityComparer? comparer = null) { - T value = stackArr [i]; + comparer ??= EqualityComparer.Default; - for (int j = i + 1; j < stackArr.Length; j++) + foreach (T obj in stack) { - T valueToFind = stackArr [j]; - - if (comparer.Equals (value, valueToFind) && !Contains (dup, valueToFind)) + if (comparer.Equals (obj, valueToFind)) { - dup.Push (value); + return true; } } - } - return dup; - } - - /// Move the first stack object value to the end. - /// The stack object type. - /// The stack object. - public static void MoveNext (this Stack stack) - { - Stack temp = new (); - T last = stack.Pop (); - - while (stack.Count > 0) - { - T value = stack.Pop (); - temp.Push (value); + return false; } - temp.Push (last); - - while (temp.Count > 0) + /// Move the first stack object value to the end. + public void MoveNext () { - stack.Push (temp.Pop ()); - } - } + Stack temp = new (); + T last = stack.Pop (); - /// Move the last stack object value to the top. - /// The stack object type. - /// The stack object. - public static void MovePrevious (this Stack stack) - { - Stack temp = new (); - T first = default; + while (stack.Count > 0) + { + T value = stack.Pop (); + temp.Push (value); + } - while (stack.Count > 0) - { - T value = stack.Pop (); - temp.Push (value); + temp.Push (last); - if (stack.Count == 1) + while (temp.Count > 0) { - first = stack.Pop (); + stack.Push (temp.Pop ()); } } - while (temp.Count > 0) - { - stack.Push (temp.Pop ()); - } - - stack.Push (first); - } - - /// Move the stack object value to the index. - /// The stack object type. - /// The stack object. - /// Value to move. - /// The index where to move. - /// The comparison object. - public static void MoveTo ( - this Stack stack, - T valueToMove, - int index = 0, - IEqualityComparer comparer = null - ) - { - if (index < 0) + /// Move the last stack object value to the top. + public void MovePrevious () { - return; - } - - comparer = comparer ?? EqualityComparer.Default; + Stack temp = new (); + T? first = default; - Stack temp = new (); - var toMove = default (T); - int stackCount = stack.Count; - var count = 0; + while (stack.Count > 0) + { + T value = stack.Pop (); + temp.Push (value); - while (stack.Count > 0) - { - T value = stack.Pop (); + if (stack.Count == 1) + { + first = stack.Pop (); + } + } - if (comparer.Equals (value, valueToMove)) + while (temp.Count > 0) { - toMove = value; - - break; + stack.Push (temp.Pop ()); } - temp.Push (value); - count++; + if (first is { }) + { + stack.Push (first); + } } - var idx = 0; - - while (stack.Count < stackCount) + /// Move the stack object value to the index. + /// Value to move. + /// The index where to move. + /// The comparison object. + public void MoveTo (T valueToMove, int index = 0, IEqualityComparer? comparer = null) { - if (count - idx == index) + if (index < 0) { - stack.Push (toMove); + return; } - else + + comparer ??= EqualityComparer.Default; + + Stack temp = new (); + var toMove = default (T); + int stackCount = stack.Count; + var count = 0; + + while (stack.Count > 0) { - stack.Push (temp.Pop ()); - } + T value = stack.Pop (); - idx++; - } - } + if (comparer.Equals (value, valueToMove)) + { + toMove = value; - /// Replaces a stack object values that match with the value to replace. - /// The stack object type. - /// The stack object. - /// Value to replace. - /// Value to replace with to what matches the value to replace. - /// The comparison object. - public static void Replace ( - this Stack stack, - T valueToReplace, - T valueToReplaceWith, - IEqualityComparer comparer = null - ) - { - comparer = comparer ?? EqualityComparer.Default; + break; + } - Stack temp = new (); + temp.Push (value); + count++; + } - while (stack.Count > 0) - { - T value = stack.Pop (); + var idx = 0; - if (comparer.Equals (value, valueToReplace)) + while (stack.Count < stackCount) { - stack.Push (valueToReplaceWith); + if (count - idx == index) + { + if (toMove is { }) + { + stack.Push (toMove); + } + } + else + { + stack.Push (temp.Pop ()); + } - break; + idx++; } - - temp.Push (value); } - while (temp.Count > 0) + /// Replaces a stack object values that match with the value to replace. + /// Value to replace. + /// Value to replace with to what matches the value to replace. + /// The comparison object. + public void Replace (T valueToReplace, T valueToReplaceWith, IEqualityComparer? comparer = null) { - stack.Push (temp.Pop ()); - } - } + comparer ??= EqualityComparer.Default; - /// Swap two stack objects values that matches with the both values. - /// The stack object type. - /// The stack object. - /// Value to swap from. - /// Value to swap to. - /// The comparison object. - public static void Swap ( - this Stack stack, - T valueToSwapFrom, - T valueToSwapTo, - IEqualityComparer comparer = null - ) - { - comparer = comparer ?? EqualityComparer.Default; + Stack temp = new (); - int index = stack.Count - 1; - T [] stackArr = new T [stack.Count]; + while (stack.Count > 0) + { + T value = stack.Pop (); - while (stack.Count > 0) - { - T value = stack.Pop (); + if (comparer.Equals (value, valueToReplace)) + { + stack.Push (valueToReplaceWith); - if (comparer.Equals (value, valueToSwapFrom)) - { - stackArr [index] = valueToSwapTo; - } - else if (comparer.Equals (value, valueToSwapTo)) - { - stackArr [index] = valueToSwapFrom; + break; + } + + temp.Push (value); } - else + + while (temp.Count > 0) { - stackArr [index] = value; + stack.Push (temp.Pop ()); } - - index--; - } - - for (var i = 0; i < stackArr.Length; i++) - { - stack.Push (stackArr [i]); } } } diff --git a/Terminal.Gui/ViewBase/Layout/Aligner.cs b/Terminal.Gui/ViewBase/Layout/Aligner.cs index 637765ba11..082581aba4 100644 --- a/Terminal.Gui/ViewBase/Layout/Aligner.cs +++ b/Terminal.Gui/ViewBase/Layout/Aligner.cs @@ -1,4 +1,3 @@ -#nullable disable using System.ComponentModel; namespace Terminal.Gui.ViewBase; @@ -9,8 +8,6 @@ namespace Terminal.Gui.ViewBase; /// public class Aligner : INotifyPropertyChanged { - private Alignment _alignment; - /// /// Gets or sets how the aligns items within a container. /// @@ -21,11 +18,11 @@ public class Aligner : INotifyPropertyChanged /// public Alignment Alignment { - get => _alignment; + get; set { - _alignment = value; - PropertyChanged?.Invoke (this, new (nameof (Alignment))); + field = value; + PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (nameof (Alignment))); } } @@ -38,7 +35,7 @@ public AlignmentModes AlignmentModes set { field = value; - PropertyChanged?.Invoke (this, new (nameof (AlignmentModes))); + PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (nameof (AlignmentModes))); } } = AlignmentModes.StartToEnd; @@ -51,12 +48,12 @@ public int ContainerSize set { field = value; - PropertyChanged?.Invoke (this, new (nameof (ContainerSize))); + PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (nameof (ContainerSize))); } } /// - public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangedEventHandler? PropertyChanged; /// /// Takes a list of item sizes and returns a list of the positions of those items when aligned within diff --git a/Terminal.Gui/Views/Autocomplete/AutocompleteFilepathContext.cs b/Terminal.Gui/Views/Autocomplete/AutocompleteFilepathContext.cs index 12384e45b1..0753889ea5 100644 --- a/Terminal.Gui/Views/Autocomplete/AutocompleteFilepathContext.cs +++ b/Terminal.Gui/Views/Autocomplete/AutocompleteFilepathContext.cs @@ -1,4 +1,3 @@ -#nullable disable using System.IO.Abstractions; using System.Runtime.InteropServices; @@ -12,7 +11,7 @@ internal class AutocompleteFilepathContext (string currentLine, int cursorPositi internal class FilepathSuggestionGenerator : ISuggestionGenerator { - private FileDialogState _state; + private FileDialogState? _state; public IEnumerable GenerateSuggestions (AutocompleteContext context) { @@ -21,74 +20,51 @@ public IEnumerable GenerateSuggestions (AutocompleteContext context) _state = fileState.State; } - if (_state is null) - { - return Enumerable.Empty (); - } - var path = Cell.ToString (context.CurrentLine); int last = path.LastIndexOfAny (FileDialog.Separators); - if (string.IsNullOrWhiteSpace (path) || !Path.IsPathRooted (path)) + if (string.IsNullOrWhiteSpace (path) || !Path.IsPathRooted (path) || _state is null) { - return Enumerable.Empty (); + return []; } - string term = path.Substring (last + 1); + string term = path [(last + 1)..]; // If path is /tmp/ then don't just list everything in it if (string.IsNullOrWhiteSpace (term)) { - return Enumerable.Empty (); + return []; } if (term.Equals (_state?.Directory?.Name)) { // Clear suggestions - return Enumerable.Empty (); + return []; } bool isWindows = RuntimeInformation.IsOSPlatform (OSPlatform.Windows); - string [] suggestions = _state!.Children.Where (d => !d.IsParent) - .Select ( - e => e.FileSystemInfo is IDirectoryInfo d - ? d.Name + Path.DirectorySeparatorChar - : e.FileSystemInfo.Name - ) - .ToArray (); + string? [] suggestions = _state?.Children.Where (d => !d.IsParent) + .Select (e => e.FileSystemInfo is IDirectoryInfo d ? d.Name + Path.DirectorySeparatorChar : e.FileSystemInfo?.Name) + .ToArray () + ?? []; string [] validSuggestions = suggestions - .Where ( - s => s.StartsWith ( - term, - isWindows - ? StringComparison.InvariantCultureIgnoreCase - : StringComparison.InvariantCulture - ) - ) - .OrderBy (m => m.Length) - .ToArray (); + .Where (s => s?.StartsWith (term, + isWindows ? StringComparison.InvariantCultureIgnoreCase : StringComparison.InvariantCulture) + == true) + .OfType () + .OrderBy (m => m.Length) + .ToArray (); // nothing to suggest if (validSuggestions.Length == 0 || validSuggestions [0].Length == term.Length) { - return Enumerable.Empty (); + return []; } - return validSuggestions.Select ( - f => new Suggestion (term.Length, f, f) - ) - .ToList (); + return validSuggestions.Select (f => new Suggestion (term.Length, f, f)).ToList (); } - public bool IsWordChar (string text) - { - if (text == "\n") - { - return false; - } - - return true; - } + public bool IsWordChar (string text) => text != "\n"; } diff --git a/Terminal.Gui/Views/DialogTResult.cs b/Terminal.Gui/Views/DialogTResult.cs index 81d4a37fd6..3a0ab97cb2 100644 --- a/Terminal.Gui/Views/DialogTResult.cs +++ b/Terminal.Gui/Views/DialogTResult.cs @@ -114,27 +114,11 @@ public Dialog () SetStyle (); } - private Size _minimumSubViewsSize; - /// public override void EndInit () { base.EndInit (); UpdateSizes (); - -#if DIALOG_SCROLLBARS - // Don't enable scrollbars until after initialized; otherwise they get created before - // our frame has dimensions. - ViewportSettings |= ViewportSettingsFlags.HasScrollBars; -#endif - } - - /// - protected override void OnSubViewAdded (View view) - { - _minimumSubViewsSize = new Size (GetWidthRequiredForSubViews (), GetHeightRequiredForSubViews ()); - UpdateSizes (); - base.OnSubViewAdded (view); } /// @@ -196,13 +180,8 @@ private void UpdateSizes () } // Always floor at Viewport size — the content area should never be smaller - // than what's visible. For DimAuto dialogs, the Frame may be larger than - // _minimumSubViewsSize (e.g. due to title width), so the content area - // should reflect the actual available space. - int subViewsWidth = Math.Max (_minimumSubViewsSize.Width, Viewport.Width); - int subViewsHeight = Math.Max (_minimumSubViewsSize.Height, Viewport.Height); - - SetContentSize (new Size (Math.Max (_minimumButtonsSize.Width, subViewsWidth), Math.Max (_minimumButtonsSize.Height, subViewsHeight))); + // than what's visible. + SetContentSize (new Size (Math.Max (_minimumButtonsSize.Width, Viewport.Width), Math.Max (_minimumButtonsSize.Height, Viewport.Height))); } /// @@ -212,10 +191,10 @@ private void UpdateSizes () /// private int GetMinimumDialogWidth () { - int minSize = Math.Max (Math.Max (_minimumSubViewsSize.Width, + int minSize = Math.Max ( - // Ensure space for title + borders - Title.GetColumns () + 4), + // Ensure space for title + borders + Title.GetColumns () + 4, _minimumButtonsSize.Width); return minSize; @@ -228,7 +207,7 @@ private int GetMinimumDialogWidth () /// private int GetMinimumDialogHeight () { - int minSize = Math.Max (_minimumSubViewsSize.Height, _minimumButtonsSize.Height - Border.Thickness.Vertical - Margin.Thickness.Vertical); + int minSize = _minimumButtonsSize.Height - Border.Thickness.Vertical - Margin.Thickness.Vertical; return minSize; } diff --git a/Terminal.Gui/Views/FileDialogs/AllowedType.cs b/Terminal.Gui/Views/FileDialogs/AllowedType.cs index 2bcdfbe4a0..05116a32d4 100644 --- a/Terminal.Gui/Views/FileDialogs/AllowedType.cs +++ b/Terminal.Gui/Views/FileDialogs/AllowedType.cs @@ -1,30 +1,5 @@ -#nullable disable - namespace Terminal.Gui.Views; -/// Interface for restrictions on which file type(s) the user is allowed to select/enter. -public interface IAllowedType -{ - /// - /// Returns true if the file at is compatible with this allow option. Note that the file - /// may not exist (e.g. in the case of saving). - /// - /// - /// - bool IsAllowed (string path); -} - -/// that allows selection of any types (*.*). -public class AllowedTypeAny : IAllowedType -{ - /// - public bool IsAllowed (string path) { return true; } - - /// Returns a string representation of this . - /// - public override string ToString () { return Strings.fdAnyFiles + "(*.*)"; } -} - /// /// Describes a requirement on what can be selected. This can be combined with other /// in a to for example show only .csv files but let user change to @@ -34,7 +9,7 @@ public class AllowedType : IAllowedType { /// Initializes a new instance of the class. /// The human-readable text to display. - /// Extension(s) to match e.g. .csv. + /// Extension(s) to match e.g. ".csv". public AllowedType (string description, params string [] extensions) { if (extensions.Length == 0) @@ -79,13 +54,13 @@ public bool IsAllowed (string path) /// Returns plus all separated by semicolons. public override string ToString () { - const int maxLength = 30; + const int MAX_LENGTH = 30; var desc = $"{Description} ({string.Join (";", Extensions.Select (e => '*' + e).ToArray ())})"; - if (desc.Length > maxLength) + if (desc.Length > MAX_LENGTH) { - return desc.Substring (0, maxLength - 2) + "…"; + return desc [..(MAX_LENGTH - 2)] + "…"; } return desc; diff --git a/Terminal.Gui/Views/FileDialogs/AllowedTypeAny.cs b/Terminal.Gui/Views/FileDialogs/AllowedTypeAny.cs new file mode 100644 index 0000000000..91ea037771 --- /dev/null +++ b/Terminal.Gui/Views/FileDialogs/AllowedTypeAny.cs @@ -0,0 +1,12 @@ +namespace Terminal.Gui.Views; + +/// that allows selection of any types (*.*). +public class AllowedTypeAny : IAllowedType +{ + /// + public bool IsAllowed (string path) => true; + + /// Returns a string representation of this . + /// + public override string ToString () => Strings.fdAnyFiles + " (*.*)"; +} diff --git a/Terminal.Gui/Views/FileDialogs/DefaultFileOperations.cs b/Terminal.Gui/Views/FileDialogs/DefaultFileOperations.cs index 1c74914035..09570a9716 100644 --- a/Terminal.Gui/Views/FileDialogs/DefaultFileOperations.cs +++ b/Terminal.Gui/Views/FileDialogs/DefaultFileOperations.cs @@ -1,4 +1,3 @@ -#nullable disable using System.IO.Abstractions; namespace Terminal.Gui.Views; @@ -7,18 +6,20 @@ namespace Terminal.Gui.Views; public class DefaultFileOperations : IFileOperations { /// - public bool Delete (IApplication app, IEnumerable toDelete) + public bool Delete (IApplication? app, IEnumerable toDelete) { // Default implementation does not allow deleting multiple files - if (toDelete.Count () != 1) + IEnumerable fileSystemInfos = toDelete as IFileSystemInfo [] ?? toDelete.ToArray (); + + if (fileSystemInfos.Count () != 1) { return false; } - IFileSystemInfo d = toDelete.Single (); + IFileSystemInfo d = fileSystemInfos.Single (); string adjective = d.Name; - int? result = MessageBox.Query (app, + int? result = MessageBox.Query (app ?? throw new ArgumentNullException (nameof (app)), string.Format (Strings.fdDeleteTitle, adjective), string.Format (Strings.fdDeleteBody, adjective), Strings.btnYes, @@ -49,52 +50,61 @@ public bool Delete (IApplication app, IEnumerable toDelete) } /// - public IFileSystemInfo Rename (IApplication app, IFileSystem fileSystem, IFileSystemInfo toRename) + public IFileSystemInfo? Rename (IApplication? app, IFileSystem fileSystem, IFileSystemInfo toRename) { // Don't allow renaming C: or D: or / (on linux) etc - if (toRename is IDirectoryInfo dir && dir.Parent is null) + if (toRename is IDirectoryInfo { Parent: null }) + { + return null; + } + + if (!Prompt (app ?? throw new ArgumentNullException (nameof (app)), Strings.fdRenameTitle, toRename.Name, out string newName)) + { + return null; + } + + if (string.IsNullOrWhiteSpace (newName)) { return null; } - if (Prompt (app, Strings.fdRenameTitle, toRename.Name, out string newName)) + try { - if (!string.IsNullOrWhiteSpace (newName)) + if (toRename is IFileInfo f) { - try - { - if (toRename is IFileInfo f) - { - IFileInfo newLocation = fileSystem.FileInfo.New (Path.Combine (f.Directory.FullName, newName)); - f.MoveTo (newLocation.FullName); - - return newLocation; - } - else - { - var d = (IDirectoryInfo)toRename; - - IDirectoryInfo newLocation = fileSystem.DirectoryInfo.New (Path.Combine (d.Parent.FullName, newName)); - d.MoveTo (newLocation.FullName); - - return newLocation; - } - } - catch (Exception ex) - { - MessageBox.ErrorQuery (app, Strings.fdRenameFailedTitle, ex.Message, Strings.btnOk); - } + IFileInfo newLocation = fileSystem.FileInfo.New (Path.Combine (f.Directory?.FullName ?? throw new InvalidOperationException (), newName)); + f.MoveTo (newLocation.FullName); + + return newLocation; + } + else + { + var d = (IDirectoryInfo)toRename; + + IDirectoryInfo newLocation = + fileSystem.DirectoryInfo.New (Path.Combine (d.Parent?.FullName ?? throw new InvalidOperationException (), newName)); + d.MoveTo (newLocation.FullName); + + return newLocation; } } + catch (Exception ex) + { + MessageBox.ErrorQuery (app, Strings.fdRenameFailedTitle, ex.Message, Strings.btnOk); + } return null; } /// - public IFileSystemInfo New (IApplication app, IFileSystem fileSystem, IDirectoryInfo inDirectory) + public IFileSystemInfo? New (IApplication? app, IFileSystem fileSystem, IDirectoryInfo inDirectory) { + if (app is null) + { + ArgumentNullException.ThrowIfNull (app); + } var tv = new TextField { Width = Dim.Fill (0, 50), Height = 1 }; - string result = app?.TopRunnable?.Prompt (tv, beginInitHandler: prompt => { prompt.Title = Strings.fdNewTitle; }); + string? result = app.TopRunnable?.Prompt (tv, beginInitHandler: prompt => { prompt.Title = Strings.fdNewTitle; }); if (string.IsNullOrWhiteSpace (result)) { diff --git a/Terminal.Gui/Views/FileDialogs/FileDialog.Commands.cs b/Terminal.Gui/Views/FileDialogs/FileDialog.Commands.cs index 7e61853e16..77786871c2 100644 --- a/Terminal.Gui/Views/FileDialogs/FileDialog.Commands.cs +++ b/Terminal.Gui/Views/FileDialogs/FileDialog.Commands.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.IO.Abstractions; namespace Terminal.Gui.Views; @@ -10,18 +11,10 @@ public partial class FileDialog /// /// /// - public bool IsCompatibleWithAllowedExtensions (IFileInfo file) => !AllowedTypes.Any () || MatchesAllowedTypes (file); + public bool IsCompatibleWithAllowedExtensions (IFileInfo file) => AllowedTypes.Count == 0 || MatchesAllowedTypes (file); /// - protected override bool OnAccepting (CommandEventArgs args) - { - if (Accept (true)) - { - return base.OnAccepting (args); - } - - return false; - } + protected override bool OnAccepting (CommandEventArgs args) => Accept (true) && base.OnAccepting (args); private void Accept (IEnumerable toMultiAccept) { @@ -154,16 +147,10 @@ private bool FinishAccept () .ToArray (); } - private bool IsCompatibleWithAllowedExtensions (string path) - { - // no restrictions - if (!AllowedTypes.Any ()) - { - return true; - } + private bool IsCompatibleWithAllowedExtensions (string path) => - return AllowedTypes.Any (t => t.IsAllowed (path)); - } + // no restrictions + AllowedTypes.Count == 0 || AllowedTypes.Any (t => t.IsAllowed (path)); private bool IsCompatibleWithOpenMode (string s, out string reason) { @@ -225,7 +212,7 @@ private bool IsCompatibleWithOpenMode (string s, out string reason) return false; - default: throw new ArgumentOutOfRangeException (nameof (OpenMode)); + default: throw new InvalidEnumArgumentException (nameof (OpenMode), (int)OpenMode, typeof (OpenMode)); } } @@ -241,9 +228,9 @@ private bool IsCompatibleWithOpenMode (string s, out string reason) /// private IEnumerable MultiRowToStats () { - HashSet toReturn = new (); + HashSet toReturn = []; - if (!AllowsMultipleSelection || !_tableView.MultiSelectedRegions.Any ()) + if (!AllowsMultipleSelection || _tableView.MultiSelectedRegions.Count == 0) { return toReturn; } @@ -260,7 +247,11 @@ private IEnumerable MultiRowToStats () private void New () { - IFileSystemInfo created = FileOperationsHandler.New (App, _fileSystem!, State!.Directory); + if (_fileSystem is null) + { + return; + } + IFileSystemInfo? created = FileOperationsHandler.New (App, _fileSystem, State!.Directory); RefreshState (); RestoreSelection (created); @@ -275,7 +266,7 @@ private void Rename (IApplication? app) return; } - IFileSystemInfo newNamed = FileOperationsHandler.Rename (app, _fileSystem!, toRename.Single ()!); + IFileSystemInfo? newNamed = FileOperationsHandler.Rename (app, _fileSystem!, toRename.Single ()!); RefreshState (); RestoreSelection (newNamed); diff --git a/Terminal.Gui/Views/FileDialogs/FileDialog.Navigation.cs b/Terminal.Gui/Views/FileDialogs/FileDialog.Navigation.cs index 80377139c3..1b8e68d773 100644 --- a/Terminal.Gui/Views/FileDialogs/FileDialog.Navigation.cs +++ b/Terminal.Gui/Views/FileDialogs/FileDialog.Navigation.cs @@ -29,7 +29,7 @@ internal void PushState (IDirectoryInfo d, bool addCurrentStateToHistory, bool s /// Select in the table view (if present) /// - internal void RestoreSelection (IFileSystemInfo toRestore) + internal void RestoreSelection (IFileSystemInfo? toRestore) { int row = State!.Children.IndexOf (r => r.FileSystemInfo == toRestore); _tableView.SetSelection (0, row >= 0 ? row : 0, false); @@ -78,7 +78,10 @@ private void PathChanged () PushState (dir.Parent, true, false); } - _tbPath.Autocomplete?.GenerateSuggestions (new AutocompleteFilepathContext (_tbPath.Text, _tbPath.InsertionPoint, State)); + if (State is { }) + { + _tbPath.Autocomplete?.GenerateSuggestions (new AutocompleteFilepathContext (_tbPath.Text, _tbPath.InsertionPoint, State)); + } } private void PushState (FileDialogState newState, bool addCurrentStateToHistory, bool setPathText = true, bool clearForward = true, string? pathText = null) @@ -189,7 +192,7 @@ private IDirectoryInfo StringToDirectoryInfo (string path) return _fileSystem!.DirectoryInfo.New (path); } - private void SuppressIfBadChar (Key k) + private static void SuppressIfBadChar (Key k) { // don't let user type bad letters var ch = (char)k; @@ -211,7 +214,7 @@ private void SetPathToSelectedObject (IFileSystemInfo? selected) if (selected is IDirectoryInfo && Style.PreserveFilenameOnDirectoryChanges) { - if (!string.IsNullOrWhiteSpace (Path) && !_fileSystem!.Directory.Exists (Path)) + if (!string.IsNullOrWhiteSpace (Path) && _fileSystem is { } && !_fileSystem.Directory.Exists (Path)) { string currentFile = _fileSystem.Path.GetFileName (Path); diff --git a/Terminal.Gui/Views/FileDialogs/FileDialog.TableView.cs b/Terminal.Gui/Views/FileDialogs/FileDialog.TableView.cs index 63ceee5180..2480e9fb5b 100644 --- a/Terminal.Gui/Views/FileDialogs/FileDialog.TableView.cs +++ b/Terminal.Gui/Views/FileDialogs/FileDialog.TableView.cs @@ -11,7 +11,7 @@ private void TableViewHandleCommandNotBound (object? sender, CommandEventArgs e) return; } - if (e.Context.Binding is MouseBinding { MouseEvent: { } mouse }) + if (e.Context.Binding is MouseBinding { MouseEvent: { Flags: MouseFlags.RightButtonClicked } mouse }) { Point? clickedCell = _tableView.ScreenToCell (mouse.Position!.Value.X, mouse.Position!.Value.Y, out int? clickedCol); @@ -258,6 +258,7 @@ private void SortColumn (int clickedCol) private static string StripArrows (string columnName) => columnName.Replace (" (▼)", string.Empty).Replace (" (▲)", string.Empty); + // TODO: Port this to use key bindings private bool TableView_KeyDown (Key keyEvent) { switch (keyEvent.KeyCode) @@ -285,7 +286,7 @@ private bool TableView_KeyDown (Key keyEvent) } } - // private void TableViewOnActivated (object? sender, EventArgs e) + // BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/5087#issuecomment-4328093883 private void TableViewOnValueChanged (object? sender, ValueChangedEventArgs e) { if (!_tableView.HasFocus || _tableView.Value is null || _tableView.Table?.Rows == 0) diff --git a/Terminal.Gui/Views/FileDialogs/FileDialog.cs b/Terminal.Gui/Views/FileDialogs/FileDialog.cs index aaa0a9a2f7..23eda2db1d 100644 --- a/Terminal.Gui/Views/FileDialogs/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialogs/FileDialog.cs @@ -27,13 +27,16 @@ public partial class FileDialog : Dialog, IDesignable private readonly Button _btnBack; + private readonly Button _btnForward; + private readonly Button _btnCancel; + /// - /// Gets the cancel button for the dialog. This is useful for checking if the user canceled the dialog by comparing + /// Gets the index of the cancel button for the dialog. This is useful for checking if the user canceled the dialog by + /// comparing /// the to the index of this button in the array. /// - public Button CancelButton { get; } + public int CancelButtonIndex => Buttons.IndexOf (_btnCancel); - private readonly Button _btnForward; private readonly Button _btnOk; private readonly Button _btnUp; private readonly FileDialogHistory _history; @@ -45,7 +48,7 @@ public partial class FileDialog : Dialog, IDesignable private readonly Button _btnTreeToggle; private readonly TreeView _treeView; private Dictionary _treeRoots = new (); - private DropDownList? _typeFilterDropDown; + private readonly DropDownList? _typeFilterDropDown; private int _currentSortColumn; private bool _currentSortIsAsc = true; private bool _disposed; @@ -71,7 +74,7 @@ internal FileDialog (IFileSystem? fileSystem) // Ensure we get Accept for any subviews; esp TreeView CommandsToBubbleUp = [Command.Accept]; - CancelButton = new Button { Text = Strings.btnCancel }; + _btnCancel = new Button { Text = Strings.btnCancel }; _btnOk = new Button { Text = Style.OkButtonText }; @@ -120,6 +123,14 @@ internal FileDialog (IFileSystem? fileSystem) _tbPath.Autocomplete = new AppendAutocomplete (_tbPath); _tbPath.Autocomplete.SuggestionGenerator = new FilepathSuggestionGenerator (); + _typeFilterDropDown = new DropDownList + { + X = Pos.AnchorEnd (), + Y = 1, + Visible = false + }; + Add (_typeFilterDropDown); + // Create table view container (right pane) _tableViewContainer = new View { @@ -201,18 +212,19 @@ internal FileDialog (IFileSystem? fileSystem) _tableView.KeyBindings.ReplaceCommands (Key.Home, Command.Start); _tableView.KeyBindings.ReplaceCommands (Key.End, Command.End); _tableView.KeyBindings.ReplaceCommands (Key.Home.WithShift, Command.StartExtend); - _tableView.KeyBindings.ReplaceCommands (Key.End.WithShift, Command.EndExtend); _history = new FileDialogHistory (this); + _tableView.KeyBindings.ReplaceCommands (Key.End.WithShift, Command.EndExtend); + _history = new FileDialogHistory (this); // Changing the key-bindings of a View is not allowed, however, // by default, Runnable doesn't bind to Command.Context, so // we can take advantage of the CommandNotBound event to handle it _tableView.CommandNotBound += TableViewHandleCommandNotBound; - _tableView.KeyBindings.Add (Key.Space.WithCtrl, Command.Context); + _tableView.KeyBindings.Add (PopoverMenu.DefaultKey, Command.Context); _tableView.MouseBindings.Add (MouseFlags.RightButtonClicked, Command.Context); _tbPath.TextChanged += (_, _) => PathChanged (); - _tbFind = new TextField { X = 1, Width = Dim.Width (_tableView) - 1, Y = Pos.AnchorEnd (), Id = "_tbFind" }; + _tbFind = new TextField { X = 0, Width = Dim.Width (_tableView) - 1, Y = Pos.AnchorEnd (), Id = "_tbFind" }; _spinnerView = new SpinnerView { @@ -247,7 +259,7 @@ internal FileDialog (IFileSystem? fileSystem) // Add the toggle along with OK/Cancel so they align as a group AddButton (_btnTreeToggle); - AddButton (CancelButton); + AddButton (_btnCancel); AddButton (_btnOk); Add (_tbPath); @@ -256,12 +268,58 @@ internal FileDialog (IFileSystem? fileSystem) Add (_btnForward); Add (_treeView); - // Default: Tree hidden and splitter hidden - SetTreeVisible (false); - Add (_tableViewContainer); _tableViewContainer.Add (_tbFind); _tableViewContainer.Add (_spinnerView); + + // to streamline user experience and allow direct typing of paths + // with zero navigation we start with focus in the text box and any + // default/current path fully selected and ready to be overwritten + _tbPath.SetFocus (); + + SetTreeVisible (false); + } + + /// + public override void EndInit () + { + base.EndInit (); + + // Style may have been updated after instance was constructed + _btnOk.Text = Style.OkButtonText; + _btnCancel.Text = Style.CancelButtonText; + _btnUp.Text = GetUpButtonText (); + _btnBack.Text = GetBackButtonText (); + _btnForward.Text = GetForwardButtonText (); + _tbPath.Title = Style.PathCaption; + _tbFind.Title = Style.SearchCaption; + _treeRoots = Style.TreeRootGetter (); + Style.IconProvider.IsOpenGetter = _treeView.IsExpanded; + _treeView.AddObjects (_treeRoots.Keys); + + // if filtering on file type is configured then create the DropDownList and establish + // initial filtering by extension(s) + if (AllowedTypes.Count > 0) + { + CurrentFilter = AllowedTypes [0]; + + _typeFilterDropDown?.Visible = true; + _typeFilterDropDown?.Source = new ListWrapper (new ObservableCollection (AllowedTypes.Select (a => a.ToString ()!).ToList ())); + _typeFilterDropDown?.Value = AllowedTypes [0].ToString () ?? string.Empty; + } + + // if no path has been provided + if (Path.Length <= 0) + { + Path = _fileSystem!.Directory.GetCurrentDirectory (); + } + + _tbPath.SelectAll (); + + if (string.IsNullOrEmpty (Title)) + { + Title = GetDefaultTitle (); + } } /// @@ -327,17 +385,6 @@ public string Path { _tbPath.Text = value; _tbPath.MoveEnd (); - - //IDirectoryInfo dir = StringToDirectoryInfo (value); - - //StringComparison comparison = OperatingSystem.IsWindows () ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - - //if (_treeView.ExpandParents (dir, (left, right) => string.Equals (left.FullName, right.FullName, comparison), out IFileSystemInfo? matched) - // && matched is { }) - //{ - // // _treeView.EnsureVisible (matched); - // // _treeView.SelectedObject = matched; - //} } } @@ -364,86 +411,6 @@ public string Path // TODO: Refactor to use CWP public event EventHandler? FilesSelected; - /// - protected override void OnIsRunningChanged (bool newIsRunning) - { - base.OnIsRunningChanged (newIsRunning); - - if (!newIsRunning) - { - return; - } - - Arrangement |= ViewArrangement.Resizable; - - // May have been updated after instance was constructed - _btnOk.Text = Style.OkButtonText; - CancelButton.Text = Style.CancelButtonText; - _btnUp.Text = GetUpButtonText (); - _btnBack.Text = GetBackButtonText (); - _btnForward.Text = GetForwardButtonText (); - - _tbPath.Title = Style.PathCaption; - _tbFind.Title = Style.SearchCaption; - - _tbPath.Autocomplete?.Scheme = new Scheme (_tbPath.GetScheme ()) - { - Normal = new Attribute (Color.Black, _tbPath.GetAttributeForRole (VisualRole.Normal).Background) - }; - - _treeRoots = Style.TreeRootGetter (); - Style.IconProvider.IsOpenGetter = _treeView.IsExpanded; - _treeView.AddObjects (_treeRoots.Keys); - - // if filtering on file type is configured then create the DropDownList and establish - // initial filtering by extension(s) - if (AllowedTypes.Any ()) - { - CurrentFilter = AllowedTypes [0]; - - _typeFilterDropDown = new DropDownList - { - X = Pos.AnchorEnd (), - Y = 1, - Source = new ListWrapper (new ObservableCollection (AllowedTypes.Select (a => a.ToString ()!).ToList ())), - Value = AllowedTypes [0].ToString () ?? string.Empty - }; - Add (_typeFilterDropDown); - } - - // if no path has been provided - if (_tbPath.Text.Length <= 0) - { - Path = _fileSystem!.Directory.GetCurrentDirectory (); - } - - // to streamline user experience and allow direct typing of paths - // with zero navigation we start with focus in the text box and any - // default/current path fully selected and ready to be overwritten - _tbPath.SetFocus (); - _tbPath.SelectAll (); - - if (string.IsNullOrEmpty (Title)) - { - Title = GetDefaultTitle (); - } - - // Ensure toggle button text matches current state after sizing - SetTreeVisible (false); - - SetNeedsDraw (); - SetNeedsLayout (); - } - - /// - protected override void Dispose (bool disposing) - { - _disposed = true; - base.Dispose (disposing); - - CancelSearch (); - } - /// /// Gets a default dialog title, when is not set or empty, result of the function will be /// shown. @@ -474,7 +441,7 @@ protected virtual string GetDefaultTitle () } /// State representing a recursive search from downwards. - internal class SearchState : FileDialogState + internal sealed class SearchState : FileDialogState { // TODO: Add thread safe child adding private readonly List _found = []; @@ -488,6 +455,7 @@ public SearchState (IDirectoryInfo dir, FileDialog parent, string searchTerms) : parent.SearchMatcher.Initialize (searchTerms); Children = []; BeginSearch (); + RefreshChildren (); } /// @@ -505,8 +473,6 @@ internal bool Cancel () return !alreadyCancelled; } - internal override void RefreshChildren () { } - private void BeginSearch () { Task.Run (() => @@ -615,4 +581,13 @@ bool IDesignable.EnableForDesign () return true; } + + /// + protected override void Dispose (bool disposing) + { + _disposed = true; + base.Dispose (disposing); + + CancelSearch (); + } } diff --git a/Terminal.Gui/Views/FileDialogs/FileDialogHistory.cs b/Terminal.Gui/Views/FileDialogs/FileDialogHistory.cs index 535a19777e..7da69c9509 100644 --- a/Terminal.Gui/Views/FileDialogs/FileDialogHistory.cs +++ b/Terminal.Gui/Views/FileDialogs/FileDialogHistory.cs @@ -1,24 +1,21 @@ -#nullable disable -using System.IO.Abstractions; +using System.IO.Abstractions; namespace Terminal.Gui.Views; -internal class FileDialogHistory +internal class FileDialogHistory (FileDialog dlg) { - private readonly Stack back = new (); - private readonly FileDialog dlg; - private readonly Stack forward = new (); - public FileDialogHistory (FileDialog dlg) { this.dlg = dlg; } + private readonly Stack _back = new (); + private readonly Stack _forward = new (); public bool Back () { - IDirectoryInfo goTo = null; - FileSystemInfoStats restoreSelection = null; - string restorePath = null; + IDirectoryInfo? goTo = null; + FileSystemInfoStats? restoreSelection = null; + string? restorePath = null; if (CanBack ()) { - FileDialogState backTo = back.Pop (); + FileDialogState backTo = _back.Pop (); goTo = backTo.Directory; restoreSelection = backTo.Selected; restorePath = backTo.Path; @@ -34,7 +31,7 @@ public bool Back () return false; } - forward.Push (dlg.State); + _forward.Push (dlg.State ?? throw new InvalidOperationException ()); dlg.PushState (goTo, false, true, false, restorePath); if (restoreSelection is { }) @@ -45,24 +42,23 @@ public bool Back () return true; } - internal bool CanBack () { return back.Count > 0; } - internal bool CanForward () { return forward.Count > 0; } - internal bool CanUp () { return dlg.State?.Directory.Parent != null; } - internal void ClearForward () { forward.Clear (); } + internal bool CanBack () => _back.Count > 0; + internal bool CanForward () => _forward.Count > 0; + internal bool CanUp () => dlg.State?.Directory.Parent != null; + internal void ClearForward () => _forward.Clear (); internal bool Forward () { - if (forward.Count > 0) + if (_forward.Count <= 0) { - dlg.PushState (forward.Pop ().Directory, true, true, false); - - return true; + return false; } + dlg.PushState (_forward.Pop ().Directory, true, true, false); - return false; + return true; } - internal void Push (FileDialogState state, bool clearForward) + internal void Push (FileDialogState? state, bool clearForward) { if (state is null) { @@ -70,29 +66,29 @@ internal void Push (FileDialogState state, bool clearForward) } // if changing to a new directory push onto the Back history - if (back.Count == 0 || back.Peek ().Directory.FullName != state.Directory.FullName) + if (_back.Count != 0 && _back.Peek ().Directory.FullName == state.Directory.FullName) { - back.Push (state); + return; + } + _back.Push (state); - if (clearForward) - { - ClearForward (); - } + if (clearForward) + { + ClearForward (); } } internal bool Up () { - IDirectoryInfo parent = dlg.State?.Directory.Parent; + IDirectoryInfo? parent = dlg.State?.Directory.Parent; - if (parent is { }) + if (parent is null) { - back.Push (new FileDialogState (parent, dlg)); - dlg.PushState (parent, false); - - return true; + return false; } + _back.Push (new FileDialogState (parent, dlg)); + dlg.PushState (parent, false); - return false; + return true; } } diff --git a/Terminal.Gui/Views/FileDialogs/FileDialogState.cs b/Terminal.Gui/Views/FileDialogs/FileDialogState.cs index aec6818175..4c267624cc 100644 --- a/Terminal.Gui/Views/FileDialogs/FileDialogState.cs +++ b/Terminal.Gui/Views/FileDialogs/FileDialogState.cs @@ -1,30 +1,28 @@ -#nullable disable -using System.IO.Abstractions; +using System.IO.Abstractions; namespace Terminal.Gui.Views; internal class FileDialogState { - protected readonly FileDialog Parent; - public FileDialogState (IDirectoryInfo dir, FileDialog parent) { - Directory = dir; Parent = parent; + Directory = dir; + Children = GetChildren (Directory).ToArray (); Path = parent.Path; - - RefreshChildren (); } + protected FileDialog Parent { get; } + public FileSystemInfoStats [] Children { get; internal set; } public IDirectoryInfo Directory { get; } /// Gets what was entered in the path text box of the dialog when the state was active. public string Path { get; } - public FileSystemInfoStats Selected { get; set; } + public FileSystemInfoStats? Selected { get; set; } - protected virtual IEnumerable GetChildren (IDirectoryInfo dir) + protected IEnumerable GetChildren (IDirectoryInfo dir) { try { @@ -33,26 +31,17 @@ protected virtual IEnumerable GetChildren (IDirectoryInfo d // if directories only if (Parent.OpenMode == OpenMode.Directory) { - children = dir.GetDirectories () - .Select (e => new FileSystemInfoStats (e, Parent.Style.Culture)) - .ToList (); + children = dir.GetDirectories ().Select (e => new FileSystemInfoStats (e, Parent.Style.Culture)).ToList (); } else { - children = dir.GetFileSystemInfos () - .Select (e => new FileSystemInfoStats (e, Parent.Style.Culture)) - .ToList (); + children = dir.GetFileSystemInfos ().Select (e => new FileSystemInfoStats (e, Parent.Style.Culture)).ToList (); } // if only allowing specific file types - if (Parent.AllowedTypes.Any () && Parent.OpenMode == OpenMode.File) + if (Parent.AllowedTypes.Count > 0 && Parent.OpenMode == OpenMode.File) { - children = children.Where ( - c => c.IsDir - || (c.FileSystemInfo is IFileInfo f - && Parent.IsCompatibleWithAllowedExtensions (f)) - ) - .ToList (); + children = children.Where (c => c.IsDir || (c.FileSystemInfo is IFileInfo f && Parent.IsCompatibleWithAllowedExtensions (f))).ToList (); } // if there's a UI filter in place too @@ -72,20 +61,12 @@ protected virtual IEnumerable GetChildren (IDirectoryInfo d catch (Exception) { // Access permissions Exceptions, Dir not exists etc - return Enumerable.Empty (); + return []; } } - protected bool MatchesApiFilter (FileSystemInfoStats arg) - { - return arg.IsDir - || (arg.FileSystemInfo is IFileInfo f - && Parent.CurrentFilter.IsAllowed (f.FullName)); - } + protected bool MatchesApiFilter (FileSystemInfoStats arg) => + Parent.CurrentFilter is { } && (arg.IsDir || (arg.FileSystemInfo is IFileInfo f && Parent.CurrentFilter.IsAllowed (f.FullName))); - internal virtual void RefreshChildren () - { - IDirectoryInfo dir = Directory; - Children = GetChildren (dir).ToArray (); - } + internal virtual void RefreshChildren () => Children = GetChildren (Directory).ToArray (); } diff --git a/Terminal.Gui/Views/FileDialogs/FileDialogTableSource.cs b/Terminal.Gui/Views/FileDialogs/FileDialogTableSource.cs index bf15d3bac0..d29fd14175 100644 --- a/Terminal.Gui/Views/FileDialogs/FileDialogTableSource.cs +++ b/Terminal.Gui/Views/FileDialogs/FileDialogTableSource.cs @@ -1,26 +1,9 @@ - namespace Terminal.Gui.Views; -internal class FileDialogTableSource ( - FileDialog? dlg, - FileDialogState? state, - FileDialogStyle? style, - int currentSortColumn, - bool currentSortIsAsc) +internal class FileDialogTableSource (FileDialog? dlg, FileDialogState? state, FileDialogStyle? style, int currentSortColumn, bool currentSortIsAsc) : ITableSource { - public object this [int row, int col] - { - get - { - if (state is { }) - { - return GetColumnValue (col, state.Children [row]); - } - - return string.Empty; - } - } + public object this [int row, int col] => state is { } ? GetColumnValue (col, state.Children [row]) : string.Empty; public int Rows => state is { } ? state.Children.Count () : 0; @@ -39,8 +22,11 @@ internal static object GetRawColumnValue (int col, FileSystemInfoStats? stats) switch (col) { case 0: return stats!.FileSystemInfo!.Name; + case 1: return stats!.MachineReadableLength; + case 2: return stats!.LastWriteTime ?? default (DateTime); + case 3: return stats!.Type; } @@ -60,9 +46,11 @@ private object GetColumnValue (int col, FileSystemInfoStats? stats) string icon = dlg!.Style.IconProvider.GetIconWithOptionalSpace (stats!.FileSystemInfo); - return (icon + (stats?.Name ?? string.Empty)).Trim (); + return (icon + stats.Name).Trim (); + case 1: return stats?.HumanReadableLength ?? string.Empty; + case 2: if (stats is null || stats.IsParent || stats.LastWriteTime is null) { @@ -70,8 +58,10 @@ private object GetColumnValue (int col, FileSystemInfoStats? stats) } return stats.LastWriteTime.Value.ToString (style!.DateFormat); + case 3: return stats?.Type ?? string.Empty; + default: throw new ArgumentOutOfRangeException (nameof (col)); } diff --git a/Terminal.Gui/Views/FileDialogs/FilesSelectedEventArgs.cs b/Terminal.Gui/Views/FileDialogs/FilesSelectedEventArgs.cs index f8629e31a4..a219c95e24 100644 --- a/Terminal.Gui/Views/FileDialogs/FilesSelectedEventArgs.cs +++ b/Terminal.Gui/Views/FileDialogs/FilesSelectedEventArgs.cs @@ -1,5 +1,3 @@ -#nullable disable - namespace Terminal.Gui.Views; /// Event args for the event @@ -7,7 +5,7 @@ public class FilesSelectedEventArgs : EventArgs { /// Creates a new instance of the /// - public FilesSelectedEventArgs (FileDialog dialog) { Dialog = dialog; } + public FilesSelectedEventArgs (FileDialog dialog) => Dialog = dialog; /// /// Set to true if you want to prevent the selection going ahead (this will leave the diff --git a/Terminal.Gui/Views/FileDialogs/IAllowedType.cs b/Terminal.Gui/Views/FileDialogs/IAllowedType.cs new file mode 100644 index 0000000000..6c51cc2a4a --- /dev/null +++ b/Terminal.Gui/Views/FileDialogs/IAllowedType.cs @@ -0,0 +1,13 @@ +namespace Terminal.Gui.Views; + +/// Interface for restrictions on which file type(s) the user is allowed to select/enter. +public interface IAllowedType +{ + /// + /// Returns true if the file at is compatible with this allow option. Note that the file + /// may not exist (e.g. in the case of saving). + /// + /// + /// + bool IsAllowed (string path); +} diff --git a/Terminal.Gui/Views/FileDialogs/OpenDialog.cs b/Terminal.Gui/Views/FileDialogs/OpenDialog.cs index 669635cb44..c6df91ee9b 100644 --- a/Terminal.Gui/Views/FileDialogs/OpenDialog.cs +++ b/Terminal.Gui/Views/FileDialogs/OpenDialog.cs @@ -1,15 +1,3 @@ -#nullable disable -// -// FileDialog.cs: File system dialogs for open and save -// -// TODO: -// * Add directory selector -// * Implement subclasses -// * Figure out why message text does not show -// * Remove the extra space when message does not show -// * Use a line separator to show the file listing, so we can use same colors as the rest -// * DirListView: Add mouse support - using System.Collections.ObjectModel; namespace Terminal.Gui.Views; @@ -26,7 +14,7 @@ namespace Terminal.Gui.Views; /// . This will run the dialog modally, and when this returns, /// the list of files will be available on the property. /// -/// To select more than one file, users can use the space key, or CTRL-T. +/// Use `Ctrl-click` or `Space` to select multiple files. `Alt-Click` extends the selection. /// public class OpenDialog : FileDialog { @@ -36,7 +24,7 @@ public OpenDialog () { } /// Returns the selected files, or an empty list if nothing has been selected /// The file paths. public IReadOnlyList FilePaths => - ((IRunnable)this).Result is null || Result == Buttons.IndexOf (CancelButton) ? Enumerable.Empty ().ToList ().AsReadOnly () : + ((IRunnable)this).Result is null || Result == CancelButtonIndex ? Enumerable.Empty ().ToList ().AsReadOnly () : AllowsMultipleSelection ? MultiSelected : new ReadOnlyCollection ([Path]); /// diff --git a/Terminal.Gui/Views/FileDialogs/OpenMode.cs b/Terminal.Gui/Views/FileDialogs/OpenMode.cs index e3e62d60f4..494d82e553 100644 --- a/Terminal.Gui/Views/FileDialogs/OpenMode.cs +++ b/Terminal.Gui/Views/FileDialogs/OpenMode.cs @@ -1,5 +1,4 @@ -#nullable disable -namespace Terminal.Gui.Views; +namespace Terminal.Gui.Views; /// Determine which type to open. public enum OpenMode diff --git a/Terminal.Gui/Views/FileDialogs/SaveDialog.cs b/Terminal.Gui/Views/FileDialogs/SaveDialog.cs index 1940895ed9..b0af109e7c 100644 --- a/Terminal.Gui/Views/FileDialogs/SaveDialog.cs +++ b/Terminal.Gui/Views/FileDialogs/SaveDialog.cs @@ -1,13 +1,4 @@ // -// FileDialog.cs: File system dialogs for open and save -// -// TODO: -// * Add directory selector -// * Implement subclasses -// * Figure out why message text does not show -// * Remove the extra space when message does not show -// * Use a line separator to show the file listing, so we can use same colors as the rest -// * DirListView: Add mouse support using System.IO.Abstractions; @@ -34,7 +25,7 @@ public class SaveDialog : FileDialog /// . /// /// The name of the file. - public string? FileName => (this as IRunnable).Result is null || Result == Buttons.IndexOf (CancelButton) ? null : Path; + public string? FileName => (this as IRunnable).Result is null || Result == CancelButtonIndex ? null : Path; /// Gets the default title for the . /// diff --git a/Terminal.sln b/Terminal.sln index 8196f36ae1..16239ed1db 100644 --- a/Terminal.sln +++ b/Terminal.sln @@ -159,8 +159,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InlineSelect", "Examples\In EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Terminal.Gui.Analyzers.Internal", "Terminal.Gui.Analyzers.Internal\Terminal.Gui.Analyzers.Internal.csproj", "{927CCC07-F00C-409C-BE42-458EB03DD4E8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AI", "Examples\AI\AI.csproj", "{1737CFE6-456F-B41B-70D0-2F9EC6BE554F}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -459,19 +457,6 @@ Global {927CCC07-F00C-409C-BE42-458EB03DD4E8}.Release|x64.Build.0 = Release|Any CPU {927CCC07-F00C-409C-BE42-458EB03DD4E8}.Release|x86.ActiveCfg = Release|Any CPU {927CCC07-F00C-409C-BE42-458EB03DD4E8}.Release|x86.Build.0 = Release|Any CPU - {1737CFE6-456F-B41B-70D0-2F9EC6BE554F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1737CFE6-456F-B41B-70D0-2F9EC6BE554F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1737CFE6-456F-B41B-70D0-2F9EC6BE554F}.Debug|x64.ActiveCfg = Debug|Any CPU - {1737CFE6-456F-B41B-70D0-2F9EC6BE554F}.Debug|x64.Build.0 = Debug|Any CPU - {1737CFE6-456F-B41B-70D0-2F9EC6BE554F}.Debug|x86.ActiveCfg = Debug|Any CPU - {1737CFE6-456F-B41B-70D0-2F9EC6BE554F}.Debug|x86.Build.0 = Debug|Any CPU - {1737CFE6-456F-B41B-70D0-2F9EC6BE554F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1737CFE6-456F-B41B-70D0-2F9EC6BE554F}.Release|Any CPU.Build.0 = Release|Any CPU - {1737CFE6-456F-B41B-70D0-2F9EC6BE554F}.Release|x64.ActiveCfg = Release|Any CPU - {1737CFE6-456F-B41B-70D0-2F9EC6BE554F}.Release|x64.Build.0 = Release|Any CPU - {1737CFE6-456F-B41B-70D0-2F9EC6BE554F}.Release|x86.ActiveCfg = Release|Any CPU - {1737CFE6-456F-B41B-70D0-2F9EC6BE554F}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -502,7 +487,6 @@ Global {90A42AE4-301D-4B05-8892-60BE5209C1B5} = {3DD033C0-E023-47BF-A808-9CCE30873C3E} {70802F77-F259-44C6-9522-46FCE2FD754E} = {3DD033C0-E023-47BF-A808-9CCE30873C3E} {3116547F-A8F2-4189-BC22-0B47C757164C} = {3DD033C0-E023-47BF-A808-9CCE30873C3E} - {1737CFE6-456F-B41B-70D0-2F9EC6BE554F} = {3DD033C0-E023-47BF-A808-9CCE30873C3E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9F8F8A4D-7B8D-4C2A-AC5E-CD7117F74C03}