-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
1 parent
de8d381
commit e0688eb
Showing
15 changed files
with
514 additions
and
243 deletions.
There are no files selected for viewing
97 changes: 97 additions & 0 deletions
97
Frank.Wpf.Controls.CSharpRenderer/CsharpSyntaxTreeViewFactory.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<NamespaceDeclarationSyntax>().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<TypeDeclarationSyntax>() | ||
.Where(t => !t.Ancestors().OfType<NamespaceDeclarationSyntax>().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<TypeDeclarationSyntax>(); | ||
|
||
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<MethodDeclarationSyntax>(); | ||
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<PropertyDeclarationSyntax>(); | ||
foreach (var propertyNode in propertyNodes) | ||
{ | ||
var propertyItem = new TreeViewItem | ||
{ | ||
Header = propertyNode.Identifier.Text | ||
}; | ||
|
||
typeItem.Items.Add(propertyItem); | ||
} | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
Frank.Wpf.Controls.CSharpRenderer/Frank.Wpf.Controls.CSharpRenderer.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Frank.Wpf.Core\Frank.Wpf.Core.csproj" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
175 changes: 109 additions & 66 deletions
175
Frank.Wpf.Controls.RoslynScript/CSharpScriptControl.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<string>(_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<string>(_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; | ||
} | ||
} | ||
} | ||
} |
4 changes: 2 additions & 2 deletions
4
Frank.Wpf.Controls.RoslynScript/Frank.Wpf.Controls.RoslynScript.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,14 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<IsPackable>false</IsPackable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.10.0" /> | ||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.11.0" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Frank.Wpf.Controls.SimpleInputs\Frank.Wpf.Controls.SimpleInputs.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
namespace Frank.Wpf.Controls.RoslynScript; | ||
|
||
public class ScriptGlobals | ||
{ | ||
private readonly Dictionary<string, object> _globals; | ||
|
||
public ScriptGlobals(Dictionary<string, object> globals) | ||
{ | ||
_globals = globals; | ||
} | ||
|
||
public object? this[string name] => _globals.ContainsKey(name) ? _globals[name] : null; | ||
} |
Oops, something went wrong.