@codesys/mcp-toolkit 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +109 -9
  3. package/README.zh-CN.md +72 -9
  4. package/dist/actions.d.ts +9 -4
  5. package/dist/actions.js +1 -1
  6. package/dist/actions.js.map +1 -1
  7. package/dist/codesys/debug.d.ts +5 -0
  8. package/dist/codesys/debug.js +89 -0
  9. package/dist/codesys/debug.js.map +1 -0
  10. package/dist/codesys/device-tools.d.ts +3 -0
  11. package/dist/codesys/device-tools.js +38 -0
  12. package/dist/codesys/device-tools.js.map +1 -0
  13. package/dist/codesys/graphical.d.ts +76 -0
  14. package/dist/codesys/graphical.js +226 -0
  15. package/dist/codesys/graphical.js.map +1 -0
  16. package/dist/codesys/lang.d.ts +39 -0
  17. package/dist/codesys/lang.js +177 -0
  18. package/dist/codesys/lang.js.map +1 -0
  19. package/dist/codesys/simtest.d.ts +48 -0
  20. package/dist/codesys/simtest.js +103 -0
  21. package/dist/codesys/simtest.js.map +1 -0
  22. package/dist/codesys/st.d.ts +1 -1
  23. package/dist/codesys/st.js.map +1 -1
  24. package/dist/codesys/trace.d.ts +19 -0
  25. package/dist/codesys/trace.js +139 -0
  26. package/dist/codesys/trace.js.map +1 -0
  27. package/dist/hmi/native.d.ts +89 -0
  28. package/dist/hmi/native.js +447 -0
  29. package/dist/hmi/native.js.map +1 -0
  30. package/dist/hmi/render.d.ts +90 -0
  31. package/dist/hmi/render.js +307 -0
  32. package/dist/hmi/render.js.map +1 -0
  33. package/dist/hmi/route.d.ts +32 -0
  34. package/dist/hmi/route.js +184 -0
  35. package/dist/hmi/route.js.map +1 -0
  36. package/dist/hmi/service.d.ts +45 -0
  37. package/dist/hmi/service.js +182 -0
  38. package/dist/hmi/service.js.map +1 -0
  39. package/dist/hmi/spec.d.ts +821 -0
  40. package/dist/hmi/spec.js +191 -0
  41. package/dist/hmi/spec.js.map +1 -0
  42. package/dist/hmi/templates.d.ts +11 -0
  43. package/dist/hmi/templates.js +92 -0
  44. package/dist/hmi/templates.js.map +1 -0
  45. package/dist/hmi/tools.d.ts +4 -0
  46. package/dist/hmi/tools.js +222 -0
  47. package/dist/hmi/tools.js.map +1 -0
  48. package/dist/hmi/value.d.ts +3 -0
  49. package/dist/hmi/value.js +26 -0
  50. package/dist/hmi/value.js.map +1 -0
  51. package/dist/panel/hmi.js +138 -0
  52. package/dist/panel.d.ts +3 -0
  53. package/dist/panel.js +17 -0
  54. package/dist/panel.js.map +1 -1
  55. package/dist/server.d.ts +1 -1
  56. package/dist/server.js +7 -2
  57. package/dist/server.js.map +1 -1
  58. package/dist/tools.d.ts +5 -1
  59. package/dist/tools.js +18 -2
  60. package/dist/tools.js.map +1 -1
  61. package/dist/viewer.d.ts +1 -1
  62. package/dist/viewer.js +13 -2
  63. package/dist/viewer.js.map +1 -1
  64. package/package.json +1 -1
  65. package/watcher/mcp_alarms.py +134 -0
  66. package/watcher/mcp_debug.py +161 -0
  67. package/watcher/mcp_devices.py +83 -0
  68. package/watcher/mcp_lang.py +179 -0
  69. package/watcher/mcp_ops.py +3 -1
  70. package/watcher/mcp_trace.py +90 -0
  71. package/watcher/mcp_ui.py +54 -0
  72. package/watcher/mcp_visu.py +422 -0
  73. package/watcher/watcher.py +35 -1
@@ -0,0 +1,422 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Native CODESYS visualizations, built from a plan the server computes (src/hmi/native.ts).
3
+ #
4
+ # Two routes, both CODESYS's own:
5
+ # - the visualization script driver (create_visualobject, add_element, set_property with
6
+ # property-view paths) for image switchers, bindings, colour toggles, movement, inputs;
7
+ # - CODESYS's element creators (the objects its toolbox uses) for what the property paths
8
+ # can't set: a static font, a gradient, a hidden frame or fill. Added through CODESYS's
9
+ # object manager, like the toolbox does.
10
+ # Rounded corners and the canvas background are members of CODESYS's element objects that
11
+ # were checked alone. Nothing here writes other raw members (some raise assertion dialogs).
12
+ # A property that fails is reported, not fatal. render paints a visualization into a PNG.
13
+ #
14
+ # Keep this file ASCII-only (IronPython 2.7 inside CODESYS).
15
+ import os
16
+
17
+ import mcp_ops
18
+ from mcp_ops import Ctx, OpError, name_of
19
+ from mcp_compat import to_text
20
+
21
+ import scriptengine as se
22
+
23
+ _CACHE = {}
24
+
25
+
26
+ def _find_type(name):
27
+ if ("t", name) in _CACHE:
28
+ return _CACHE[("t", name)]
29
+ from System import AppDomain
30
+ found = None
31
+ for asm in AppDomain.CurrentDomain.GetAssemblies():
32
+ try:
33
+ found = asm.GetType(name)
34
+ except Exception:
35
+ found = None
36
+ if found is not None:
37
+ break
38
+ _CACHE[("t", name)] = found
39
+ return found
40
+
41
+
42
+ def _static(type_name, prop):
43
+ from System.Reflection import BindingFlags
44
+ t = _find_type(type_name)
45
+ if t is None:
46
+ return None
47
+ p = t.GetProperty(prop, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)
48
+ return p.GetValue(None, None) if p is not None else None
49
+
50
+
51
+ def _object_mgr():
52
+ for t in ("_3S.CoDeSys.VisualEditor.APEnvironment", "_3S.CoDeSys.SecurityManager.APEnvironment"):
53
+ m = _static(t, "ObjectMgr")
54
+ if m is not None:
55
+ return m
56
+ raise OpError("unavailable", "CODESYS's object manager isn't reachable.")
57
+
58
+
59
+ def _drawing():
60
+ import clr
61
+ clr.AddReference("System.Drawing")
62
+ import System.Drawing as SD
63
+ return SD
64
+
65
+
66
+ def _argb(v):
67
+ import System
68
+ v = int(v)
69
+ return _drawing().Color.FromArgb(System.Int32(v - 0x100000000 if v >= 0x80000000 else v))
70
+
71
+
72
+ def _value(v):
73
+ if isinstance(v, dict) and "argb" in v:
74
+ return _argb(v["argb"])
75
+ return v
76
+
77
+
78
+ def _enum(type_name, wanted):
79
+ import System
80
+ t = _find_type(type_name)
81
+ if t is None:
82
+ raise OpError("unavailable", "%s not found" % type_name)
83
+ names = list(System.Enum.GetNames(t))
84
+ for n in names:
85
+ if n == wanted:
86
+ return System.Enum.Parse(t, n)
87
+ for n in names:
88
+ if wanted in n:
89
+ return System.Enum.Parse(t, n)
90
+ raise OpError("bad_args", "%s has no %s (%s)" % (type_name, wanted, ", ".join(names)))
91
+
92
+
93
+ def _pset(o, name, value):
94
+ """Set a property, also one implemented explicitly on an interface."""
95
+ t = o.GetType()
96
+ for it in [t] + list(t.GetInterfaces()):
97
+ p = it.GetProperty(name)
98
+ if p is not None and p.CanWrite:
99
+ p.SetValue(o, value, None)
100
+ return
101
+ raise AttributeError(name)
102
+
103
+
104
+ class _Modify(object):
105
+ def __init__(self, proj, obj):
106
+ self.h, self.g = proj.handle, obj.guid
107
+
108
+ def __enter__(self):
109
+ self.mgr = _object_mgr()
110
+ self.mo = self.mgr.GetObjectToModify(self.h, self.g)
111
+ return self.mo.Object
112
+
113
+ def __exit__(self, et, ev, tb):
114
+ self.mgr.SetObject(self.mo, et is None, None)
115
+ return False
116
+
117
+
118
+ def _creator(kind):
119
+ import System
120
+ from System.Reflection import BindingFlags
121
+ t = _find_type("_3S.CoDeSys.ScriptDriverVisualization.APEnvironment")
122
+ if t is None:
123
+ raise OpError("unavailable", "The visualization script driver isn't loaded.")
124
+ f = t.GetMethod("CreateVisualElemCreatorFactory", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static).Invoke(None, None)
125
+ return f.CreateInstance(kind)
126
+
127
+
128
+ def _gradient(c1, c2, angle):
129
+ import System
130
+ GV = _find_type("_3S.CoDeSys.PropertyView.GradientView")
131
+ k = [k for k in GV.GetConstructors() if len(k.GetParameters()) == 9][0]
132
+ ps = k.GetParameters()
133
+ con_t = ps[0].ParameterType
134
+
135
+ def con(v):
136
+ return System.Activator.CreateInstance(con_t, System.Array[object]([_argb(v)]))
137
+ look = System.Enum.GetValues(ps[4].ParameterType).GetValue(0)
138
+ SD = _drawing()
139
+ return k.Invoke(System.Array[object]([con(c1), con(c2), int(angle), SD.Point(0, 0), look, 1, 0, con(c2), None]))
140
+
141
+
142
+ def _font(spec):
143
+ SD = _drawing()
144
+ name, size = spec[0], float(spec[1])
145
+ style = SD.FontStyle.Bold if len(spec) > 2 and spec[2] == "bold" else SD.FontStyle.Regular
146
+ return SD.Font(name, size, style)
147
+
148
+
149
+ _SIMPLE = {"rect": "RECTANGLE", "round": "ROUNDRECT", "circle": "CIRCLE"}
150
+
151
+
152
+ def _simple_creator(el):
153
+ import System
154
+ SD = _drawing()
155
+ cr = _creator("VisuFbElemSimple")
156
+ _pset(cr, "Type", _enum("_3S.CoDeSys.VisualElem.VisuEnumSimpleType", _SIMPLE[el["kind"]]))
157
+ _pset(cr, "Position", SD.Rectangle(int(el["x"]), int(el["y"]), int(el["w"]), int(el["h"])))
158
+ if el.get("text") is not None:
159
+ _pset(cr, "Text", el["text"])
160
+ _pset(cr, "HorizontalAlignment", _enum("_3S.CoDeSys.VisualElem.VisuEnumHorizontalAlignment", el.get("align") or "HCENTER"))
161
+ if el.get("font"):
162
+ m = cr.GetType().GetInterface("ITextElementCreator").GetMethod("SetFont")
163
+ m.Invoke(cr, System.Array[object]([_font(el["font"]), _argb(el.get("color", 0xFF0F172A))]))
164
+ if el.get("fill") is None:
165
+ try:
166
+ _pset(cr, "FillAttributes", _enum("_3S.CoDeSys.VisualElem.VisuEnumBrushStyle", "NULL"))
167
+ except OpError:
168
+ _pset(cr, "FillAttributes", _enum("_3S.CoDeSys.VisualElem.VisuEnumBrushStyle", "HOLLOW"))
169
+ else:
170
+ _pset(cr, "Color", _argb(el["fill"]))
171
+ if el.get("frame") is None:
172
+ _pset(cr, "FrameAttributes", _enum("_3S.CoDeSys.VisualElem.VisuEnumPenStyle", "HOLLOW"))
173
+ else:
174
+ _pset(cr, "FrameColor", _argb(el["frame"]))
175
+ _pset(cr, "LineWidth", int(el.get("lineWidth") or 1))
176
+ if el.get("gradient"):
177
+ g = el["gradient"]
178
+ _pset(cr, "Gradient", _gradient(g[0], g[1], g[2] if len(g) > 2 else 90))
179
+ _pset(cr, "UseGradient", True)
180
+ if el.get("invisible"):
181
+ _pset(cr, "InvisibleExpression", el["invisible"])
182
+ return cr
183
+
184
+
185
+ def _add_created(proj, visu, cr, name):
186
+ elem = cr.CreateVisualElement(name)
187
+ with _Modify(proj, visu) as vo:
188
+ elem.Id = vo.UniqueId
189
+ elem.OwningObjectGuid = vo.MetaObject.ObjectGuid
190
+ vo.VisualElemList.Add(elem)
191
+ return elem.Id
192
+
193
+
194
+ def _script_element(visu, eid):
195
+ for e in visu.visual_element_list:
196
+ if e.id == eid:
197
+ return e
198
+ return None
199
+
200
+
201
+ def _find_child(c, name):
202
+ for ch in c.app.get_children(False):
203
+ if name_of(ch).lower() == name.lower():
204
+ return ch
205
+ return None
206
+
207
+
208
+ def op_visu_build(args):
209
+ c = Ctx()
210
+ if not hasattr(c.app, "create_visualobject"):
211
+ raise OpError("unavailable", "This CODESYS has no visualization scripting (CODESYS Visualization 4.7 or later is needed).")
212
+ name = args["name"]
213
+ for n in (name, args.get("image_pool")):
214
+ if not n:
215
+ continue
216
+ old = _find_child(c, n)
217
+ if old is not None:
218
+ if not args.get("replace"):
219
+ raise OpError("exists", "%s already exists. Pass replace to rebuild it." % n)
220
+ old.remove()
221
+ warnings = []
222
+ if args.get("images"):
223
+ pool = c.app.create_imagepool(args["image_pool"])
224
+ for img in args["images"]:
225
+ try:
226
+ pool.images.add(img["id"], img["path"], se.ImageLinkType.Embedded, se.AutoUpdateMode.Never)
227
+ except Exception as ex:
228
+ warnings.append("image %s: %s" % (img["id"], to_text(ex)))
229
+ visu = c.app.create_visualobject(name)
230
+ if args.get("size") or args.get("dialog"):
231
+ try:
232
+ _visual_property(c, visu, args.get("size"), bool(args.get("dialog")))
233
+ except Exception as ex:
234
+ warnings.append("size/dialog: %s" % to_text(ex))
235
+ T = se.VisualElementType
236
+ corners = []
237
+ for i, el in enumerate(args.get("elements") or []):
238
+ label = el.get("name") or "E%d" % i
239
+ try:
240
+ if el["kind"] in _SIMPLE:
241
+ eid = _add_created(c.proj, visu, _simple_creator(el), label)
242
+ e = _script_element(visu, eid)
243
+ else:
244
+ e = visu.visual_element_list.add_element({"switcher": T.ImageSwitcher, "image": T.Image}[el["kind"]])
245
+ for p, v in (("Position.X", el["x"]), ("Position.Y", el["y"]), ("Position.Width", el["w"]), ("Position.Height", el["h"])):
246
+ e.set_property(p, int(v))
247
+ eid = e.id
248
+ except Exception as ex:
249
+ warnings.append("%s: not added: %s" % (label, to_text(ex)))
250
+ continue
251
+ for path, v in (el.get("props") or {}).items():
252
+ try:
253
+ e.set_property(path, _value(v))
254
+ except Exception as ex:
255
+ if len(warnings) < 40:
256
+ warnings.append("%s %s: %s" % (label, path, to_text(ex)))
257
+ if el["kind"] not in _SIMPLE and (el.get("click") or el.get("goto") or el.get("dialog") or el.get("close_dialog") or el.get("login")):
258
+ # Image elements have no input handling: an input action there makes CODESYS's
259
+ # code generation raise an assertion dialog. Click areas are simple shapes.
260
+ warnings.append("%s: input actions only go on shapes, not on %s" % (label, el["kind"]))
261
+ el = dict(el)
262
+ for k in ("click", "goto", "dialog", "close_dialog", "login"):
263
+ el.pop(k, None)
264
+ if el.get("click"):
265
+ try:
266
+ e.add_input_action(visu.input_action_factory.create_execute_st_code(el["click"]), se.InputActionEventType.OnMouseClick)
267
+ except Exception as ex:
268
+ warnings.append("%s click: %s" % (label, to_text(ex)))
269
+ if el.get("goto"):
270
+ try:
271
+ act = visu.input_action_factory.create_change_shown_visualization(se.ChangeShownVisualizationType.Assignment, el["goto"])
272
+ e.add_input_action(act, se.InputActionEventType.OnMouseClick)
273
+ except Exception as ex:
274
+ warnings.append("%s goto: %s" % (label, to_text(ex)))
275
+ if el.get("dialog"):
276
+ try:
277
+ from System.Collections.Generic import Dictionary, List
278
+ d = el["dialog"]
279
+ act = visu.input_action_factory.create_open_dialog(d["name"], Dictionary[object, object](), List[object](), True, False, str(int(d.get("x", 200))), str(int(d.get("y", 120))))
280
+ e.add_input_action(act, se.InputActionEventType.OnMouseClick)
281
+ except Exception as ex:
282
+ warnings.append("%s dialog: %s" % (label, to_text(ex)))
283
+ if el.get("login"):
284
+ try:
285
+ lt = _find_type("_3S.CoDeSys.VisualElem.VisuUserMgmtInputActionFactory")
286
+ import System
287
+ act = System.Activator.CreateInstance(lt).CreateInputAction()
288
+ e.add_input_action(act, se.InputActionEventType.OnMouseClick)
289
+ except Exception as ex:
290
+ warnings.append("%s login: %s" % (label, to_text(ex)))
291
+ if el.get("close_dialog"):
292
+ try:
293
+ act = visu.input_action_factory.create_close_dialog(el["close_dialog"], "OK")
294
+ e.add_input_action(act, se.InputActionEventType.OnMouseClick)
295
+ except Exception as ex:
296
+ warnings.append("%s close: %s" % (label, to_text(ex)))
297
+ if el.get("radius"):
298
+ corners.append((eid, int(el["radius"])))
299
+ try:
300
+ with _Modify(c.proj, visu) as vo:
301
+ if args.get("background") is not None:
302
+ import System
303
+ bg = vo.VisualElemList.Background
304
+ bg.UseBackgroundColor = True
305
+ v = int(args["background"])
306
+ bg.BackgroundColor = System.Int32(v - 0x100000000 if v >= 0x80000000 else v)
307
+ for eid, r in corners:
308
+ el = vo.FindElementById(eid)
309
+ if el is not None:
310
+ el.GetVisualElemMember("m_Radius.m_iRadius", True, r).Value = r
311
+ except Exception as ex:
312
+ warnings.append("background/corners: %s" % to_text(ex))
313
+ return {"name": name_of(visu), "path": name_of(visu), "kind": "visualization", "elements": len(visu.visual_element_list), "warnings": warnings, "saved": c.save_if(args)}
314
+
315
+
316
+ def _visual_property(c, visu, size, dialog):
317
+ """The visualization's own settings: canvas size, and whether it is a dialog."""
318
+ import System
319
+ SD = _drawing()
320
+ mgr = _object_mgr()
321
+ mo = mgr.GetObjectToModify(c.proj.handle, visu.guid)
322
+ ok = False
323
+ try:
324
+ props = [p for p in mo.Properties if p.GetType().Name == "VisualProperty"]
325
+ if props:
326
+ vp = props[0]
327
+ if size:
328
+ vp.Size = SD.Size(int(size[0]), int(size[1]))
329
+ if dialog:
330
+ vp.UsageType = System.Enum.Parse(_find_type("_3S.CoDeSys.VisualObject.VisualUsageType"), "Dialog")
331
+ ok = True
332
+ finally:
333
+ mgr.SetObject(mo, ok, None)
334
+
335
+
336
+ def op_visu_manager(args):
337
+ """Start page, client size and style of the Visualization Manager, TargetVisu and WebVisu."""
338
+ import System
339
+ c = Ctx()
340
+ SD = _drawing()
341
+ done, warnings = [], []
342
+ style = None
343
+ if args.get("style"):
344
+ repo = _static("_3S.CoDeSys.VisualEditor.APEnvironment", "VisualizationStylesRepository")
345
+ for st in (repo.GetAllStyles() if repo is not None else []):
346
+ nm = to_text(_get(st, "Name") or "")
347
+ if nm.lower() == args["style"].lower():
348
+ style = to_text(_get(st, "DisplayName"))
349
+ if style is None:
350
+ warnings.append("style %s is not installed" % args["style"])
351
+ for name in ("Visualization Manager", "TargetVisu", "WebVisu"):
352
+ obj = _find_child_deep(c, name)
353
+ if obj is None:
354
+ continue
355
+ try:
356
+ with _Modify(c.proj, obj) as vo:
357
+ vs = vo.ViewSettings
358
+ if args.get("start"):
359
+ vs.StartVisu = args["start"]
360
+ if name == "Visualization Manager":
361
+ if style:
362
+ vs.VisualizationStyle = style
363
+ elif args.get("width") and args.get("height"):
364
+ try:
365
+ vs.ClientSizeMode = System.Enum.Parse(vs.ClientSizeMode.GetType(), "Specified")
366
+ except Exception:
367
+ pass
368
+ vs.ClientSize = SD.Size(int(args["width"]), int(args["height"]))
369
+ try:
370
+ vs.BestFit = True
371
+ except Exception:
372
+ pass
373
+ done.append(name)
374
+ except Exception as ex:
375
+ warnings.append("%s: %s" % (name, to_text(ex)))
376
+ return {"updated": done, "warnings": warnings, "saved": c.save_if(args)}
377
+
378
+
379
+ def _get(o, name):
380
+ try:
381
+ return getattr(o, name)
382
+ except Exception:
383
+ return None
384
+
385
+
386
+ def _find_child_deep(c, name):
387
+ for ch in c.app.get_children(True):
388
+ if name_of(ch).lower() == name.lower():
389
+ return ch
390
+ return None
391
+
392
+
393
+ def op_visu_render(args):
394
+ """Paints a visualization into a PNG (no editor or window needed)."""
395
+ c = Ctx()
396
+ visu = _find_child(c, args["name"])
397
+ if visu is None:
398
+ raise OpError("not_found", "No visualization named %s." % args["name"])
399
+ mgr = _object_mgr()
400
+ SD = _drawing()
401
+ w, h = int(args.get("width") or 1280), int(args.get("height") or 720)
402
+ bmp = SD.Bitmap(w, h)
403
+ g = SD.Graphics.FromImage(bmp)
404
+ try:
405
+ g.Clear(SD.Color.White)
406
+ mgr.GetObjectToRead(c.proj.handle, visu.guid).Object.Paint(g, SD.Rectangle(0, 0, w, h))
407
+ finally:
408
+ g.Dispose()
409
+ path = args["path"]
410
+ folder = os.path.dirname(path)
411
+ if folder and not os.path.isdir(folder):
412
+ os.makedirs(folder)
413
+ bmp.Save(path, SD.Imaging.ImageFormat.Png)
414
+ bmp.Dispose()
415
+ return {"path": path}
416
+
417
+
418
+ mcp_ops.OPS.update({
419
+ "visu.manager": op_visu_manager,
420
+ "visu.build": op_visu_build,
421
+ "visu.render": op_visu_render,
422
+ })
@@ -5,7 +5,8 @@
5
5
  # (with --show-window: no --noUI and CODESYS_MCP_UI=1. The same blocking loop runs: CODESYS
6
6
  # applies script_prompt_handling only while a script runs, so a timer that runs requests after
7
7
  # the script returned would show every prompt as a dialog. system.delay() serves the window's
8
- # message loop, so the window keeps painting while the loop waits.)
8
+ # message loop, so the window keeps painting while the loop waits. Objects that requests
9
+ # write or create open in an editor tab there; see mcp_ui.py.)
9
10
  # Environment (inherited from the server):
10
11
  # CODESYS_MCP_DIR session folder (requests/, results/, heartbeat.json, ready.json, watcher.log)
11
12
  # CODESYS_MCP_SCRIPTS folder holding this file and its modules
@@ -50,6 +51,24 @@ STATE = {"count": 0, "last_beat": 0.0, "busy_since": None, "op": None}
50
51
  # A dialog must stay up this long before the server is told, so brief windows don't count.
51
52
  DIALOG_GRACE_S = 10.0
52
53
 
54
+ # With the window shown (--show-window), these ops open the object they wrote in an editor.
55
+ UI = {"on": False}
56
+ FOLLOW_OPS = ("object.write", "object.create", "visu.build")
57
+
58
+
59
+ def follow_in_editor(ops_mod, result):
60
+ """Opens the object a request wrote in its editor, so the window shows the code."""
61
+ path = (result or {}).get("path")
62
+ if not path or (result or {}).get("kind") == "folder":
63
+ return
64
+ try:
65
+ import scriptengine as se
66
+ import mcp_ui
67
+ obj = ops_mod.Ctx().resolve(path)
68
+ log("editor %s: %s" % (mcp_ui.open_editor(se, obj), path))
69
+ except BaseException as e:
70
+ log("editor not opened for %s: %s" % (path, to_text(e)))
71
+
53
72
 
54
73
  def log(msg):
55
74
  try:
@@ -208,6 +227,8 @@ def handle(ops_mod, fname):
208
227
  else:
209
228
  try:
210
229
  res = {"id": rid, "ok": True, "result": fn(args)}
230
+ if UI["on"] and op in FOLLOW_OPS:
231
+ follow_in_editor(ops_mod, res["result"])
211
232
  except ops_mod.OpError as e:
212
233
  res = {"id": rid, "ok": False, "error": {"code": e.code, "message": to_text(e.message)}}
213
234
  except BaseException as e: # IronPython: KeyboardInterrupt is not an Exception
@@ -225,6 +246,7 @@ def handle(ops_mod, fname):
225
246
 
226
247
 
227
248
  def main(ui=False):
249
+ UI["on"] = ui
228
250
  for d in (REQ_DIR, RES_DIR):
229
251
  if not os.path.isdir(d):
230
252
  os.makedirs(d)
@@ -232,6 +254,12 @@ def main(ui=False):
232
254
  import scriptengine as se
233
255
  import mcp_ops
234
256
  import mcp_sim # noqa: F401 (adds its operations to mcp_ops.OPS)
257
+ import mcp_visu # noqa: F401 (adds the visualization operations)
258
+ import mcp_lang # noqa: F401 (adds signatures and cross-references)
259
+ import mcp_trace # noqa: F401 (adds trace recording; after mcp_visu)
260
+ import mcp_debug # noqa: F401 (adds breakpoints and stepping)
261
+ import mcp_devices # noqa: F401 (adds device search and add)
262
+ import mcp_alarms # noqa: F401 (adds alarm management and visu user groups)
235
263
  prompts = setup_prompts(se)
236
264
  log(prompts)
237
265
  if not hasattr(se, "FAKE"):
@@ -277,6 +305,12 @@ def main_attach():
277
305
  import scriptengine as se
278
306
  import mcp_ops
279
307
  import mcp_sim # noqa: F401 (adds its operations to mcp_ops.OPS)
308
+ import mcp_visu # noqa: F401 (adds the visualization operations)
309
+ import mcp_lang # noqa: F401 (adds signatures and cross-references)
310
+ import mcp_trace # noqa: F401 (adds trace recording; after mcp_visu)
311
+ import mcp_debug # noqa: F401 (adds breakpoints and stepping)
312
+ import mcp_devices # noqa: F401 (adds device search and add)
313
+ import mcp_alarms # noqa: F401 (adds alarm management and visu user groups)
280
314
  prompts = setup_prompts(se)
281
315
  log(prompts)
282
316
  old = getattr(__builtin__, "_mcp_attach_timer", None)