Files
sbox-public/game/addons/tools/Code/Notification/CodeCompileNotice.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

257 lines
5.9 KiB
C#

using Microsoft.CodeAnalysis;
namespace Editor;
internal class CodeCompileNotice : NoticeWidget
{
CompileGroup CompilerGroup;
public CodeCompileNotice()
{
}
public CodeCompileNotice( CompileGroup compiler )
{
Icon = "build_circle";
Position = 10;
CompilerGroup = compiler;
Reset();
}
protected override Vector2 SizeHint()
{
return 1000;
}
/// <summary>
/// Called when it's about to be re-used by a new compiler
/// </summary>
public override void Reset()
{
base.Reset();
IsRunning = true;
Tick();
SetBodyWidget( null );
FixedWidth = 300;
FixedHeight = 76;
Title = $"Compile '{CompilerGroup.Name}'";
BorderColor = Theme.Primary;
isErrored = false;
}
bool isErrored = false;
private readonly List<string> files = [];
public override bool WantsVisible
{
get
{
if ( EditorPreferences.CompileNotifications == EditorPreferences.NotificationLevel.ShowAlways )
return true;
if ( EditorPreferences.CompileNotifications == EditorPreferences.NotificationLevel.ShowOnError )
return isErrored;
return false;
}
}
public override void Tick()
{
if ( !IsRunning )
return;
var buildCount = CompilerGroup.Compilers.Count( x => x.IsBuilding );
var finishCount = CompilerGroup.Compilers.Count( x => !x.IsBuilding );
var warnings = CompilerGroup.Compilers.Where( x => x.Diagnostics != null )
.SelectMany( x => x.Diagnostics )
.Where( x => x.Severity >= DiagnosticSeverity.Warning )
.Count();
IsRunning = CompilerGroup.IsBuilding;
Subtitle = $"{buildCount} pending, {finishCount} completed";
if ( warnings > 0 )
Subtitle += $", {warnings} warnings";
if ( IsRunning )
return;
bool success = CompilerGroup.Compilers.All( x => x.BuildSuccess );
files.Clear();
if ( success )
{
isErrored = false;
BorderColor = Theme.Green;
NoticeManager.Remove( this, 1 );
if ( EditorPreferences.NotificationSounds )
EditorUtility.PlayRawSound( "sounds/editor/success.wav" );
}
else
{
isErrored = true;
BorderColor = Theme.Red;
Subtitle = "";
NoticeManager.Remove( this, EditorPreferences.ErrorNotificationTimeout );
AddDiagnostics();
if ( EditorPreferences.NotificationSounds )
EditorUtility.PlayRawSound( "sounds/editor/fail.wav" );
}
}
protected override void OnPaint()
{
if ( !EditorPreferences.NotificationPopups ) return;
base.OnPaint();
}
protected override void OnMousePress( MouseEvent e )
{
base.OnMousePress( e );
if ( e.LeftMouseButton && e.HasShift )
{
foreach ( var file in files )
{
CodeEditor.OpenFile( file );
}
e.Accepted = true;
return;
}
}
private void AddDiagnostics()
{
var diagnostics = CompilerGroup.Compilers
.Where( x => x.Diagnostics != null )
.SelectMany( x => x.Diagnostics )
.Where( x => x.Severity >= DiagnosticSeverity.Error )
.OrderByDescending( x => (int)x.Severity )
.Take( 5 )
.ToArray();
if ( diagnostics.Length == 0 ) return;
var bodyWidget = new Widget( this );
bodyWidget.Layout = Layout.Column();
foreach ( var diag in diagnostics )
{
var btn = bodyWidget.Layout.Add( new DiagnosticWidget( diag ) );
files.Add( btn.FilePath );
}
SetBodyWidget( bodyWidget );
}
[Event( "compile.started" )]
public static void OnCompileStarted( CompileGroup compiler )
{
// find an old notice to replace
var notice = NoticeManager.All.OfType<CodeCompileNotice>().FirstOrDefault( x => x.CompilerGroup.Name == compiler.Name );
if ( !notice.IsValid() ) notice = new CodeCompileNotice( compiler );
notice.CompilerGroup = compiler;
notice.Reset();
}
}
internal class DiagnosticWidget : Widget
{
private Diagnostic diag;
public string FilePath { get; private set; }
public int LineNumber { get; private set; }
public int CharNumber { get; private set; }
public string Message { get; private set; }
public DiagnosticWidget( Diagnostic diag ) : base( null )
{
this.diag = diag;
FixedHeight = 18;
MinimumWidth = 400;
Cursor = CursorShape.Finger;
ToolTip = $"{diag.GetMessage()}\n{diag.Location}";
var span = diag.Location.GetLineSpan();
var mappedSpan = diag.Location.GetMappedLineSpan();
Message = diag.GetMessage();
// Path can be null if the spans are not valid (not related to a file)
FilePath = mappedSpan.HasMappedPath ? mappedSpan.Path : span.Path;
LineNumber = mappedSpan.Span.Start.Line + 1;
CharNumber = mappedSpan.Span.Start.Character + 1;
}
protected override void DoLayout()
{
base.DoLayout();
}
protected override void OnPaint()
{
base.OnPaint();
Paint.Antialiasing = true;
Paint.TextAntialiasing = true;
var color = Theme.Red;
if ( diag.Severity == DiagnosticSeverity.Warning ) color = Theme.Yellow;
var bareFilename = System.IO.Path.GetFileName( FilePath );
var rect = LocalRect;
var textColor = Color.Lerp( Color.White, color, 0.5f );
if ( IsUnderMouse )
{
Paint.ClearPen();
Paint.SetBrush( color.WithAlpha( 0.1f ) );
Paint.DrawRect( rect );
}
var measureText = $"{diag.Id}: {Message} {bareFilename}:{LineNumber}";
var size = Paint.MeasureText( new Rect( 0, 1000 ), measureText, TextFlag.LeftCenter );
rect = rect.Shrink( 4, 0 );
Paint.SetPen( textColor.WithAlpha( 0.8f ) );
var fileRect = Paint.DrawText( rect, $"{bareFilename}:{LineNumber}", TextFlag.RightCenter );
rect.Right -= fileRect.Width;
rect.Right -= 8;
Paint.SetPen( textColor.WithAlpha( 0.5f ) );
Paint.DrawText( rect.Shrink( 16, 0, 0, 0 ), $"{diag.Id}: {Message}", TextFlag.LeftCenter );
Paint.ClearPen();
Paint.SetBrush( color );
Paint.DrawCircle( new Rect( 0, 16 ).Shrink( 2 ) );
}
protected override void OnMouseClick( MouseEvent e )
{
if ( e.LeftMouseButton )
{
// Can be null if our diagnostic spans are not valid
if ( FilePath != null )
{
CodeEditor.OpenFile( FilePath, LineNumber, CharNumber );
}
e.Accepted = true;
return;
}
base.OnMouseClick( e );
}
}