diff --git a/Cauldron.Core/RoslynHost.cs b/Cauldron.Core/RoslynHost.cs index c840291..4233ab0 100644 --- a/Cauldron.Core/RoslynHost.cs +++ b/Cauldron.Core/RoslynHost.cs @@ -1,4 +1,6 @@ -using Microsoft.CodeAnalysis.CSharp.Scripting; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; namespace Cauldron.Core; @@ -13,8 +15,7 @@ public class RoslynHost /// Values that will be made available to the script /// public static async Task RunScript(string code, string[] imports, - RoslynHostGlobals globals, - CancellationToken cancellationToken = default) + RoslynHostGlobals globals, CancellationToken cancellationToken = default) { ScriptOptions options = ScriptOptions.Default .AddImports(imports); @@ -22,6 +23,16 @@ public class RoslynHost await CSharpScript.RunAsync(code, options, globals, cancellationToken: cancellationToken); } + + public static ImmutableArray BuildScript(string code, string[] imports, + RoslynHostGlobals globals) + { + ScriptOptions options = ScriptOptions.Default + .AddImports(imports); + + Script script = CSharpScript.Create(code, options, globals.GetType()); + return script.GetCompilation().GetDiagnostics(); + } } public class RoslynHostGlobals diff --git a/Cauldron.Macos/Main.storyboard b/Cauldron.Macos/Main.storyboard index 99a03a1..ff62573 100644 --- a/Cauldron.Macos/Main.storyboard +++ b/Cauldron.Macos/Main.storyboard @@ -413,7 +413,7 @@ - + @@ -421,11 +421,11 @@ - + - + @@ -463,7 +463,7 @@ - + diff --git a/Cauldron.Macos/MainWindow.cs b/Cauldron.Macos/MainWindow.cs index 23a2332..eadbecd 100644 --- a/Cauldron.Macos/MainWindow.cs +++ b/Cauldron.Macos/MainWindow.cs @@ -1,8 +1,13 @@ using System; +using System.Collections.Immutable; +using System.Linq; using System.Threading; +using System.Timers; using AppKit; using Cauldron.Macos.SourceWriter; using Cauldron.Macos.SourceWriter.LanguageFormats; +using Foundation; +using Microsoft.CodeAnalysis; namespace Cauldron.Macos; @@ -61,6 +66,7 @@ public partial class MainWindow : NSWindowController this.RunScriptToolbarButton.Activated += RunScript; SourceTextView scriptTextBox = this.ScriptEditorTextBox; + scriptTextBox.OnFinishedTyping += this.BuildScript; scriptTextBox.Font = NSFont.MonospacedSystemFont(new nfloat(14), NSFontWeight.Regular); scriptTextBox.AutomaticQuoteSubstitutionEnabled = false; scriptTextBox.AutomaticDashSubstitutionEnabled = false; @@ -74,6 +80,11 @@ public partial class MainWindow : NSWindowController scriptTextBox.Formatter.Reformat(); } + public void BuildScript(object sender, ElapsedEventArgs args) + { + ScriptRunner.BuildScript(this); + } + public void RunScript(object sender, EventArgs e) { ScriptRunner.RunScript(this); @@ -94,6 +105,46 @@ public partial class MainWindow : NSWindowController this.CreateNewTab(); } + public void UpdateScriptDiagnostics(ImmutableArray diagnostics) + { + Console.WriteLine(diagnostics.Select(d => $"{d.Severity}: {d.GetMessage()}").Join("\n")); + + foreach (Diagnostic diagnostic in diagnostics) + { + int start = diagnostic.Location.SourceSpan.Start; + int end = diagnostic.Location.SourceSpan.End; + + if (start == end && end < this.ScriptText.Length) + end += 1; + else if (start == end) + start -= 1; + + NSRange range = new(start,end); + + this.ScriptEditorTextBox.LayoutManager + .AddTemporaryAttribute(NSStringAttributeKey.UnderlineStyle, + new NSNumber((int)(NSUnderlineStyle.Thick | NSUnderlineStyle.PatternDot)), + range); + this.ScriptEditorTextBox.LayoutManager + .AddTemporaryAttribute(NSStringAttributeKey.ToolTip, + new NSString($"{diagnostic.Id} {diagnostic.GetMessage()}"), + range); + + if (diagnostic.Severity == DiagnosticSeverity.Error) + this.ScriptEditorTextBox.LayoutManager + .AddTemporaryAttribute(NSStringAttributeKey.UnderlineColor, NSColor.SystemRed, + range); + else if (diagnostic.Severity == DiagnosticSeverity.Warning) + this.ScriptEditorTextBox.LayoutManager + .AddTemporaryAttribute(NSStringAttributeKey.UnderlineColor, NSColor.SystemGreen, + range); + else if (diagnostic.Severity == DiagnosticSeverity.Info) + this.ScriptEditorTextBox.LayoutManager + .AddTemporaryAttribute(NSStringAttributeKey.UnderlineColor, NSColor.SystemBlue, + range); + } + } + public void SetScriptRunState(bool scriptIsRunning) { if (scriptIsRunning) diff --git a/Cauldron.Macos/ScriptRunner.cs b/Cauldron.Macos/ScriptRunner.cs index 8808108..64ef4fc 100644 --- a/Cauldron.Macos/ScriptRunner.cs +++ b/Cauldron.Macos/ScriptRunner.cs @@ -7,40 +7,39 @@ using Cauldron.Core; using System.Reflection; using System.Linq; using System.ComponentModel.DataAnnotations; -using System.Collections; using System.IO; namespace Cauldron.Macos; public static class ScriptRunner { + public static void BuildScript(MainWindow window) + { + string script = ""; + window.BeginInvokeOnMainThread(() => script = window.ScriptText); + + TagBuilder body = new("body"); + + RoslynHostGlobals globals = new(null); + window.BeginInvokeOnMainThread(() => globals = new RoslynHostGlobals(CreateCauldronWriter(window, body))); + + Task task = Task + .Run(() => RoslynHost.BuildScript(script, Array.Empty(), globals)) + .ContinueWith(t => window.BeginInvokeOnMainThread( + () => window.UpdateScriptDiagnostics(t.Result))); + } + public static void RunScript(MainWindow window) { window.SetScriptRunState(true); TagBuilder body = new("body"); - - window.ScriptOutputWebView.SetOutputPanelContent(body); + string script = window.ScriptText; + window.ScriptCancellationTokenSource = new CancellationTokenSource(); TaskScheduler uiThread = TaskScheduler.FromCurrentSynchronizationContext(); - CauldronWriter writer = new(obj => - { - window.BeginInvokeOnMainThread(() => - { - TagBuilder outputSection = new("section"); - outputSection.AddCssClass("output-section"); - outputSection.InnerHtml.AppendHtml(GenerateValueOutput(obj)); - body.InnerHtml.AppendHtml(outputSection); - - window.ScriptOutputWebView.SetOutputPanelContent(body); - }); - - return Task.CompletedTask; - }); - - window.ScriptCancellationTokenSource = new CancellationTokenSource(); - - string script = window.ScriptText; + // Clear the output for the new run + window.ScriptOutputWebView.SetOutputPanelContent(body); Task task = Task .Run(async () => @@ -48,7 +47,7 @@ public static class ScriptRunner try { await RoslynHost.RunScript(script, Array.Empty(), - new RoslynHostGlobals(writer), + new RoslynHostGlobals(CreateCauldronWriter(window, body)), window.ScriptCancellationTokenSource.Token); } catch (Exception ex) @@ -66,6 +65,24 @@ public static class ScriptRunner .ContinueWith((t) => window.SetScriptRunState(false), uiThread); } + private static CauldronWriter CreateCauldronWriter(MainWindow window, TagBuilder body) + { + return new CauldronWriter (obj => + { + window.BeginInvokeOnMainThread(() => + { + TagBuilder outputSection = new("section"); + outputSection.AddCssClass("output-section"); + outputSection.InnerHtml.AppendHtml(GenerateValueOutput(obj)); + body.InnerHtml.AppendHtml(outputSection); + + window.ScriptOutputWebView.SetOutputPanelContent(body); + }); + + return Task.CompletedTask; + }); + } + private static TagBuilder GenerateValueOutput(object value) { if (value is null) diff --git a/Cauldron.Macos/SourceWriter/SourceTextView.cs b/Cauldron.Macos/SourceWriter/SourceTextView.cs index b38a995..76e9263 100644 --- a/Cauldron.Macos/SourceWriter/SourceTextView.cs +++ b/Cauldron.Macos/SourceWriter/SourceTextView.cs @@ -1,4 +1,5 @@ using System; +using System.Timers; using AppKit; using CoreGraphics; using Foundation; @@ -435,6 +436,17 @@ public class SourceTextView : NSTextView #region Override Methods + private Timer InputTimoutTimer { get; set; } + /// + /// The amount of time with no user input after which will be run + /// + public TimeSpan InputTimeoutInterval { get; set; } = new TimeSpan(0, 0, 1); + /// + /// An event triggered when the user has stopped typing for a period of time defined by + /// + /// + public event ElapsedEventHandler OnFinishedTyping; + /// /// Look for special keys being pressed and does specific processing based on the key. /// @@ -570,7 +582,12 @@ public class SourceTextView : NSTextView this.Formatter.Reformat(); - //Console.WriteLine ("Key: {0}", (int)theEvent.Characters[0]); + this.InputTimoutTimer?.Stop(); + this.InputTimoutTimer?.Close(); + this.InputTimoutTimer = new Timer(this.InputTimeoutInterval); + this.InputTimoutTimer.AutoReset = false; + this.InputTimoutTimer.Elapsed += this.OnFinishedTyping; + this.InputTimoutTimer.Start(); } ///