"""show_runnable_code_cell: a Jupyter notebook like code cell * Displays an editable code cell * A Run button * An indication (*) whether the code was modified since it was last run. * The result of the last run is displayed below the code cell (you can provide a custom renderer for the result) This is very much a work in progress. """ import sys import io from typing import Any, Callable from imgui_bundle import immapp, imgui_md, imgui, imgui_ctx, hello_imgui, ImVec2 import textwrap from dataclasses import dataclass class _NoResult: pass _NoResultValue = _NoResult() ResultRenderer = Callable[[Any], None] def _default_result_renderer(result: Any) -> None: as_string = str(result) md_string = "```\n" + as_string + "\n```" imgui_md.render(md_string) class _CaptureStdout(list[str]): def __enter__(self) -> "_CaptureStdout": self._stdout = sys.stdout sys.stdout = self._stringio = io.StringIO() return self def __exit__(self, *args: Any) -> None: self.extend(self._stringio.getvalue().splitlines()) sys.stdout = self._stdout def _execute_and_capture_last_expr(code: str) -> Any | _NoResult: with _CaptureStdout() as output: code = textwrap.dedent(code).strip() # De-indent and strip code lines = code.splitlines() if not lines: return None last_line = lines[-1] try: exec(code, globals(), locals()) # Execute the entire code block except Exception as e: return f"Error:\n{e}" try: last_line_result = eval(last_line, globals(), locals()) # Evaluate the last line except Exception: last_line_result = _NoResult() return last_line_result @dataclass class RunnableCodeCellResult: code: str = "" last_run_code: str = "" was_last_edited_code_ran: bool = False # simply indicates if last_run_code == code result: Any = _NoResultValue was_just_run: bool = False def show_runnable_code_cell(label_id: str, code: str = "", result_renderer: ResultRenderer | None = None) -> RunnableCodeCellResult: statics = show_runnable_code_cell @dataclass class CodeAndResult: snippet_data: immapp.snippets.SnippetData result: Any last_ran_code: str if not hasattr(statics, "s_code_cells"): statics.s_code_cells: dict[str, CodeAndResult] = {} # type: ignore if label_id not in statics.s_code_cells: snippet_data = immapp.snippets.SnippetData() snippet_data.code = code snippet_data.height_in_lines = code.count("\n") snippet_data.palette = immapp.snippets.SnippetTheme.dark statics.s_code_cells[label_id] = CodeAndResult(snippet_data, _NoResult(), "") code_and_result = statics.s_code_cells[label_id] runnnable_code_cell_result = RunnableCodeCellResult() with imgui_ctx.push_id(label_id): original_cur_pos = imgui.get_cursor_screen_pos() visible_label = label_id if "##" not in label_id else label_id.split("##")[0] imgui.separator_text(visible_label) # Add "Run" button at the end of the separator line cur_pos_after_separator = imgui.get_cursor_screen_pos() x_end = imgui.get_item_rect_max().x x_end = x_end - hello_imgui.em_size(3.5) imgui.set_cursor_screen_pos(ImVec2(x_end, original_cur_pos.y)) if imgui.button("Run"): code_and_result.result = _execute_and_capture_last_expr(code_and_result.snippet_data.code) code_and_result.last_ran_code = code_and_result.snippet_data.code runnnable_code_cell_result.was_just_run = True if code_and_result.last_ran_code != code_and_result.snippet_data.code: imgui.same_line() imgui.text("*") imgui.set_cursor_screen_pos(cur_pos_after_separator) immapp.snippets.show_editable_code_snippet(label_id, code_and_result.snippet_data) runnnable_code_cell_result.code = code_and_result.snippet_data.code runnnable_code_cell_result.last_run_code = code_and_result.last_ran_code runnnable_code_cell_result.result = code_and_result.result runnnable_code_cell_result.was_last_edited_code_ran = code_and_result.last_ran_code == code_and_result.snippet_data.code if not isinstance(code_and_result.result, _NoResult): if result_renderer is None: result_renderer = _default_result_renderer result_renderer(code_and_result.result) imgui.new_line() return runnnable_code_cell_result