diff --git a/Cauldron.Macos/Cauldron.Macos.csproj b/Cauldron.Macos/Cauldron.Macos.csproj
index b6448b5..deae7bc 100644
--- a/Cauldron.Macos/Cauldron.Macos.csproj
+++ b/Cauldron.Macos/Cauldron.Macos.csproj
@@ -44,6 +44,9 @@
AppDelegate.cs
+
+ DiagnosticsPopoverController.cs
+
@@ -56,10 +59,12 @@
+
+
diff --git a/Cauldron.Macos/DiagnosticsPopoverController.cs b/Cauldron.Macos/DiagnosticsPopoverController.cs
new file mode 100644
index 0000000..3dadfab
--- /dev/null
+++ b/Cauldron.Macos/DiagnosticsPopoverController.cs
@@ -0,0 +1,69 @@
+using AppKit;
+using Microsoft.CodeAnalysis;
+using System.Collections.Immutable;
+using System.Linq;
+using Cauldron.Macos.SourceList;
+using Foundation;
+
+namespace Cauldron.Macos
+{
+ public partial class DiagnosticsPopoverController : NSViewController
+ {
+ public DiagnosticSeverity Severity { get; set; }
+
+ public DiagnosticsPopoverController (ObjCRuntime.NativeHandle handle) : base (handle) { }
+
+ public override void ViewWillAppear()
+ {
+ base.ViewWillAppear();
+
+ // Build the list of diagnostics info
+
+ MainWindow window = NSApplication.SharedApplication.KeyWindow.WindowController
+ as MainWindow;
+
+ ImmutableArray diagnostics = window.Diagnostics;
+
+ this.DiagnosticsOutlineView.Initialize();
+
+ SourceListItem errors = new("Errors")
+ {
+ IsHeader = true
+ };
+ SourceListItem warnings = new("Warnings")
+ {
+ IsHeader = true
+ };
+ SourceListItem infos = new("Information")
+ {
+ IsHeader = true
+ };
+
+ foreach (var diagnostic in diagnostics)
+ {
+ SourceListItem item = new($"{diagnostic.Id} {diagnostic.GetMessage()}\n{diagnostic.Location}", "",
+ () =>
+ {
+ window.ScriptEditorTextBox.SetSelectedRange(
+ new NSRange(diagnostic.Location.SourceSpan.Start, diagnostic.Location.SourceSpan.End));
+ this.DismissController(this);
+ });
+
+ if (diagnostic.Severity == DiagnosticSeverity.Error)
+ errors.AddItem(item);
+ else if (diagnostic.Severity == DiagnosticSeverity.Warning)
+ warnings.AddItem(item);
+ else if (diagnostic.Severity == DiagnosticSeverity.Info)
+ infos.AddItem(item);
+ }
+
+ this.DiagnosticsOutlineView.AddItem(errors);
+ this.DiagnosticsOutlineView.AddItem(warnings);
+ this.DiagnosticsOutlineView.AddItem(infos);
+
+ this.DiagnosticsOutlineView.ReloadData();
+ this.DiagnosticsOutlineView.ExpandItem(null, true);
+ this.DiagnosticsOutlineView.UsesAutomaticRowHeights = true;
+ }
+ }
+}
diff --git a/Cauldron.Macos/DiagnosticsPopoverController.designer.cs b/Cauldron.Macos/DiagnosticsPopoverController.designer.cs
new file mode 100644
index 0000000..f2cb638
--- /dev/null
+++ b/Cauldron.Macos/DiagnosticsPopoverController.designer.cs
@@ -0,0 +1,26 @@
+// WARNING
+//
+// This file has been generated automatically by Visual Studio to store outlets and
+// actions made in the UI designer. If it is removed, they will be lost.
+// Manual changes to this file may not be handled correctly.
+//
+using Foundation;
+using System.CodeDom.Compiler;
+
+namespace Cauldron.Macos
+{
+ [Register ("DiagnosticsPopoverController")]
+ partial class DiagnosticsPopoverController
+ {
+ [Outlet]
+ Cauldron.Macos.SourceList.SourceListView DiagnosticsOutlineView { get; set; }
+
+ void ReleaseDesignerOutlets ()
+ {
+ if (DiagnosticsOutlineView != null) {
+ DiagnosticsOutlineView.Dispose ();
+ DiagnosticsOutlineView = null;
+ }
+ }
+ }
+}
diff --git a/Cauldron.Macos/Main.storyboard b/Cauldron.Macos/Main.storyboard
index d998ed9..0634e53 100644
--- a/Cauldron.Macos/Main.storyboard
+++ b/Cauldron.Macos/Main.storyboard
@@ -411,6 +411,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -435,25 +533,30 @@
-
+
-
+
+
+
-
+
-
+
-
+
+
+
+
@@ -478,7 +581,7 @@
-
+
@@ -531,18 +634,18 @@
-
+
-
+
-
+
-
+
@@ -585,7 +688,7 @@
-
+
@@ -599,7 +702,7 @@
-
+
@@ -638,6 +741,7 @@
+
diff --git a/Cauldron.Macos/MainWindow.cs b/Cauldron.Macos/MainWindow.cs
index eb02b26..b5ec0d0 100644
--- a/Cauldron.Macos/MainWindow.cs
+++ b/Cauldron.Macos/MainWindow.cs
@@ -55,6 +55,7 @@ public partial class MainWindow : NSWindowController
#region Shared properties
public CancellationTokenSource ScriptCancellationTokenSource { get; set; }
+ public ImmutableArray Diagnostics { get; set; } = ImmutableArray.Empty;
#endregion
@@ -92,6 +93,24 @@ public partial class MainWindow : NSWindowController
this.SetDocumentEdited(this.ScriptDocument.IsDocumentEdited);
}
+ public override void PrepareForSegue(NSStoryboardSegue segue, NSObject sender)
+ {
+ base.PrepareForSegue(segue, sender);
+
+ if (sender is NSSegmentedControl segmentedControl
+ && segmentedControl.Identifier == "DiagnosticsButtons"
+ && segue.DestinationController is DiagnosticsPopoverController diagPopover)
+ {
+ diagPopover.Severity = segmentedControl.SelectedSegment switch
+ {
+ 0 => DiagnosticSeverity.Info,
+ 1 => DiagnosticSeverity.Warning,
+ 2 => DiagnosticSeverity.Error,
+ _ => DiagnosticSeverity.Info
+ };
+ }
+ }
+
public void UpdateDocument(object sender, EventArgs args)
{
this.ScriptDocument.ScriptText = new NSString(this.ScriptText);
@@ -113,13 +132,10 @@ public partial class MainWindow : NSWindowController
this.ScriptCancellationTokenSource?.Cancel();
}
- partial void NewTabMenuItemClicked(AppKit.NSMenuItem sender)
- {
- this.CreateNewTab();
- }
-
public void UpdateScriptDiagnostics(ImmutableArray diagnostics)
{
+ this.Diagnostics = diagnostics;
+
ImmutableList infoDiagnostics = diagnostics
.Where(d => d.Severity == DiagnosticSeverity.Info)
.ToImmutableList();
@@ -131,9 +147,15 @@ public partial class MainWindow : NSWindowController
.ToImmutableList();
this.DiagnosticsToolbarGroup.SetLabel(infoDiagnostics.Count.ToString(), 0);
- this.DiagnosticsToolbarGroup.SetLabel(warningDiagnostics.Count.ToString(), 1);
- this.DiagnosticsToolbarGroup.SetLabel(errorDiagnostics.Count.ToString(), 2);
+ this.DiagnosticsToolbarGroup.SetEnabled(infoDiagnostics.Count != 0, 0);
+ this.DiagnosticsToolbarGroup.SetLabel(warningDiagnostics.Count.ToString(), 1);
+ this.DiagnosticsToolbarGroup.SetEnabled(warningDiagnostics.Count != 0, 1);
+
+ this.DiagnosticsToolbarGroup.SetLabel(errorDiagnostics.Count.ToString(), 2);
+ this.DiagnosticsToolbarGroup.SetEnabled(errorDiagnostics.Count != 0, 2);
+
+ // Mark text in the
foreach (Diagnostic diagnostic in diagnostics)
{
int start = diagnostic.Location.SourceSpan.Start;
@@ -161,7 +183,7 @@ public partial class MainWindow : NSWindowController
range);
else if (diagnostic.Severity == DiagnosticSeverity.Warning)
this.ScriptEditorTextBox.LayoutManager
- .AddTemporaryAttribute(NSStringAttributeKey.UnderlineColor, NSColor.SystemGreen,
+ .AddTemporaryAttribute(NSStringAttributeKey.UnderlineColor, NSColor.SystemYellow,
range);
else if (diagnostic.Severity == DiagnosticSeverity.Info)
this.ScriptEditorTextBox.LayoutManager
@@ -181,12 +203,4 @@ public partial class MainWindow : NSWindowController
this.RunScriptToolbarButton.Enabled = true;
}
}
-
- public void CreateNewTab()
- {
- MainWindow newWindow = this.Storyboard.InstantiateInitialController()
- as MainWindow;
- this.Window.AddTabbedWindow(newWindow.Window, NSWindowOrderingMode.Above);
- this.Window.SelectNextTab(this);
- }
}
diff --git a/Cauldron.Macos/MainWindow.designer.cs b/Cauldron.Macos/MainWindow.designer.cs
index f24b85d..a0090fc 100644
--- a/Cauldron.Macos/MainWindow.designer.cs
+++ b/Cauldron.Macos/MainWindow.designer.cs
@@ -20,27 +20,18 @@ namespace Cauldron.Macos
[Action ("BtnRunScriptClicked:")]
partial void BtnRunScriptClicked (AppKit.NSToolbarItem sender);
-
- [Action ("NewTabClicked:")]
- partial void NewTabClicked (AppKit.NSToolbarItem sender);
-
- [Action ("NewTabMenuItemClicked:")]
- partial void NewTabMenuItemClicked (AppKit.NSMenuItem sender);
-
- [Action ("NewTabMenuItemClicked2:")]
- partial void NewTabMenuItemClicked2 (AppKit.NSMenuItem sender);
void ReleaseDesignerOutlets ()
{
- if (RunScriptToolbarButton != null) {
- RunScriptToolbarButton.Dispose ();
- RunScriptToolbarButton = null;
- }
-
if (DiagnosticsToolbarGroup != null) {
DiagnosticsToolbarGroup.Dispose ();
DiagnosticsToolbarGroup = null;
}
+
+ if (RunScriptToolbarButton != null) {
+ RunScriptToolbarButton.Dispose ();
+ RunScriptToolbarButton = null;
+ }
}
}
}
diff --git a/Cauldron.Macos/SourceList/SourceListDataSource.cs b/Cauldron.Macos/SourceList/SourceListDataSource.cs
new file mode 100644
index 0000000..6f827d8
--- /dev/null
+++ b/Cauldron.Macos/SourceList/SourceListDataSource.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using AppKit;
+using Foundation;
+
+namespace Cauldron.Macos.SourceList
+{
+ public class SourceListDataSource : NSOutlineViewDataSource
+ {
+ #region Private Variables
+
+ private SourceListView _controller;
+
+ #endregion
+
+ #region Public Variables
+
+ public List Items = new();
+
+ #endregion
+
+ #region Constructors
+
+ public SourceListDataSource(SourceListView controller)
+ {
+ // Initialize
+ this._controller = controller;
+ }
+
+ #endregion
+
+ #region Override Properties
+
+ public override nint GetChildrenCount(NSOutlineView outlineView, Foundation.NSObject item)
+ {
+ if (item == null)
+ {
+ return Items.Count;
+ }
+ else
+ {
+ return ((SourceListItem)item).Count;
+ }
+ }
+
+ public override bool ItemExpandable(NSOutlineView outlineView, Foundation.NSObject item)
+ {
+ return ((SourceListItem)item).HasChildren;
+ }
+
+ public override NSObject GetChild(NSOutlineView outlineView, nint childIndex, Foundation.NSObject item)
+ {
+ if (item == null)
+ {
+ return Items[(int)childIndex];
+ }
+ else
+ {
+ return ((SourceListItem)item)[(int)childIndex];
+ }
+ }
+
+ public override NSObject GetObjectValue(NSOutlineView outlineView, NSTableColumn tableColumn, NSObject item)
+ {
+ return new NSString(((SourceListItem)item).Title);
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal SourceListItem ItemForRow(int row)
+ {
+ int index = 0;
+
+ // Look at each group
+ foreach (SourceListItem item in Items)
+ {
+ // Is the row inside this group?
+ if (row >= index && row <= (index + item.Count))
+ {
+ return item[row - index - 1];
+ }
+
+ // Move index
+ index += item.Count + 1;
+ }
+
+ // Not found
+ return null;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/Cauldron.Macos/SourceList/SourceListDelegate.cs b/Cauldron.Macos/SourceList/SourceListDelegate.cs
new file mode 100644
index 0000000..1f1d9ab
--- /dev/null
+++ b/Cauldron.Macos/SourceList/SourceListDelegate.cs
@@ -0,0 +1,112 @@
+using AppKit;
+using CoreGraphics;
+using Foundation;
+
+namespace Cauldron.Macos.SourceList
+{
+ public class SourceListDelegate : NSOutlineViewDelegate
+ {
+ #region Private variables
+
+ private SourceListView _controller;
+
+ #endregion
+
+ #region Constructors
+
+ public SourceListDelegate(SourceListView controller)
+ {
+ this._controller = controller;
+ }
+
+ #endregion
+
+ #region Override Methods
+
+ public override bool ShouldEditTableColumn(NSOutlineView outlineView,
+ NSTableColumn tableColumn, NSObject item)
+ {
+ return false;
+ }
+
+ public override NSCell GetCell(NSOutlineView outlineView, NSTableColumn tableColumn,
+ NSObject item)
+ {
+ nint row = outlineView.RowForItem(item);
+ return tableColumn.DataCellForRow(row);
+ }
+
+ public override bool IsGroupItem(NSOutlineView outlineView, NSObject item)
+ {
+ return ((SourceListItem)item).HasChildren;
+ }
+
+ public override NSView GetView(NSOutlineView outlineView, NSTableColumn tableColumn,
+ NSObject item)
+ {
+ NSTableCellView view;
+
+ // Is this a group item?
+ if (((SourceListItem)item).IsHeader)
+ {
+ view = (NSTableCellView)outlineView.MakeView("HeaderCell", this);
+ }
+ else
+ {
+ view = (NSTableCellView)outlineView.MakeView("DataCell", this);
+ view.ImageView.Image = ((SourceListItem)item).Icon;
+ view.TextField.LineBreakMode = NSLineBreakMode.CharWrapping;
+ view.TextField.UsesSingleLineMode = false;
+ view.TextField.MaximumNumberOfLines = 0;
+ }
+
+ view.TextField.StringValue = ((SourceListItem)item).Title;
+ view.TextField.SetBoundsSize(CalculateTextFieldHeight(view));
+
+ return view;
+ }
+
+ public override bool ShouldSelectItem(NSOutlineView outlineView, NSObject item)
+ {
+ return (outlineView.GetParent(item) != null);
+ }
+
+ public override void SelectionDidChange(NSNotification notification)
+ {
+ NSIndexSet selectedIndexes = _controller.SelectedRows;
+
+ // More than one item selected?
+ if (selectedIndexes.Count > 1)
+ {
+ // Not handling this case
+ }
+ else
+ {
+ // Grab the item
+ var item = _controller.Data.ItemForRow((int)selectedIndexes.FirstIndex);
+
+ // Was an item found?
+ if (item != null)
+ {
+ // Fire the clicked event for the item
+ item.RaiseClickedEvent();
+
+ // Inform caller of selection
+ _controller.RaiseItemSelected(item);
+ }
+ }
+ }
+
+ private static CGSize CalculateTextFieldHeight(NSTableCellView cell)
+ {
+ CGRect rect = new(0, 0, cell.TextField.Bounds.Width, double.MaxValue);
+ NSString str = new(cell.TextField.StringValue);
+ CGRect bounds = str.BoundingRectWithSize(rect.Size, 0,
+ new NSDictionary(NSStringAttributeKey.Font, cell.TextField.Font));
+
+ return new CGSize(cell.TextField.Bounds.Width, bounds.Size.Height);
+ }
+
+ #endregion
+ }
+}
diff --git a/Cauldron.Macos/SourceList/SourceListItem.cs b/Cauldron.Macos/SourceList/SourceListItem.cs
new file mode 100644
index 0000000..a74dbf6
--- /dev/null
+++ b/Cauldron.Macos/SourceList/SourceListItem.cs
@@ -0,0 +1,241 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using AppKit;
+using Foundation;
+
+namespace Cauldron.Macos.SourceList
+{
+ public class SourceListItem : NSObject, IEnumerator, IEnumerable
+ {
+ #region Private Properties
+
+ private string _title;
+ private NSImage _icon;
+ private string _tag;
+ private bool _isHeader = false;
+ private List _items = new();
+
+ #endregion
+
+ #region Computed Properties
+
+ public string Title
+ {
+ get { return _title; }
+ set { _title = value; }
+ }
+
+ public NSImage Icon
+ {
+ get { return _icon; }
+ set { _icon = value; }
+ }
+
+ public string Tag
+ {
+ get { return _tag; }
+ set { _tag = value; }
+ }
+
+ public bool IsHeader
+ {
+ get => this._isHeader;
+ set => this._isHeader = value;
+ }
+
+ #endregion
+
+ #region Indexer
+
+ public SourceListItem this[int index]
+ {
+ get
+ {
+ return _items[index];
+ }
+
+ set
+ {
+ _items[index] = value;
+ }
+ }
+
+ public int Count
+ {
+ get { return _items.Count; }
+ }
+
+ public bool HasChildren
+ {
+ get { return (Count > 0); }
+ }
+
+ #endregion
+
+ #region Enumerable Routines
+
+ private int _position = -1;
+
+ public IEnumerator GetEnumerator()
+ {
+ _position = -1;
+ return (IEnumerator)this;
+ }
+
+ public bool MoveNext()
+ {
+ _position++;
+ return (_position < _items.Count);
+ }
+
+ public void Reset()
+ { _position = -1; }
+
+ public object Current
+ {
+ get
+ {
+ try
+ {
+ return _items[_position];
+ }
+
+ catch (IndexOutOfRangeException)
+ {
+ throw new InvalidOperationException();
+ }
+ }
+ }
+
+ #endregion
+
+ #region Constructors
+
+ public SourceListItem() { }
+
+ public SourceListItem(string title)
+ {
+ this._title = title;
+ }
+
+ public SourceListItem(string title, string icon)
+ {
+ this._title = title;
+ this._icon = NSImage.ImageNamed(icon);
+ }
+
+ public SourceListItem(string title, string icon, ClickedDelegate clicked)
+ {
+ this._title = title;
+ this._icon = NSImage.ImageNamed(icon);
+ this.Clicked = clicked;
+ }
+
+ public SourceListItem(string title, NSImage icon)
+ {
+ this._title = title;
+ this._icon = icon;
+ }
+
+ public SourceListItem(string title, NSImage icon, ClickedDelegate clicked)
+ {
+ this._title = title;
+ this._icon = icon;
+ this.Clicked = clicked;
+ }
+
+ public SourceListItem(string title, NSImage icon, string tag)
+ {
+ this._title = title;
+ this._icon = icon;
+ this._tag = tag;
+ }
+
+ public SourceListItem(string title, NSImage icon, string tag, ClickedDelegate clicked)
+ {
+ this._title = title;
+ this._icon = icon;
+ this._tag = tag;
+ this.Clicked = clicked;
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public void AddItem(SourceListItem item)
+ {
+ _items.Add(item);
+ }
+
+ public void AddItem(string title)
+ {
+ _items.Add(new SourceListItem(title));
+ }
+
+ public void AddItem(string title, string icon)
+ {
+ _items.Add(new SourceListItem(title, icon));
+ }
+
+ public void AddItem(string title, string icon, ClickedDelegate clicked)
+ {
+ _items.Add(new SourceListItem(title, icon, clicked));
+ }
+
+ public void AddItem(string title, NSImage icon)
+ {
+ _items.Add(new SourceListItem(title, icon));
+ }
+
+ public void AddItem(string title, NSImage icon, ClickedDelegate clicked)
+ {
+ _items.Add(new SourceListItem(title, icon, clicked));
+ }
+
+ public void AddItem(string title, NSImage icon, string tag)
+ {
+ _items.Add(new SourceListItem(title, icon, tag));
+ }
+
+ public void AddItem(string title, NSImage icon, string tag, ClickedDelegate clicked)
+ {
+ _items.Add(new SourceListItem(title, icon, tag, clicked));
+ }
+
+ public void Insert(int n, SourceListItem item)
+ {
+ _items.Insert(n, item);
+ }
+
+ public void RemoveItem(SourceListItem item)
+ {
+ _items.Remove(item);
+ }
+
+ public void RemoveItem(int n)
+ {
+ _items.RemoveAt(n);
+ }
+
+ public void Clear()
+ {
+ _items.Clear();
+ }
+
+ #endregion
+
+ #region Events
+
+ public delegate void ClickedDelegate();
+ public event ClickedDelegate Clicked;
+
+ internal void RaiseClickedEvent()
+ {
+ this.Clicked?.Invoke();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/Cauldron.Macos/SourceList/SourceListView.cs b/Cauldron.Macos/SourceList/SourceListView.cs
new file mode 100644
index 0000000..b361add
--- /dev/null
+++ b/Cauldron.Macos/SourceList/SourceListView.cs
@@ -0,0 +1,69 @@
+using System;
+using AppKit;
+using Foundation;
+
+namespace Cauldron.Macos.SourceList
+{
+ [Register("SourceListView")]
+ public class SourceListView : NSOutlineView
+ {
+ #region Computed Properties
+
+ public SourceListDataSource Data
+ {
+ get { return (SourceListDataSource)this.DataSource; }
+ }
+
+ #endregion
+
+ #region Constructors
+
+ public SourceListView() { }
+
+ public SourceListView(IntPtr handle) : base(handle) { }
+
+ public SourceListView(NSCoder coder) : base(coder) { }
+
+ public SourceListView(NSObjectFlag t) : base(t) { }
+
+ public SourceListView(ObjCRuntime.NativeHandle handle) : base(handle) { }
+
+ #endregion
+
+ #region Override Methods
+
+ public override void AwakeFromNib()
+ {
+ base.AwakeFromNib();
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public void Initialize()
+ {
+ this.DataSource = new SourceListDataSource(this);
+ this.Delegate = new SourceListDelegate(this);
+ }
+
+ public void AddItem(SourceListItem item)
+ {
+ Data?.Items.Add(item);
+ }
+
+ #endregion
+
+ #region Events
+
+ public delegate void ItemSelectedDelegate(SourceListItem item);
+ public event ItemSelectedDelegate ItemSelected;
+
+ internal void RaiseItemSelected(SourceListItem item)
+ {
+ this.ItemSelected?.Invoke(item);
+ }
+
+ #endregion
+ }
+}