From e0688ebcad2dcc84ef967db7c1b9b1fe3a6d7054 Mon Sep 17 00:00:00 2001 From: "Frank R. Haugen" Date: Fri, 16 Aug 2024 13:53:21 +0200 Subject: [PATCH] Add ScriptRunner infrastructure and update UI for script execution Implemented ScriptRunner and supporting classes to execute Roslyn C# scripts. Refactored the `CSharpScriptControl` to utilize this new infrastructure, added a menu for running scripts and auto-run functionality, and introduced a JSON serialization for non-string result types. --- .../CsharpSyntaxTreeViewFactory.cs | 97 +++++++++ .../Frank.Wpf.Controls.CSharpRenderer.csproj | 14 ++ .../ExpandoControl.cs | 2 +- .../CSharpScriptControl.cs | 175 ++++++++++------ .../Frank.Wpf.Controls.RoslynScript.csproj | 4 +- .../ScriptGlobals.cs | 13 ++ .../ScriptRunner.cs | 57 +++-- .../ScriptRunnerBase.cs | 56 +++++ .../ScriptRunnerBuilder.cs | 49 +++++ .../TextBoxWithLineNumbers.cs | 8 + Frank.Wpf.Tests.App/DefaultWindow.cs | 194 +++--------------- .../Windows/CSharpScriptingWindow.cs | 12 ++ Frank.Wpf.Tests/CsharpTreeViewFactoryTests.cs | 69 +++++++ Frank.Wpf.Tests/Frank.Wpf.Tests.csproj | 1 + Frank.Wpf.sln | 6 + 15 files changed, 514 insertions(+), 243 deletions(-) create mode 100644 Frank.Wpf.Controls.CSharpRenderer/CsharpSyntaxTreeViewFactory.cs create mode 100644 Frank.Wpf.Controls.CSharpRenderer/Frank.Wpf.Controls.CSharpRenderer.csproj create mode 100644 Frank.Wpf.Controls.RoslynScript/ScriptGlobals.cs create mode 100644 Frank.Wpf.Controls.RoslynScript/ScriptRunnerBase.cs create mode 100644 Frank.Wpf.Controls.RoslynScript/ScriptRunnerBuilder.cs create mode 100644 Frank.Wpf.Tests.App/Windows/CSharpScriptingWindow.cs create mode 100644 Frank.Wpf.Tests/CsharpTreeViewFactoryTests.cs diff --git a/Frank.Wpf.Controls.CSharpRenderer/CsharpSyntaxTreeViewFactory.cs b/Frank.Wpf.Controls.CSharpRenderer/CsharpSyntaxTreeViewFactory.cs new file mode 100644 index 0000000..5788fa7 --- /dev/null +++ b/Frank.Wpf.Controls.CSharpRenderer/CsharpSyntaxTreeViewFactory.cs @@ -0,0 +1,97 @@ +using System.Windows.Controls; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Frank.Wpf.Controls.CSharpRenderer; + +public class CsharpSyntaxTreeViewFactory +{ + public TreeView Create(SyntaxTree syntaxTree) + { + var treeView = new TreeView(); + var root = syntaxTree.GetRoot(); + + // Find all namespace declarations + var namespaceNodes = root.DescendantNodes().OfType().ToList(); + + // Add namespaces first + foreach (var namespaceNode in namespaceNodes) + { + var namespaceItem = new TreeViewItem + { + Header = namespaceNode.Name.ToString() + }; + + treeView.Items.Add(namespaceItem); + + // For each namespace, add the types within it + AddTypesToNamespace(namespaceItem, namespaceNode); + } + + // Find all types that are not inside namespaces + var nonNamespaceTypes = root.DescendantNodes().OfType() + .Where(t => !t.Ancestors().OfType().Any()).ToList(); + + // Add these types at the root level, after namespaces + foreach (var typeNode in nonNamespaceTypes) + { + var typeItem = new TreeViewItem + { + Header = typeNode.Identifier.Text + }; + + treeView.Items.Add(typeItem); + + // Add members (methods, properties, etc.) to the type + AddMembersToType(typeItem, typeNode); + } + + return treeView; + } + + private void AddTypesToNamespace(TreeViewItem namespaceItem, NamespaceDeclarationSyntax namespaceNode) + { + var types = namespaceNode.DescendantNodes().OfType(); + + foreach (var typeNode in types) + { + var typeItem = new TreeViewItem + { + Header = typeNode.Identifier.Text + }; + + namespaceItem.Items.Add(typeItem); + + // Add members (methods, properties, etc.) to the type + AddMembersToType(typeItem, typeNode); + } + } + + private void AddMembersToType(TreeViewItem typeItem, TypeDeclarationSyntax typeNode) + { + // Add methods + var methodNodes = typeNode.DescendantNodes().OfType(); + foreach (var methodNode in methodNodes) + { + var methodItem = new TreeViewItem + { + Header = $"{methodNode.Identifier.Text}({string.Join(", ", methodNode.ParameterList.Parameters.Select(p => p.Type.ToString()))})" + }; + + typeItem.Items.Add(methodItem); + } + + // Add properties + var propertyNodes = typeNode.DescendantNodes().OfType(); + foreach (var propertyNode in propertyNodes) + { + var propertyItem = new TreeViewItem + { + Header = propertyNode.Identifier.Text + }; + + typeItem.Items.Add(propertyItem); + } + } +} diff --git a/Frank.Wpf.Controls.CSharpRenderer/Frank.Wpf.Controls.CSharpRenderer.csproj b/Frank.Wpf.Controls.CSharpRenderer/Frank.Wpf.Controls.CSharpRenderer.csproj new file mode 100644 index 0000000..3b7a465 --- /dev/null +++ b/Frank.Wpf.Controls.CSharpRenderer/Frank.Wpf.Controls.CSharpRenderer.csproj @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Frank.Wpf.Controls.ExpandoControl/ExpandoControl.cs b/Frank.Wpf.Controls.ExpandoControl/ExpandoControl.cs index 1a3a6c7..6deee98 100644 --- a/Frank.Wpf.Controls.ExpandoControl/ExpandoControl.cs +++ b/Frank.Wpf.Controls.ExpandoControl/ExpandoControl.cs @@ -7,6 +7,6 @@ public class ExpandoControl : ContentControl { static ExpandoControl() { - DefaultStyleKeyProperty.OverrideMetadata(typeof(ExpandoControl), new FrameworkPropertyMetadata(typeof(ExpandoControl))); + } } \ No newline at end of file diff --git a/Frank.Wpf.Controls.RoslynScript/CSharpScriptControl.cs b/Frank.Wpf.Controls.RoslynScript/CSharpScriptControl.cs index 081fae9..0870eb3 100644 --- a/Frank.Wpf.Controls.RoslynScript/CSharpScriptControl.cs +++ b/Frank.Wpf.Controls.RoslynScript/CSharpScriptControl.cs @@ -1,99 +1,142 @@ +using System.Text.Json; +using System.Text.Json.Serialization; using System.Windows; using System.Windows.Controls; +using Frank.Wpf.Controls.SimpleInputs; namespace Frank.Wpf.Controls.RoslynScript; -public class CSharpScriptControl : ContentControl +public class CSharpScriptControl : UserControl { - private StackPanel _stackPanel = new(); - private CheckBox _autorunCheckBox; - private TextBox _inputTextBox; - private Button _runButton; - private TextBlock _outputTextBlock; - - private ScriptRunner _scriptRunner = new(); + private readonly TextBoxWithLineNumbers _outputTextBlock; + + private readonly ScriptRunner _scriptRunner; private string _code; - + private bool _autorun; + private readonly MenuItem _runMenuItem; + public CSharpScriptControl() { - _autorunCheckBox = CreateAutorunCheckBox(); + var scriptRunnerBuilder = new ScriptRunnerBuilder() + .WithReference(typeof(object).Assembly) + .WithReference(typeof(Enumerable).Assembly) + .WithReference(typeof(MessageBox).Assembly) + .WithReference(typeof(Enumerable).Assembly) + .WithReference(typeof(IQueryable).Assembly); + _scriptRunner = scriptRunnerBuilder.Build(); + var menu = new Menu(); + _runMenuItem = new MenuItem { Header = "Run" }; + _runMenuItem.Click += async (sender, args) => await ExecuteScriptAsync(); + menu.Items.Add(_runMenuItem); + + var autorunCheckBox = CreateAutorunCheckBox(); + var autorunMenuItem = new MenuItem { Header = autorunCheckBox }; + menu.Items.Add(autorunMenuItem); + _outputTextBlock = CreateOutputTextBlock(); - _runButton = CreateRunButton(); - _inputTextBox = CreateInputTextBox(); + var inputTextBox = CreateInputTextBox(); + + var inputGroupBox = new GroupBox + { + Header = "Input", + Content = inputTextBox + }; - _stackPanel.Children.Add(_autorunCheckBox); - _stackPanel.Children.Add(_inputTextBox); - _stackPanel.Children.Add(_runButton); - _stackPanel.Children.Add(_outputTextBlock); + var outputGroupBox = new GroupBox + { + Header = "Output", + Content = _outputTextBlock + }; - Content = _stackPanel; + var stackPanel = new StackPanel() + { + Orientation = Orientation.Horizontal + }; + stackPanel.Children.Add(inputGroupBox); + stackPanel.Children.Add(outputGroupBox); + + var outerStackPanel = new StackPanel(); + outerStackPanel.Children.Add(menu); + outerStackPanel.Children.Add(stackPanel); + + Content = outerStackPanel; } - + private CheckBox CreateAutorunCheckBox() { - var checkBox = new CheckBox(); - checkBox.Content = "Autorun"; - checkBox.IsChecked = false; - checkBox.Checked += (sender, args) => _runButton.IsEnabled = false; - checkBox.Unchecked += (sender, args) => _runButton.IsEnabled = true; - + var checkBox = new CheckBox + { + Content = "Autorun", + IsChecked = false + }; + + checkBox.Checked += (sender, args) => + { + _autorun = true; + _runMenuItem.IsEnabled = false; + }; + + checkBox.Unchecked += (sender, args) => + { + _autorun = false; + _runMenuItem.IsEnabled = true; + }; + return checkBox; } - - private TextBlock CreateOutputTextBlock() - { - var textBlock = new TextBlock(); - textBlock.Text = "Output"; - return textBlock; - } - - private Button CreateRunButton() + + private TextBoxWithLineNumbers CreateOutputTextBlock() { - var button = new Button(); - button.Content = "Run"; - button.Click += Button_Click; - return button; + return new TextBoxWithLineNumbers() { Text = "Output" }; } - - private TextBox CreateInputTextBox() + + private TextBoxWithLineNumbers CreateInputTextBox() { - var textBox = new TextBox(); - textBox.Text = "return \"Hello, \nWorld!\";"; - textBox.AcceptsReturn = true; - textBox.AcceptsTab = true; - textBox.TextWrapping = TextWrapping.Wrap; - textBox.VerticalScrollBarVisibility = ScrollBarVisibility.Auto; - - textBox.TextChanged += (sender, args) => + var textBox = new TextBoxWithLineNumbers { - if (_autorunCheckBox.IsChecked == true) + TextWrapping = TextWrapping.Wrap, + }; + + textBox.TextChanged += async () => + { + _code = textBox.Text; + if (_autorun) { - _code = textBox.Text; - try - { - var result = _scriptRunner.RoslynScriptingAsync(_code).Result; - _outputTextBlock.Text = result; - } - catch (Exception exception) - { - // Do nothing - } + await ExecuteScriptAsync(); } }; + return textBox; } - - private void Button_Click(object sender, RoutedEventArgs e) + + private async Task ExecuteScriptAsync() { - _code = _inputTextBox.Text; try { - var result = _scriptRunner.RoslynScriptingAsync(_code).Result; - _outputTextBlock.Text = result; + var result = await _scriptRunner.RunAsync(_code); + + var resultType = result?.GetType(); + if (resultType == null) + { + _outputTextBlock.Text = "null"; + return; + } + + if (resultType == typeof(string)) + { + _outputTextBlock.Text = result?.ToString() ?? "null"; + return; + } + + var jsonDocument = JsonSerializer.SerializeToDocument(result); + var jsonElement = jsonDocument.RootElement; + var prettyPrintedJson = JsonSerializer.Serialize(jsonElement, new JsonSerializerOptions { WriteIndented = true, Converters = { new JsonStringEnumConverter()}}); + + _outputTextBlock.Text = prettyPrintedJson; } - catch (Exception exception) + catch (Exception ex) { - _outputTextBlock.Text = exception.Message; + _outputTextBlock.Text = ex.Message; } } -} \ No newline at end of file +} diff --git a/Frank.Wpf.Controls.RoslynScript/Frank.Wpf.Controls.RoslynScript.csproj b/Frank.Wpf.Controls.RoslynScript/Frank.Wpf.Controls.RoslynScript.csproj index 556a05d..10b8950 100644 --- a/Frank.Wpf.Controls.RoslynScript/Frank.Wpf.Controls.RoslynScript.csproj +++ b/Frank.Wpf.Controls.RoslynScript/Frank.Wpf.Controls.RoslynScript.csproj @@ -1,14 +1,14 @@  - false - + + diff --git a/Frank.Wpf.Controls.RoslynScript/ScriptGlobals.cs b/Frank.Wpf.Controls.RoslynScript/ScriptGlobals.cs new file mode 100644 index 0000000..b485b8f --- /dev/null +++ b/Frank.Wpf.Controls.RoslynScript/ScriptGlobals.cs @@ -0,0 +1,13 @@ +namespace Frank.Wpf.Controls.RoslynScript; + +public class ScriptGlobals +{ + private readonly Dictionary _globals; + + public ScriptGlobals(Dictionary globals) + { + _globals = globals; + } + + public object? this[string name] => _globals.ContainsKey(name) ? _globals[name] : null; +} \ No newline at end of file diff --git a/Frank.Wpf.Controls.RoslynScript/ScriptRunner.cs b/Frank.Wpf.Controls.RoslynScript/ScriptRunner.cs index fce559d..8990255 100644 --- a/Frank.Wpf.Controls.RoslynScript/ScriptRunner.cs +++ b/Frank.Wpf.Controls.RoslynScript/ScriptRunner.cs @@ -1,30 +1,59 @@ -using System.Reflection; -using Microsoft.CodeAnalysis.CSharp.Scripting; +using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; namespace Frank.Wpf.Controls.RoslynScript; -public class ScriptRunner +public class ScriptRunner : ScriptRunnerBase { - private readonly ScriptOptions _scriptOptions; + internal ScriptRunner(ScriptOptions scriptOptions, Dictionary globals) + : base(scriptOptions, globals) { } - public ScriptRunner() + public object? Run(string code) + => RunInternalAsync(code, false).GetAwaiter().GetResult(); + + public Task RunAsync(string code) + => RunInternalAsync(code, true); + + private async Task RunInternalAsync(string code, bool isAsync) { - _scriptOptions = ScriptOptions.Default - .AddReferences(typeof(string).Assembly) - .AddImports("System"); + var script = CSharpScript.Create(code, ScriptOptions, globalsType: typeof(ScriptGlobals)); + var state = isAsync + ? await script.RunAsync(new ScriptGlobals(Globals)) + : script.RunAsync(new ScriptGlobals(Globals)).GetAwaiter().GetResult(); + + return state?.ReturnValue; } - public async Task RoslynScriptingAsync(string code, params Assembly[] assemblies) + public override Type GetExpectedOutputType() { - _scriptOptions.AddReferences(assemblies); + return typeof(object); // Non-generic returns object + } +} + +public class ScriptRunner : ScriptRunnerBase +{ + internal ScriptRunner(ScriptOptions scriptOptions, Dictionary globals) + : base(scriptOptions, globals) { } - var script = CSharpScript.Create(code, _scriptOptions); + public T? Run(string code) + => RunInternalAsync(code, false).GetAwaiter().GetResult(); - var state = await script.RunAsync(); + public Task RunAsync(string code) + => RunInternalAsync(code, true); - var result = state.ReturnValue; + private async Task RunInternalAsync(string code, bool isAsync) + { + var script = CSharpScript.Create(code, ScriptOptions, globalsType: typeof(ScriptGlobals)); + var state = isAsync + ? await script.RunAsync(new ScriptGlobals(Globals)) + : script.RunAsync(new ScriptGlobals(Globals)).GetAwaiter().GetResult(); - return result; + var returnValue = state.ReturnValue; + return returnValue == null ? default : (T)returnValue; + } + + public override Type GetExpectedOutputType() + { + return typeof(T); // Generic returns the type T } } \ No newline at end of file diff --git a/Frank.Wpf.Controls.RoslynScript/ScriptRunnerBase.cs b/Frank.Wpf.Controls.RoslynScript/ScriptRunnerBase.cs new file mode 100644 index 0000000..03621d2 --- /dev/null +++ b/Frank.Wpf.Controls.RoslynScript/ScriptRunnerBase.cs @@ -0,0 +1,56 @@ +using System.Text; +using Microsoft.CodeAnalysis.Scripting; + +namespace Frank.Wpf.Controls.RoslynScript; + +public abstract class ScriptRunnerBase +{ + protected ScriptOptions ScriptOptions { get; } + protected Dictionary Globals { get; } + + protected ScriptRunnerBase(ScriptOptions scriptOptions, Dictionary globals) + { + ScriptOptions = scriptOptions; + Globals = globals; + } + + public IEnumerable GetAvailableNamespaces() + { + var namespaces = new HashSet(); + foreach (var import in ScriptOptions.Imports) + { + var parts = import.Split(' '); + if (parts.Length == 1) + { + namespaces.Add(parts[0]); + } + else if (parts.Length == 2 && parts[0] == "using") + { + namespaces.Add(parts[1]); + } + } + + return namespaces; + } + + public string GetInputParameters() + { + var parameters = Globals.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.GetType()); + + var stringBuilder = new StringBuilder(); + + foreach (var parameter in parameters) + { + stringBuilder.Append($"{parameter.Value.Name} {parameter.Key}, "); + } + + if (stringBuilder.Length > 0) + { + stringBuilder.Length -= 2; + } + + return stringBuilder.ToString(); + } + + public abstract Type GetExpectedOutputType(); +} \ No newline at end of file diff --git a/Frank.Wpf.Controls.RoslynScript/ScriptRunnerBuilder.cs b/Frank.Wpf.Controls.RoslynScript/ScriptRunnerBuilder.cs new file mode 100644 index 0000000..274296d --- /dev/null +++ b/Frank.Wpf.Controls.RoslynScript/ScriptRunnerBuilder.cs @@ -0,0 +1,49 @@ +using System.Reflection; +using Microsoft.CodeAnalysis.Scripting; + +namespace Frank.Wpf.Controls.RoslynScript; + +public class ScriptRunnerBuilder +{ + private readonly ScriptOptions _scriptOptions; + private readonly Dictionary _globals = new(); + + public ScriptRunnerBuilder() + { + _scriptOptions = ScriptOptions.Default + .AddReferences(typeof(string).Assembly) + .AddImports("System") + .AddImports("System.Collections.Generic") + .AddImports("System.Text") + .AddImports("System.Threading.Tasks") + .AddImports("System.Text.RegularExpressions"); + } + + public ScriptRunnerBuilder WithReference(Assembly assembly) + { + _scriptOptions.AddReferences(assembly); + return this; + } + + public ScriptRunnerBuilder WithImport(string import) + { + _scriptOptions.AddImports(import); + return this; + } + + public ScriptRunnerBuilder WithInput(string name, object value) + { + _globals[name] = value; + return this; + } + + public ScriptRunner Build() + { + return new ScriptRunner(_scriptOptions, _globals); + } + + public ScriptRunner Build() + { + return new ScriptRunner(_scriptOptions, _globals); + } +} \ No newline at end of file diff --git a/Frank.Wpf.Controls.SimpleInputs/TextBoxWithLineNumbers.cs b/Frank.Wpf.Controls.SimpleInputs/TextBoxWithLineNumbers.cs index bb048ab..7f0440a 100644 --- a/Frank.Wpf.Controls.SimpleInputs/TextBoxWithLineNumbers.cs +++ b/Frank.Wpf.Controls.SimpleInputs/TextBoxWithLineNumbers.cs @@ -62,6 +62,12 @@ public TextBoxWithLineNumbers() Content = scrollViewer; UpdateLineNumbers(); + + // Scroll to the left when the control is loaded + Loaded += (s, e) => + { + scrollViewer.ScrollToHorizontalOffset(0); + }; } public void Clear() => _textBox.Clear(); @@ -132,5 +138,7 @@ private void UpdateLineNumbers() var lines = Enumerable.Range(1, lineCount).Select(i => i.ToString()).ToArray(); _lineNumbers.Text = string.Join(Environment.NewLine, lines); } + + public event Action TextChanged; } } diff --git a/Frank.Wpf.Tests.App/DefaultWindow.cs b/Frank.Wpf.Tests.App/DefaultWindow.cs index 20ea3a2..b2b3e60 100644 --- a/Frank.Wpf.Tests.App/DefaultWindow.cs +++ b/Frank.Wpf.Tests.App/DefaultWindow.cs @@ -4,175 +4,49 @@ using Frank.Wpf.Hosting; using Frank.Wpf.Tests.App.Windows; -namespace Frank.Wpf.Tests.App; - public class DefaultWindow : MainWindow { private readonly ButtonStack _windowButtons = new(Orientation.Vertical); + private const int DefaultWidth = 400; + private const int DefaultHeight = 300; + private readonly WindowStartupLocation _defaultPosition = WindowStartupLocation.CenterScreen; + private readonly Dictionary> _windows = new() + { + { "Show Big Text Input Window", () => new BigTextInputWindow() }, + { "Show Code Window", () => new CodeWindow() }, + { "Show Console Window", () => new ConsoleWindow() }, + { "Show CSharp Scripting Window", () => new CSharpScriptingWindow() }, + { "Show Custom List Box Window", () => new CustomListBoxWindow() }, + { "Show Dropdown Window", () => new DropdownWindow() }, + { "Show Json Window", () => new JsonWindow() }, + { "Show Key-Value Pair Editor Window", () => new KvpEditorWindow() }, + { "Show Paged Tab Control Window", () => new PagedTabControlWindow() }, + { "Show Searchable Selection List Window", () => new SearchableSelectionListWindow() }, + { "Show SQL Runner", () => new SqlLiteWindow() }, + { "Show Text Box With Line Numbers Window", () => new TextBoxWithLineNumbersWindow() }, + { "Show Text Completion Window", () => new TextCompletionWindow() }, + { "Show Text Label Experimentation Window", () => new TextLabelExperimentationWindow() }, + { "Show Xml Window", () => new XmlWindow() } + }; + public DefaultWindow() { - var defaultWidth = 400; - var defaultHeight = 300; - var defaultPosition = WindowStartupLocation.CenterScreen; - Title = "Frank.Wpf.Tests.App"; Content = _windowButtons; SizeToContent = SizeToContent.WidthAndHeight; - WindowStartupLocation = defaultPosition; - - _windowButtons.AddButton("Show SQL Runner", (sender, args) => - { - var window = new SqlLiteWindow - { - MinWidth = defaultWidth, - MinHeight = defaultHeight, - WindowStartupLocation = defaultPosition - }; - window.Show(); - }); - - _windowButtons.AddButton("Show Json Window", (sender, args) => - { - var window = new JsonWindow - { - MinWidth = defaultWidth, - MinHeight = defaultHeight, - WindowStartupLocation = defaultPosition - }; - window.Show(); - }); - - _windowButtons.AddButton("Show Console Window", (sender, args) => - { - var window = new ConsoleWindow - { - MinWidth = defaultWidth, - MinHeight = defaultHeight, - WindowStartupLocation = defaultPosition - }; - window.Show(); - }); - - _windowButtons.AddButton("Show Custom List Box Window", (sender, args) => - { - var window = new CustomListBoxWindow - { - MinWidth = defaultWidth, - MinHeight = defaultHeight, - WindowStartupLocation = defaultPosition - }; - window.Show(); - }); - - _windowButtons.AddButton("Show Searchable Selection List Window", (sender, args) => - { - var window = new SearchableSelectionListWindow - { - MinWidth = defaultWidth, - MinHeight = defaultHeight, - WindowStartupLocation = defaultPosition - }; - window.Show(); - }); - - _windowButtons.AddButton("Show Key-Value Pair Editor Window", (sender, args) => - { - var window = new KvpEditorWindow - { - MinWidth = defaultWidth, - MinHeight = defaultHeight, - WindowStartupLocation = defaultPosition - }; - window.Show(); - }); - - _windowButtons.AddButton("Show Dropdown Window", (sender, args) => - { - var window = new DropdownWindow - { - MinWidth = defaultWidth, - MinHeight = defaultHeight, - WindowStartupLocation = defaultPosition - }; - window.Show(); - }); - - _windowButtons.AddButton("Show Paged Tab Control Window", (sender, args) => - { - var window = new PagedTabControlWindow - { - MinWidth = defaultWidth, - MinHeight = defaultHeight, - WindowStartupLocation = defaultPosition - }; - window.Show(); - }); - - _windowButtons.AddButton("Show Big Text Input Window", (sender, args) => - { - var window = new BigTextInputWindow - { - MinWidth = defaultWidth, - MinHeight = defaultHeight, - WindowStartupLocation = defaultPosition - }; - window.Show(); - }); - - _windowButtons.AddButton("Show Text Box With Line Numbers Window", (sender, args) => - { - var window = new TextBoxWithLineNumbersWindow - { - MinWidth = defaultWidth, - MinHeight = defaultHeight, - WindowStartupLocation = defaultPosition - }; - window.Show(); - }); - - _windowButtons.AddButton("Show Text Label Experimentation Window", (sender, args) => - { - var window = new TextLabelExperimentationWindow - { - MinWidth = defaultWidth, - MinHeight = defaultHeight, - WindowStartupLocation = defaultPosition - }; - window.Show(); - }); - - _windowButtons.AddButton("Show Text Completion Window", (sender, args) => - { - var window = new TextCompletionWindow - { - MinWidth = defaultWidth, - MinHeight = defaultHeight, - WindowStartupLocation = defaultPosition - }; - window.Show(); - }); - - _windowButtons.AddButton("Show Code Window", (sender, args) => - { - var window = new CodeWindow - { - MinWidth = defaultWidth, - MinHeight = defaultHeight, - WindowStartupLocation = defaultPosition - }; - window.Show(); - }); - - _windowButtons.AddButton("Show Xml Window", (sender, args) => + WindowStartupLocation = _defaultPosition; + + foreach (var (title, createWindow) in _windows) { - var window = new XmlWindow + _windowButtons.AddButton(title, (_, _) => { - MinWidth = defaultWidth, - MinHeight = defaultHeight, - WindowStartupLocation = defaultPosition - }; - window.Show(); - }); + var window = createWindow(); + window.MinWidth = DefaultWidth; + window.MinHeight = DefaultHeight; + window.WindowStartupLocation = _defaultPosition; + window.Show(); + }); + } } -} \ No newline at end of file +} diff --git a/Frank.Wpf.Tests.App/Windows/CSharpScriptingWindow.cs b/Frank.Wpf.Tests.App/Windows/CSharpScriptingWindow.cs new file mode 100644 index 0000000..44a257b --- /dev/null +++ b/Frank.Wpf.Tests.App/Windows/CSharpScriptingWindow.cs @@ -0,0 +1,12 @@ +using System.Windows; +using Frank.Wpf.Controls.RoslynScript; + +namespace Frank.Wpf.Tests.App.Windows; + +public class CSharpScriptingWindow : Window +{ + public CSharpScriptingWindow() + { + Content = new CSharpScriptControl(); + } +} \ No newline at end of file diff --git a/Frank.Wpf.Tests/CsharpTreeViewFactoryTests.cs b/Frank.Wpf.Tests/CsharpTreeViewFactoryTests.cs new file mode 100644 index 0000000..9c2c7b3 --- /dev/null +++ b/Frank.Wpf.Tests/CsharpTreeViewFactoryTests.cs @@ -0,0 +1,69 @@ +using System.Text; +using System.Windows.Markup; +using System.Xml; +using System.Xml.Linq; +using Frank.Wpf.Controls.CSharpRenderer; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Xunit.Abstractions; + +namespace Frank.Wpf.Tests; + +public class CsharpTreeViewFactoryTests(ITestOutputHelper outputHelper) +{ + [WpfFact] + public void Test1() + { + var factory = new CsharpSyntaxTreeViewFactory(); + + var syntaxTree = GetSyntaxTree(); + + var treeView = factory.Create(syntaxTree); + var resultXml = XamlWriter.Save(treeView); + var result = PrettyPrint(resultXml); + + outputHelper.WriteLine(result); + } + + private SyntaxTree GetSyntaxTree() + { + var code = """ + using System; + using System.Collections.Generic; + + namespace HelloWorld + { + public class Program + { + public static void Main() + { + Console.WriteLine(""Hello, World!""); + } + } + } + """; + + return CSharpSyntaxTree.ParseText(code); + } + + private static string PrettyPrint(string xml) + { + var stringBuilder = new StringBuilder(); + + var element = XElement.Parse(xml); + + var settings = new XmlWriterSettings + { + OmitXmlDeclaration = true, + Indent = true, + NewLineOnAttributes = true + }; + + using (var xmlWriter = XmlWriter.Create(stringBuilder, settings)) + { + element.Save(xmlWriter); + } + + return stringBuilder.ToString(); + } +} \ No newline at end of file diff --git a/Frank.Wpf.Tests/Frank.Wpf.Tests.csproj b/Frank.Wpf.Tests/Frank.Wpf.Tests.csproj index e882f69..8b7cc65 100644 --- a/Frank.Wpf.Tests/Frank.Wpf.Tests.csproj +++ b/Frank.Wpf.Tests/Frank.Wpf.Tests.csproj @@ -25,6 +25,7 @@ + diff --git a/Frank.Wpf.sln b/Frank.Wpf.sln index 6ddf273..08eeb7d 100644 --- a/Frank.Wpf.sln +++ b/Frank.Wpf.sln @@ -60,6 +60,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frank.Wpf.Controls.Completi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frank.Wpf.Controls.XmlRenderer", "Frank.Wpf.Controls.XmlRenderer\Frank.Wpf.Controls.XmlRenderer.csproj", "{BB9161CA-F8DE-49E9-B6E1-92AA52962906}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frank.Wpf.Controls.CSharpRenderer", "Frank.Wpf.Controls.CSharpRenderer\Frank.Wpf.Controls.CSharpRenderer.csproj", "{B4865447-A82D-4973-92E7-998A95472BC1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -161,5 +163,9 @@ Global {BB9161CA-F8DE-49E9-B6E1-92AA52962906}.Debug|Any CPU.Build.0 = Debug|Any CPU {BB9161CA-F8DE-49E9-B6E1-92AA52962906}.Release|Any CPU.ActiveCfg = Release|Any CPU {BB9161CA-F8DE-49E9-B6E1-92AA52962906}.Release|Any CPU.Build.0 = Release|Any CPU + {B4865447-A82D-4973-92E7-998A95472BC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B4865447-A82D-4973-92E7-998A95472BC1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B4865447-A82D-4973-92E7-998A95472BC1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B4865447-A82D-4973-92E7-998A95472BC1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal