diff --git a/mfp/evaluator.py b/mfp/evaluator.py index d4a87bd..7deacde 100644 --- a/mfp/evaluator.py +++ b/mfp/evaluator.py @@ -127,13 +127,37 @@ def lazyrecurse(evalstr): return rv def exec_str(self, pystr, global_vars=None): - if global_vars is not None: - for name, value in self.global_names.items(): - if name not in global_vars: - global_vars[name] = value - exec(pystr, global_vars) - else: - exec(pystr, self.global_names) + try: + if global_vars is not None: + for name, value in self.global_names.items(): + if name not in global_vars: + global_vars[name] = value + exec(pystr, global_vars) + else: + exec(pystr, self.global_names) + except SyntaxError as syn: + return dict( + lineno=syn.lineno, + offset=syn.offset, + code=syn.text, + message=str(syn) + ) + except NameError as name_err: + import sys + _, _, exc_tb = sys.exc_info() + return dict( + lineno=exc_tb.tb_next.tb_lineno, + offset=0, + code=name_err.name, + message=str(name_err) + ) + except Exception as e: + return dict( + lineno=1, + offset=0, + code="", + message=str(e) + ) def exec_file(self, filename): import os.path diff --git a/mfp/gui/base_element.py b/mfp/gui/base_element.py index d517cb5..8affa98 100644 --- a/mfp/gui/base_element.py +++ b/mfp/gui/base_element.py @@ -393,17 +393,24 @@ async def create(self, obj_type, init_args): self.obj_type = obj_type self.obj_args = init_args + self.tags = {} + self.update_badge() objinfo = await MFPGUI().mfp.create(obj_type, init_args, patchname, scopename, name, self.synced_params()) - if self.layer is not None and objinfo: - objinfo["layername"] = self.layer.name - - if objinfo is None: + if not objinfo or "obj_id" not in objinfo: self.app_window.hud_write("ERROR: Could not create, see log for details") + + if objinfo and "code" in objinfo: + self.code = objinfo["code"] + self.connections_out = connections_out self.connections_in = connections_in + self.tags['errorcount'] = 1 + self.update_badge() return None + if self.layer is not None and objinfo and isinstance(objinfo, dict): + objinfo["layername"] = self.layer.name objinfo['obj_type'] = obj_type # init state from objinfo @@ -562,7 +569,7 @@ def port_alloc(self): @mutates( 'num_inlets', 'num_outlets', 'dsp_inlets', 'dsp_outlets', 'obj_name', 'no_export', 'is_export', 'export_offset_x', - 'export_offset_y', 'debug', 'layer' + 'export_offset_y', 'debug', 'layer', 'code' ) async def configure(self, params): self.num_inlets = params.get("num_inlets", 0) @@ -575,6 +582,7 @@ async def configure(self, params): self.export_offset_x = params.get("export_offset_x", 0) self.export_offset_y = params.get("export_offset_y", 0) self.debug = params.get("debug", False) + self.code = params.get("code", None) newscope = params.get("scope", "__patch__") if (not self.scope) or newscope != self.scope: diff --git a/mfp/gui/imgui/app_window/info_panel.py b/mfp/gui/imgui/app_window/info_panel.py index 891f375..9087524 100644 --- a/mfp/gui/imgui/app_window/info_panel.py +++ b/mfp/gui/imgui/app_window/info_panel.py @@ -34,9 +34,12 @@ TAB_PADDING_Y = 4 open_code_editors = {} +focused_editor = None def open_code_editor(app_window, param_name, param_type, param_value, target): + global focused_editor + def _ensure_editor(): editor = target.imgui_code_editor if not editor: @@ -51,9 +54,11 @@ def _ensure_editor(): return editor editor = _ensure_editor() open_code_editors[target] = (editor, param_name) + focused_editor = target def render_code_editors(app_window): + global focused_editor to_close = [] for target, info in open_code_editors.items(): editor, param_name = info @@ -66,6 +71,10 @@ def render_code_editors(app_window): (350, 400), cond=imgui.Cond_.once ) + if focused_editor == target: + imgui.set_next_window_focus() + focused_editor = None + _, window_open = imgui.begin( f"{label}##code_editor", True, @@ -83,6 +92,7 @@ def render_code_editors(app_window): new_val = target.code or {} new_val["body"] = editor.get_text() new_val["lang"] = "python" + new_val["errorinfo"] = None MFPGUI().async_task(target.dispatch_setter(param_name, new_val)) selected, _ = imgui.menu_item("Close", "", False) @@ -100,6 +110,14 @@ def render_code_editors(app_window): imgui.pop_style_var() imgui.pop_style_var() + if target.code and "errorinfo" in target.code and target.code["errorinfo"]: + errinfo = target.code["errorinfo"] + editor.set_error_markers( + { errinfo["lineno"]: errinfo["message"]} + ) + else: + editor.set_error_markers({}) + editor.render(label) imgui.end() diff --git a/mfp/gui/modes/global_mode.py b/mfp/gui/modes/global_mode.py index 8b6481d..9f57bdf 100644 --- a/mfp/gui/modes/global_mode.py +++ b/mfp/gui/modes/global_mode.py @@ -381,7 +381,7 @@ async def hover(self, details): o = self.manager.pointer_obj try: - if o is not None and o.obj_state == BaseElement.OBJ_COMPLETE: + if not self.snoop_conn and o is not None and o.obj_state == BaseElement.OBJ_COMPLETE: await o.show_tip(self.manager.pointer_x, self.manager.pointer_y, details) except Exception: pass diff --git a/mfp/gui/processor_element.py b/mfp/gui/processor_element.py index d5f2f7d..76cda18 100644 --- a/mfp/gui/processor_element.py +++ b/mfp/gui/processor_element.py @@ -106,7 +106,7 @@ async def recreate_element(self, action, state_diff, previous): ) @saga('code') - def params_changed(self, action, state_diff, previous): + async def params_changed(self, action, state_diff, previous): self.send_params() async def label_edit_finish(self, widget, text=None, aborted=False): diff --git a/mfp/mfp_app.py b/mfp/mfp_app.py index f05b949..4dc6d3e 100644 --- a/mfp/mfp_app.py +++ b/mfp/mfp_app.py @@ -398,6 +398,9 @@ def load_extension(self, libname): log.warning(f"mfp_app.load_extension: not implemented completely, path={fullpath}") async def create(self, init_type, init_args, patch, scope, name, params=None): + create_params = {} + rval = None + # first try: is a factory registered? ctor = self.registry.get(init_type) @@ -407,7 +410,10 @@ async def create(self, init_type, init_args, patch, scope, name, params=None): code = params.get("code") if code and code.get("lang") == "python": codestr = code.get("body") - patch.evaluator.exec_str(codestr, defs) + errinfo = patch.evaluator.exec_str(codestr, defs) + if errinfo: + create_params["code"] = dict(body=codestr, lang="python", errorinfo=errinfo) + rval = create_params # second try: is there a .mfp patch file in the search path? if ctor is None: @@ -430,14 +436,14 @@ async def create(self, init_type, init_args, patch, scope, name, params=None): if ctor is None: log.error(f"[create] Could not find a build method for {init_type}") - return None + return rval # create intervening scope if needed if '.' in name: parts = name.split('.') if len(parts) > 2: log.error("Cannot deep-create name {}".format(name)) - return None + return rval testobj = self.resolve(parts[0], patch, True) if testobj: @@ -447,12 +453,12 @@ async def create(self, init_type, init_args, patch, scope, name, params=None): log.error( "Cannot deep-create object {} in patch {}".format(name, testobj) ) - return None + return rval elif not isinstance(scope, LexicalScope): log.error( "Cannot deep-create object {} in another object {}".format(name, testobj) ) - return None + return rval else: scope = testobj else: @@ -473,6 +479,10 @@ async def create(self, init_type, init_args, patch, scope, name, params=None): if obj and obj.obj_id: await obj.setup() + + for attr, val in create_params.items(): + obj.gui_params[attr] = val + obj.mark_ready() return obj diff --git a/mfp/mfp_command.py b/mfp/mfp_command.py index 6c61586..89abad9 100644 --- a/mfp/mfp_command.py +++ b/mfp/mfp_command.py @@ -25,6 +25,9 @@ async def create(self, objtype, initargs, patch_name, scope_name, obj_name, para if obj is None: log.warning(f"Failed to create object {objtype} {initargs} {patch} {scope} {obj_name}") return None + elif isinstance(obj, dict): + log.warning(f"Error in creating {objtype} {obj}") + return obj return obj.gui_params async def create_export_gui(self, obj_id):