From 97577fec0d4dcd3faea494a61dd9bd60d8f54166 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sat, 7 Dec 2024 23:14:29 +0700 Subject: [PATCH] Allow view-annotate to display in Sage notebook --- src/sage/misc/cython.py | 21 +++++++++++-- src/sage/repl/ipython_extension.py | 48 ++++++++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/sage/misc/cython.py b/src/sage/misc/cython.py index 8364a6badc8..a7f684b368c 100644 --- a/src/sage/misc/cython.py +++ b/src/sage/misc/cython.py @@ -81,7 +81,7 @@ def _standard_libs_libdirs_incdirs_aliases(): def cython(filename, verbose=0, compile_message=False, use_cache=False, create_local_c_file=False, annotate=True, view_annotate=False, - sage_namespace=True, create_local_so_file=False): + view_annotate_callback=webbrowser.open, sage_namespace=True, create_local_so_file=False): r""" Compile a Cython file. This converts a Cython file to a C (or C++ file), and then compiles that. The .c file and the .so file are @@ -114,6 +114,11 @@ def cython(filename, verbose=0, compile_message=False, - ``view_annotate`` -- boolean (default: ``False``); if ``True``, open the annotated html file in a web browser using :func:`webbrowser.open` + - ``view_annotate_callback`` -- function (default: ``webbrowser.open``); a function + that takes a string being the path to the html file. This can be overridden to + change what to do with the annotated html file. Have no effect unless + ``view_annotate`` is ``True`` + - ``sage_namespace`` -- boolean (default: ``True``); if ``True``, import ``sage.all`` @@ -258,6 +263,18 @@ def cython(filename, verbose=0, compile_message=False, Traceback (most recent call last): ... ValueError: Cannot view annotated file without creating it + + :: + + sage: collected_paths = [] + sage: cython(''' + ....: def f(int n): + ....: return n*n + ....: ''', view_annotate=True, view_annotate_callback=collected_paths.append) + sage: collected_paths + ['...'] + sage: len(collected_paths) + 1 """ if not filename.endswith('pyx'): print("Warning: file (={}) should have extension .pyx".format(filename), file=sys.stderr) @@ -416,7 +433,7 @@ def cython(filename, verbose=0, compile_message=False, if view_annotate: if not annotate: raise ValueError("Cannot view annotated file without creating it") - webbrowser.open(os.path.join(target_dir, name + ".html")) + view_annotate_callback(os.path.join(target_dir, name + ".html")) # This emulates running "setup.py build" with the correct options # diff --git a/src/sage/repl/ipython_extension.py b/src/sage/repl/ipython_extension.py index 6cc08a7dbb5..29f36af8007 100644 --- a/src/sage/repl/ipython_extension.py +++ b/src/sage/repl/ipython_extension.py @@ -65,6 +65,8 @@ """ from IPython.core.magic import Magics, magics_class, line_magic, cell_magic +from IPython.core.display import HTML +from IPython import get_ipython from sage.repl.load import load_wrap from sage.env import SAGE_IMPORTALL, SAGE_STARTUP_FILE @@ -72,6 +74,14 @@ from sage.misc.misc import run_once +def _running_in_notebook(): + try: + from ipykernel.zmqshell import ZMQInteractiveShell + except ImportError: + return False + return isinstance(get_ipython(), ZMQInteractiveShell) + + @magics_class class SageMagics(Magics): @@ -356,6 +366,16 @@ def cython(self, line, cell): See :func:`~sage.misc.cython.cython` for details. + For ``--view-annotate``, the following additional choices are allowed: + + - ``--view-annotate=none`` (default) + - ``--view-annotate=auto`` (same as ``--view-annotate``); select one of the + choices below automatically + - ``--view-annotate=webbrowser``; open the annotation in a web browser + (preferred if the Sage command line is used) + - ``--view-annotate=displayhtml``; display the annotation inline in the notebook + (preferred if the Sage notebook is used) + - ``cell`` -- string; the Cython source code to process OUTPUT: none; the Cython code is compiled and loaded @@ -396,6 +416,15 @@ def cython(self, line, cell): ....: ''') UsageError: unrecognized arguments: --help + Test ``--view-annotate`` invalid arguments:: + + sage: # needs sage.misc.cython + sage: shell.run_cell(''' + ....: %%cython --view-annotate=xx + ....: print(1) + ....: ''') + UsageError: argument --view-annotate: invalid choice: 'xx' (choose from 'none', 'auto', 'webbrowser', 'displayhtml') + Test invalid quotes (see :mod:`sage.repl.interpreter` for explanation of the dummy line):: sage: # needs sage.misc.cython @@ -428,11 +457,26 @@ def error(self, message): parser.add_argument("--use-cache", action=argparse.BooleanOptionalAction) parser.add_argument("--create-local-c-file", action=argparse.BooleanOptionalAction) parser.add_argument("--annotate", action=argparse.BooleanOptionalAction) - parser.add_argument("--view-annotate", action=argparse.BooleanOptionalAction) + parser.add_argument("--view-annotate", choices=["none", "auto", "webbrowser", "displayhtml"], + nargs="?", const="auto", default="none") parser.add_argument("--sage-namespace", action=argparse.BooleanOptionalAction) parser.add_argument("--create-local-so-file", action=argparse.BooleanOptionalAction) args = parser.parse_args(shlex.split(line)) - return cython_compile(cell, **{k: v for k, v in args.__dict__.items() if v is not None}) + view_annotate = args.view_annotate + del args.view_annotate + if view_annotate == "auto": + if _running_in_notebook(): + view_annotate = "displayhtml" + else: + view_annotate = "webbrowser" + args_dict = {k: v for k, v in args.__dict__.items() if v is not None} + if view_annotate != "none": + args_dict["view_annotate"] = True + if view_annotate == "displayhtml": + path_to_annotate_html_container = [] + cython_compile(cell, **args_dict, view_annotate_callback=path_to_annotate_html_container.append) + return HTML(filename=path_to_annotate_html_container[0]) + return cython_compile(cell, **args_dict) @cell_magic def fortran(self, line, cell):