From 07c44d143ce85ea19b2be6cbbe1863f117f5d90b Mon Sep 17 00:00:00 2001 From: "Lumberbot (aka Jack)" <39504233+meeseeksmachine@users.noreply.github.com> Date: Tue, 26 Nov 2024 08:52:22 -0800 Subject: [PATCH] Backport PR #521 on branch 3.x (PR: Add logic to handle traceback color configuration) (#523) --- spyder_kernels/console/kernel.py | 35 ++++-- spyder_kernels/console/shell.py | 14 +++ spyder_kernels/console/start.py | 3 - spyder_kernels/customize/code_runner.py | 6 +- spyder_kernels/customize/umr.py | 11 +- spyder_kernels/utils/style.py | 138 ++++++++++++++++++++++++ 6 files changed, 192 insertions(+), 15 deletions(-) create mode 100644 spyder_kernels/utils/style.py diff --git a/spyder_kernels/console/kernel.py b/spyder_kernels/console/kernel.py index ad16c084..391c7392 100644 --- a/spyder_kernels/console/kernel.py +++ b/spyder_kernels/console/kernel.py @@ -47,6 +47,7 @@ from spyder_kernels.utils.mpl import automatic_backend, MPL_BACKENDS_TO_SPYDER from spyder_kernels.utils.nsview import ( get_remote_data, make_remote_view, get_size) +from spyder_kernels.utils.style import create_style_class from spyder_kernels.console.shell import SpyderShell from spyder_kernels.comms.utils import WriteContext @@ -639,6 +640,8 @@ def set_configuration(self, conf): ret["special_kernel_error"] = value elif key == "color scheme": self.set_color_scheme(value) + elif key == "traceback_highlight_style": + self.set_traceback_syntax_highlighting(value) elif key == "jedi_completer": self.set_jedi_completer(value) elif key == "greedy_completer": @@ -657,13 +660,31 @@ def set_configuration(self, conf): return ret def set_color_scheme(self, color_scheme): - if color_scheme == "dark": - # Needed to change the colors of tracebacks - self.shell.run_line_magic("colors", "linux") - self.set_sympy_forecolor(background_color='dark') - elif color_scheme == "light": - self.shell.run_line_magic("colors", "lightbg") - self.set_sympy_forecolor(background_color='light') + self.shell.set_spyder_theme(color_scheme) + self.set_sympy_forecolor(background_color=color_scheme) + self.set_traceback_highlighting(color_scheme) + + def set_traceback_highlighting(self, color_scheme): + """Set the traceback highlighting color.""" + color = 'bg:ansired' if color_scheme == 'dark' else 'bg:ansiyellow' + from IPython.core.ultratb import VerboseTB + + if getattr(VerboseTB, 'tb_highlight', None) is not None: + VerboseTB.tb_highlight = color + elif getattr(VerboseTB, '_tb_highlight', None) is not None: + VerboseTB._tb_highlight = color + + def set_traceback_syntax_highlighting(self, syntax_style): + """Set the traceback syntax highlighting style.""" + import IPython.core.ultratb + from IPython.core.ultratb import VerboseTB + + IPython.core.ultratb.get_style_by_name = create_style_class + + if getattr(VerboseTB, 'tb_highlight_style', None) is not None: + VerboseTB.tb_highlight_style = syntax_style + elif getattr(VerboseTB, '_tb_highlight_style', None) is not None: + VerboseTB._tb_highlight_style = syntax_style def get_cwd(self): """Get current working directory.""" diff --git a/spyder_kernels/console/shell.py b/spyder_kernels/console/shell.py index 3e9224c9..0de22d43 100644 --- a/spyder_kernels/console/shell.py +++ b/spyder_kernels/console/shell.py @@ -56,6 +56,7 @@ def __init__(self, *args, **kwargs): self._allow_kbdint = False self.register_debugger_sigint() self.update_gui_frontend = False + self._spyder_theme = 'dark' # register post_execute self.events.register('post_execute', self.do_post_execute) @@ -84,6 +85,19 @@ def _showtraceback(self, etype, evalue, stb): stb = [''] super(SpyderShell, self)._showtraceback(etype, evalue, stb) + def set_spyder_theme(self, theme): + """Set the theme for the console.""" + self._spyder_theme = theme + if theme == "dark": + # Needed to change the colors of tracebacks + self.run_line_magic("colors", "linux") + elif theme == "light": + self.run_line_magic("colors", "lightbg") + + def get_spyder_theme(self): + """Get the theme for the console.""" + return self._spyder_theme + def enable_matplotlib(self, gui=None): """Enable matplotlib.""" if gui is None or gui.lower() == "auto": diff --git a/spyder_kernels/console/start.py b/spyder_kernels/console/start.py index 600b656c..b8a423af 100644 --- a/spyder_kernels/console/start.py +++ b/spyder_kernels/console/start.py @@ -69,9 +69,6 @@ def kernel_config(): # Don't load nor save history in our IPython consoles. spy_cfg.HistoryAccessor.enabled = False - # Until we implement Issue 1052 - spy_cfg.InteractiveShell.xmode = 'Plain' - # Jedi completer. jedi_o = os.environ.get('SPY_JEDI_O') == 'True' spy_cfg.IPCompleter.use_jedi = jedi_o diff --git a/spyder_kernels/customize/code_runner.py b/spyder_kernels/customize/code_runner.py index 9917abb9..8f3ac12c 100644 --- a/spyder_kernels/customize/code_runner.py +++ b/spyder_kernels/customize/code_runner.py @@ -143,12 +143,14 @@ class SpyderCodeRunner(Magics): Functions and magics related to code execution, debugging, profiling, etc. """ def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.show_global_msg = True self.show_invalid_syntax_msg = True self.umr = UserModuleReloader( - namelist=os.environ.get("SPY_UMR_NAMELIST", None) + namelist=os.environ.get("SPY_UMR_NAMELIST", None), + shell=self.shell, ) - super().__init__(*args, **kwargs) @runfile_arguments @needs_local_scope diff --git a/spyder_kernels/customize/umr.py b/spyder_kernels/customize/umr.py index e779ec33..c1105158 100644 --- a/spyder_kernels/customize/umr.py +++ b/spyder_kernels/customize/umr.py @@ -20,7 +20,7 @@ class UserModuleReloader: namelist [list]: blacklist in terms of module name """ - def __init__(self, namelist=None, pathlist=None): + def __init__(self, namelist=None, pathlist=None, shell=None): if namelist is None: namelist = [] else: @@ -45,6 +45,7 @@ def __init__(self, namelist=None, pathlist=None): self.namelist = namelist + spy_modules + mpl_modules + other_modules self.pathlist = pathlist + self._shell = shell # List of previously loaded modules self.previous_modules = list(sys.modules.keys()) @@ -92,7 +93,11 @@ def run(self): # Report reloaded modules if self.verbose and modnames_to_reload: modnames = modnames_to_reload - print("\x1b[4;33m%s\x1b[24m%s\x1b[0m" - % ("Reloaded modules", ": "+", ".join(modnames))) + colors = {"dark": "33", "light": "31"} + color = colors["dark"] + if self._shell: + color = colors[self._shell.get_spyder_theme()] + content = ": "+", ".join(modnames) + print(f"\x1b[4;{color}mReloaded modules\x1b[24m{content}\x1b[0m") return modnames_to_reload diff --git a/spyder_kernels/utils/style.py b/spyder_kernels/utils/style.py new file mode 100644 index 00000000..255ce959 --- /dev/null +++ b/spyder_kernels/utils/style.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright (c) 2009- Spyder Kernels Contributors +# +# Licensed under the terms of the MIT License +# (see spyder_kernels/__init__.py for details) +# ----------------------------------------------------------------------------- + +""" +Style for IPython Console +""" + +# Third party imports +from pygments.style import Style +from pygments.token import ( + Name, + Keyword, + Comment, + String, + Number, + Punctuation, + Operator, +) + + +def create_pygments_dict(color_scheme_dict): + """ + Create a dictionary that saves the given color scheme as a + Pygments style. + """ + + def give_font_weight(is_bold): + if is_bold: + return "bold" + else: + return "" + + def give_font_style(is_italic): + if is_italic: + return "italic" + else: + return "" + + color_scheme = color_scheme_dict + + fon_c, fon_fw, fon_fs = color_scheme["normal"] + font_color = fon_c + font_font_weight = give_font_weight(fon_fw) + font_font_style = give_font_style(fon_fs) + + key_c, key_fw, key_fs = color_scheme["keyword"] + keyword_color = key_c + keyword_font_weight = give_font_weight(key_fw) + keyword_font_style = give_font_style(key_fs) + + bui_c, bui_fw, bui_fs = color_scheme["builtin"] + builtin_color = bui_c + builtin_font_weight = give_font_weight(bui_fw) + builtin_font_style = give_font_style(bui_fs) + + str_c, str_fw, str_fs = color_scheme["string"] + string_color = str_c + string_font_weight = give_font_weight(str_fw) + string_font_style = give_font_style(str_fs) + + num_c, num_fw, num_fs = color_scheme["number"] + number_color = num_c + number_font_weight = give_font_weight(num_fw) + number_font_style = give_font_style(num_fs) + + com_c, com_fw, com_fs = color_scheme["comment"] + comment_color = com_c + comment_font_weight = give_font_weight(com_fw) + comment_font_style = give_font_style(com_fs) + + def_c, def_fw, def_fs = color_scheme["definition"] + definition_color = def_c + definition_font_weight = give_font_weight(def_fw) + definition_font_style = give_font_style(def_fs) + + ins_c, ins_fw, ins_fs = color_scheme["instance"] + instance_color = ins_c + instance_font_weight = give_font_weight(ins_fw) + instance_font_style = give_font_style(ins_fs) + + font_token = font_font_style + " " + font_font_weight + " " + font_color + definition_token = ( + definition_font_style + + " " + + definition_font_weight + + " " + + definition_color + ) + builtin_token = ( + builtin_font_style + " " + builtin_font_weight + " " + builtin_color + ) + instance_token = ( + instance_font_style + " " + instance_font_weight + " " + instance_color + ) + keyword_token = ( + keyword_font_style + " " + keyword_font_weight + " " + keyword_color + ) + comment_token = ( + comment_font_style + " " + comment_font_weight + " " + comment_color + ) + string_token = ( + string_font_style + " " + string_font_weight + " " + string_color + ) + number_token = ( + number_font_style + " " + number_font_weight + " " + number_color + ) + + syntax_style_dic = { + Name: font_token.strip(), + Name.Class: definition_token.strip(), + Name.Function: definition_token.strip(), + Name.Builtin: builtin_token.strip(), + Name.Builtin.Pseudo: instance_token.strip(), + Keyword: keyword_token.strip(), + Keyword.Type: builtin_token.strip(), + Comment: comment_token.strip(), + String: string_token.strip(), + Number: number_token.strip(), + Punctuation: font_token.strip(), + Operator.Word: keyword_token.strip(), + } + + return syntax_style_dic + + +def create_style_class(color_scheme_dict): + """Create a Pygments Style class with the given color scheme.""" + + class StyleClass(Style): + default_style = "" + styles = create_pygments_dict(color_scheme_dict) + + return StyleClass