using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using CodeForge;
using TeamAAS.FlowEditor.Models;
using TeamAAS.FlowEditor.Plugins;
using Plugins.Script.Models;
using Plugins.Script.Core;
namespace Plugins.Script.Views
{
/// 错误/警告列表项(ListBox 绑定)。
public class DiagnosticItem
{
public string SeverityText { get; set; }
public Brush SeverityBrush { get; set; }
public string Display { get; set; }
public int Line { get; set; }
public int Column { get; set; }
public bool IsWarning { get; set; }
}
///
/// C# 脚本节点的自定义编辑视图(承载在 PluginViewDialog,DataContext 由宿主注入为 ScriptToolModel)。
/// 中部是 CodeForge 完整代码编辑器 :自由写命名空间/多个类/方法,
/// 智能补全、编译诊断波浪线、代码配色、鸟瞰图全部可用;顶部「引用」按钮打开程序集引用管理(添加/移除 DLL)。
/// 用户代码是一个完整插件类(继承 BasePlugin<ScriptToolModel>,PluginRun/DeclareOutputs 预写好)。
/// - IPluginViewSave:确定时把代码回写模型。
///
public partial class ScriptToolView : UserControl, IPluginViewSave, IPluginViewTip
{
static ScriptToolView()
{
// XAML 里用到 CodeForge 控件,按 CodeForge 说明书在解析前确保其内嵌依赖解析器已注册(幂等)
try { CodeForgeRuntime.Initialize(); } catch { /* 模块初始化器通常已自动注册 */ }
}
private readonly ObservableCollection _diagnostics = new ObservableCollection();
private ScriptToolModel _model;
private ScriptToolModel Model { get { return _model; } }
private bool _initialized;
private bool _refsAdded;
public ScriptToolView()
{
InitializeComponent();
DataContextChanged += (s, e) => _model = e.NewValue as ScriptToolModel;
ErrorListBox.ItemsSource = _diagnostics;
Editor.DiagnosticMessage += (s, msg) => Dispatcher.BeginInvoke(new Action(() => AppendOutput(msg)));
Editor.CompileRequested += (s, e) => Dispatcher.BeginInvoke(new Action(CompileCode));
Loaded += OnLoaded;
Unloaded += OnUnloaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (_model != null)
{
TxtTitle.Text = string.IsNullOrWhiteSpace(_model.NodeName) ? "C#脚本" : _model.NodeName;
Editor.Code = string.IsNullOrEmpty(_model.Code) ? ScriptToolModel.DefaultCode : _model.Code;
}
else
{
Editor.Code = ScriptToolModel.DefaultCode;
}
if (!_refsAdded)
{
// 批量添加引用:BeginBatch 作用域内挂起 ReferencesChanged,Dispose 时只触发一次。
// 否则每加一个引用都会重启一次 Roslyn 预热(N 个引用=N 次并发预热),打开脚本编辑器会卡顿数秒。
using (Editor.Engine.References.BeginBatch())
AddPlatformReferences();
_refsAdded = true;
}
try { var _ = Editor.WarmupRoslynAsync(); } catch { }
_initialized = true;
RefreshErrorCount();
AppendOutput("就绪 · 顶部「引用」=程序集引用管理 · 编译(F5)/运行 · 入口=继承 BasePlugin 的类(默认 MyScript)");
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
try { Editor.Dispose(); } catch { }
}
/// 给编辑器补全/编译环境加入平台程序集引用(TeamAAS 全家桶 + 本插件 + 用户归档的 References\)。
private void AddPlatformReferences()
{
try { Editor.AddReference(typeof(ScriptToolModel).Assembly); } catch { }
var seen = new HashSet(StringComparer.OrdinalIgnoreCase);
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
try
{
if (asm.IsDynamic) continue;
var name = asm.GetName().Name ?? "";
if (!name.StartsWith("TeamAAS", StringComparison.OrdinalIgnoreCase)) continue;
var loc = asm.Location;
if (string.IsNullOrEmpty(loc) || !File.Exists(loc)) continue;
if (!seen.Add(loc)) continue;
Editor.AddReferenceFromFile(loc);
}
catch { /* 忽略单个引用失败 */ }
}
// 用户 DLL 归档目录:根目录 References\(CodeForge 归档约定处)+ Runtime\References\(历史 PostBuild 搬移遗留位置)
var refDirs = new[]
{
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "References"),
Path.Combine(TeamAAS.PathHelper.RuntimeDir, "References"),
};
foreach (var dir in refDirs)
{
try
{
if (!Directory.Exists(dir)) continue;
foreach (var dll in Directory.GetFiles(dir, "*.dll"))
{
try { Editor.AddReferenceFromFile(dll); } catch { }
}
}
catch { }
}
}
#region 工具栏事件
private void BtnRefs_Click(object sender, RoutedEventArgs e)
{
var owner = Window.GetWindow(this);
var dlg = new ScriptReferenceDialog(Editor, owner);
if (owner != null)
{
dlg.Owner = owner;
dlg.WindowStartupLocation = WindowStartupLocation.CenterOwner;
}
dlg.ShowDialog();
}
private void BtnCompile_Click(object sender, RoutedEventArgs e) { CompileCode(); }
private void BtnRun_Click(object sender, RoutedEventArgs e) { RunScript(); }
private void BtnClearOutput_Click(object sender, RoutedEventArgs e) { OutputTextBox.Text = ""; }
#endregion
#region 编译 / 运行
private void CompileCode()
{
ClearDiagnostics();
AppendOutput("======== 编译 " + DateTime.Now.ToString("HH:mm:ss") + " ========");
try
{
var c = Editor.Compile();
Editor.ShowCompileDiagnostics(c);
if (c != null)
{
bool hasSpan = c.Spans != null && c.Spans.Count > 0;
if (hasSpan)
{
foreach (var s in c.Spans) AddDiagnostic(s.IsWarning, s.Message, s.Line, s.Column);
}
else if (c.Errors != null)
{
foreach (var er in c.Errors) AddDiagnostic(false, er, 0, 0);
}
if (c.Warnings != null) foreach (var w in c.Warnings) AppendOutput(" [警告] " + w);
AppendOutput(c.Success
? " ✅ 编译成功(" + (c.AssemblyBytes != null ? c.AssemblyBytes.Length : 0) + " bytes)"
: " ❌ 编译失败");
}
}
catch (Exception ex)
{
AppendOutput(" 💥 编译异常:" + ex.Message);
AddDiagnostic(false, ex.Message, 0, 0);
}
RefreshErrorCount();
}
private void RunScript()
{
ClearDiagnostics();
AppendOutput("======== 运行 " + DateTime.Now.ToString("HH:mm:ss") + " ========");
AppendOutput("(设计期:Service/CoreManager 可用;上游流程变量无数据;Log 写入平台日志)");
var tempModel = new ScriptToolModel { Code = Editor.Code ?? "" };
SetDesignTimeNames(tempModel);
List errs, warns;
BasePlugin script;
try
{
script = ScriptCompiler.CreateScript(Editor.Code ?? "", tempModel,
TeamAAS.Global.ServiceRegistry.Default, null, out errs, out warns);
}
catch (Exception ex) { AppendOutput("💥 编译异常:" + ex.Message); return; }
if (script == null)
{
if (errs != null) foreach (var er in errs) { AppendOutput(" [错误] " + er); AddDiagnostic(false, er, 0, 0); }
RefreshErrorCount();
AppendOutput(" ❌ 编译失败,未运行");
return;
}
if (warns != null) foreach (var w in warns) AppendOutput(" [警告] " + w);
Dictionary results = null;
NodeRunStatus status = NodeRunStatus.Failed;
var oldOut = Console.Out;
var oldErr = Console.Error;
var sw = new StringWriter();
try
{
Console.SetOut(sw);
Console.SetError(sw);
status = script.PluginRun(CancellationToken.None, out results);
}
catch (Exception ex) { AppendOutput("💥 运行异常:" + ex); }
finally { Console.SetOut(oldOut); Console.SetError(oldErr); }
var cout = sw.ToString();
if (!string.IsNullOrEmpty(cout)) AppendOutput(cout.TrimEnd());
AppendOutput(" 状态 = " + status);
if (results != null)
foreach (var kv in results) AppendOutput(" " + kv.Key + " = " + (kv.Value ?? "null"));
}
/// 设计期临时模型的 FlowName/NodeName 是 internal set,反射赋值以免脚本 Log 时取到 null。
private static void SetDesignTimeNames(ScriptToolModel m)
{
SetProp(m, "FlowName", "脚本测试");
SetProp(m, "NodeName", "设计期运行");
}
private static void SetProp(object obj, string propName, object value)
{
try
{
var p = typeof(TeamAAS.FlowEditor.Plugins.BasePluginModel).GetProperty(propName);
if (p == null) return;
var setter = p.GetSetMethod(true);
if (setter != null) setter.Invoke(obj, new object[] { value });
}
catch { /* 设计期辅助,失败忽略 */ }
}
#endregion
#region 错误列表
private void ClearDiagnostics() { _diagnostics.Clear(); }
private void AddDiagnostic(bool isWarning, string message, int line, int column)
{
_diagnostics.Add(new DiagnosticItem
{
IsWarning = isWarning,
SeverityText = isWarning ? "警告" : "错误",
SeverityBrush = isWarning ? Brushes.Goldenrod : Brushes.IndianRed,
Line = Math.Max(1, line),
Column = Math.Max(1, column),
Display = (line > 0 ? "L" + line + ",C" + column + " " : "") + message
});
}
private void RefreshErrorCount()
{
int err = 0, warn = 0;
foreach (var d in _diagnostics) { if (d.IsWarning) warn++; else err++; }
ErrorCount.Text = "(" + err + " 错误 / " + warn + " 警告)";
}
private void ErrorListBox_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
try
{
var it = ErrorListBox.SelectedItem as DiagnosticItem;
if (it == null || it.Line < 1) return;
Editor.ScrollToLine(it.Line);
}
catch { /* 跳转失败忽略 */ }
}
#endregion
#region IPluginViewSave
public void SaveChanges()
{
if (_model == null) return;
_model.Code = Editor.Code ?? "";
}
#endregion
#region IPluginViewTip
private TeamAAS.Dialogs.Dialogs.PluginViewDialog HostDialog
{
get { return Window.GetWindow(this) as TeamAAS.Dialogs.Dialogs.PluginViewDialog; }
}
public void ShowTip(string message) { var d = HostDialog; if (d != null) d.ShowTip(message); }
public void ShowHoverHint(string message) { var d = HostDialog; if (d != null) d.ShowHoverHint(message); }
#endregion
private void AppendOutput(string text)
{
OutputTextBox.AppendText((text ?? "") + Environment.NewLine);
OutputTextBox.ScrollToEnd();
}
}
}