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(); } } }