| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- 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
- {
- /// <summary>错误/警告列表项(ListBox 绑定)。</summary>
- 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; }
- }
- /// <summary>
- /// C# 脚本节点的自定义编辑视图(承载在 PluginViewDialog,DataContext 由宿主注入为 ScriptToolModel)。
- /// 中部是 CodeForge 完整代码编辑器 <see cref="CodeEditorControl"/>:自由写命名空间/多个类/方法,
- /// 智能补全、编译诊断波浪线、代码配色、鸟瞰图全部可用;顶部「引用」按钮打开程序集引用管理(添加/移除 DLL)。
- /// 用户代码是一个完整插件类(继承 BasePlugin<ScriptToolModel>,PluginRun/DeclareOutputs 预写好)。
- /// - IPluginViewSave:确定时把代码回写模型。
- /// </summary>
- public partial class ScriptToolView : UserControl, IPluginViewSave, IPluginViewTip
- {
- static ScriptToolView()
- {
- // XAML 里用到 CodeForge 控件,按 CodeForge 说明书在解析前确保其内嵌依赖解析器已注册(幂等)
- try { CodeForgeRuntime.Initialize(); } catch { /* 模块初始化器通常已自动注册 */ }
- }
- private readonly ObservableCollection<DiagnosticItem> _diagnostics = new ObservableCollection<DiagnosticItem>();
- 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<ScriptToolModel> 的类(默认 MyScript)");
- }
- private void OnUnloaded(object sender, RoutedEventArgs e)
- {
- try { Editor.Dispose(); } catch { }
- }
- /// <summary>给编辑器补全/编译环境加入平台程序集引用(TeamAAS 全家桶 + 本插件 + 用户归档的 References\)。</summary>
- private void AddPlatformReferences()
- {
- try { Editor.AddReference(typeof(ScriptToolModel).Assembly); } catch { }
- var seen = new HashSet<string>(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<string> errs, warns;
- BasePlugin<ScriptToolModel> 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<string, object> 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"));
- }
- /// <summary>设计期临时模型的 FlowName/NodeName 是 internal set,反射赋值以免脚本 Log 时取到 null。</summary>
- 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();
- }
- }
- }
|