Editor/ErrorList.cs
using System.Collections.Generic;
using System.Linq;
using Editor;
using Microsoft.CodeAnalysis;
using Sandbox;
using Application = Editor.Application;
[Dock( "Editor", "Error List", "report" )]
public class ErrorList : Widget
{
// Static because the widget can be deleted on hide/show & hotload
public static readonly List<Diagnostic> Diagnostics = new();
public ErrorListView ErrorListView;
public Button ErrorsButton;
public Button InfoButton;
private bool ShowErrors = true;
private bool ShowInfo = true;
private bool ShowWarnings = true;
public Button WarningsButton;
public ErrorList( Widget parent ) : base( parent )
{
Name = "ErrorList";
Layout = Layout.Column();
var layout = Layout.Add( Layout.Row() );
layout.Spacing = 8;
layout.Margin = 5;
ErrorsButton = new Button( "0 Errors", "error", this )
{
Clicked = () =>
{
ShowErrors = !ShowErrors;
UpdateErrors();
ErrorsButton.Update();
},
OnPaintOverride = () => PaintShittyButton( ErrorsButton, "error", ErrorColor, ShowErrors ),
StatusTip = "Toggle display of errors"
};
WarningsButton = new Button( "0 Warnings", "warning", this )
{
Clicked = () =>
{
ShowWarnings = !ShowWarnings;
UpdateErrors();
WarningsButton.Update();
},
OnPaintOverride = () => PaintShittyButton( WarningsButton, "warning", WarningColor, ShowWarnings ),
StatusTip = "Toggle display of warnings"
};
InfoButton = new Button( "0 Messages", "info", this )
{
Clicked = () =>
{
ShowInfo = !ShowInfo;
UpdateErrors();
InfoButton.Update();
},
OnPaintOverride = () => PaintShittyButton( InfoButton, "info", InfoColor, ShowInfo ),
StatusTip = "Toggle display of information"
};
layout.Add( ErrorsButton );
layout.Add( WarningsButton );
layout.Add( InfoButton );
layout.AddStretchCell();
var clearButton = new Button( "", "delete", this )
{
Tint = Color.Gray,
Clicked = () =>
{
Diagnostics.Clear();
UpdateErrors();
},
StatusTip = "Clear error list"
};
clearButton.SetProperty( "cssClass", "clear" );
layout.Add( clearButton );
ErrorListView = new ErrorListView( this );
Layout.Add( ErrorListView, 1 );
UpdateErrors();
}
internal static Color WarningColor => Theme.Yellow;
internal static Color ErrorColor => Theme.Red;
internal static Color InfoColor => Theme.Blue;
[Event( "compile.complete" )]
public static void CaptureDiagnostics( CompileGroup compileGroup )
{
Diagnostics.Clear();
Diagnostics.AddRange( compileGroup.Compilers.Where( x => x.Diagnostics != null )
.SelectMany( x => x.Diagnostics ) );
// Grab a total count of all errors, update status bar and pop up errors list if they have some
var errors = Diagnostics.Where( a => a.Severity == DiagnosticSeverity.Error ).ToArray();
if ( errors.Any() )
{
EditorWindow?.StatusBar.ShowMessage( $"Build failed - you have {errors.Count()} errors", 10 );
// Pop-up the error list if we have any errors
// EditorWindow.ErrorListDock?.Show(); // Opens it if it's not already open
// EditorWindow.ErrorListDock?.Raise(); // Switches any tab to it
}
}
[Event( "compile.complete", Priority = 10 )]
public void OnCompileComplete( CompileGroup _ )
{
// CaptureDiagnostics fills in the static Diagnostics list
// which is the diagostics from the most recent compile.
// which is all we care about, really
UpdateErrors();
}
public void UpdateErrors()
{
// Convert Diagnostics to ProjectDiagnostic objects
var q = Diagnostics
.AsEnumerable()
.Where( x => x.Severity != DiagnosticSeverity.Hidden )
.Select( x => new ProjectDiagnostic( x ) ); // Map to ProjectDiagnostic
WarningsButton.Text = $"{q.Count( x => x.OriginalDiagnostic.Severity == DiagnosticSeverity.Warning )} Warnings";
ErrorsButton.Text = $"{q.Count( x => x.OriginalDiagnostic.Severity == DiagnosticSeverity.Error )} Errors";
InfoButton.Text = $"{q.Count( x => x.OriginalDiagnostic.Severity == DiagnosticSeverity.Info )} Messages";
if ( !ShowErrors )
{
q = q.Where( x => x.OriginalDiagnostic.Severity != DiagnosticSeverity.Error );
}
if ( !ShowWarnings )
{
q = q.Where( x => x.OriginalDiagnostic.Severity != DiagnosticSeverity.Warning );
}
if ( !ShowInfo )
{
q = q.Where( x => x.OriginalDiagnostic.Severity != DiagnosticSeverity.Info );
}
q = q.OrderByDescending( x => x.OriginalDiagnostic.Severity == DiagnosticSeverity.Error );
ErrorListView.SetItems( q );
}
private bool PaintShittyButton( Button btn, string icon, Color color, bool active )
{
var rect = btn.LocalRect;
Paint.SetBrush( Theme.Primary.WithAlpha( Paint.HasMouseOver ? 0.2f : 0.1f ) );
Paint.ClearPen();
if ( active )
{
Paint.SetPen( Theme.Primary.WithAlpha( 0.4f ), 2.0f );
Paint.DrawRect( rect, 2 );
}
rect = rect.Shrink( 3, 1 );
Paint.Antialiasing = true;
Paint.SetPen( color.WithAlpha( Paint.HasMouseOver ? 1 : 0.7f ), 3.0f );
Paint.ClearBrush();
// Severity Icon
var iconRect = rect;
iconRect.Left += 0;
iconRect.Width = 16;
Paint.DrawIcon( iconRect, icon, 16 );
rect.Left = iconRect.Right + 2;
Paint.SetDefaultFont();
Paint.SetPen( Theme.Text.WithAlpha( active ? 1 : 0.4f ), 3.0f );
Paint.DrawText( rect, btn.Text );
return true;
}
}
public class ErrorListView : ListView
{
public ErrorListView( Widget parent ) : base( parent )
{
Name = "Output";
ItemActivated = a =>
{
if ( a is ProjectDiagnostic diagnostic )
{
CodeEditor.OpenFile( diagnostic.FilePath, diagnostic.LineNumber, diagnostic.CharNumber );
}
};
ItemContextMenu = OpenItemContextMenu;
ItemSize = new Vector2( 0, 48 );
ItemSpacing = 0;
Margin = 0;
}
private void OpenItemContextMenu( object item )
{
if ( item is not ProjectDiagnostic diagnostic )
{
return;
}
var m = new Menu();
m.AddOption( "Open in Code Editor", "file_open",
() => CodeEditor.OpenFile( diagnostic.FilePath, diagnostic.LineNumber, diagnostic.CharNumber ) );
m.AddOption( "Copy Error", "content_copy", () => EditorUtility.Clipboard.Copy( diagnostic.Message ) );
m.OpenAt( Application.CursorPosition );
}
protected override void PaintItem( VirtualWidget item )
{
if ( item.Object is not ProjectDiagnostic diagnostic )
{
return;
}
var color = diagnostic.OriginalDiagnostic.Severity switch
{
DiagnosticSeverity.Error => ErrorList.ErrorColor,
DiagnosticSeverity.Warning => ErrorList.WarningColor,
_ => ErrorList.InfoColor
};
var icon = diagnostic.OriginalDiagnostic.Severity switch
{
DiagnosticSeverity.Error => "error",
DiagnosticSeverity.Warning => "warning",
_ => "info"
};
Paint.SetBrush( color.WithAlpha( Paint.HasMouseOver ? 0.3f : 0.2f ).Darken( 0.4f ) );
Paint.ClearPen();
Paint.DrawRect( item.Rect.Shrink( 0, 1 ) );
Paint.Antialiasing = true;
Paint.SetPen( color.WithAlpha( Paint.HasMouseOver ? 1 : 0.7f ), 3.0f );
Paint.ClearBrush();
// Severity Icon
var iconRect = item.Rect.Shrink( 12, 0 );
iconRect.Width = 24;
Paint.DrawIcon( iconRect, icon, 24 );
var rect = item.Rect.Shrink( 48, 8, 0, 8 );
Paint.SetPen( Theme.Text.WithAlpha( Paint.HasMouseOver ? 1 : 0.8f ), 3.0f );
Paint.DrawText( rect, diagnostic.Message, TextFlag.LeftTop | TextFlag.SingleLine );
Paint.SetPen( Theme.Text.WithAlpha( Paint.HasMouseOver ? 0.5f : 0.4f ), 3.0f );
Paint.DrawText( rect,
$"{Project.Current.Config.Title} - {diagnostic.FilePath}({diagnostic.LineNumber},{diagnostic.CharNumber})",
TextFlag.LeftBottom | TextFlag.SingleLine );
}
}