Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use selected syntax style for tracebacks and improve ANSI color codes support #608

Merged
merged 16 commits into from
Aug 12, 2024
Merged
31 changes: 26 additions & 5 deletions qtconsole/ansi_code_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,31 @@ def _replace_special(self, match):
self.actions.append(ScrollAction('scroll', 'down', 'page', 1))
return ''

def _parse_ansi_color(self, color, intensity):
"""
Map an ANSI color code to color name or a RGB tuple.
Based on: https://gist.github.com/MightyPork/1d9bd3a3fd4eb1a661011560f6921b5b
"""
parsed_color = None
if color < 16:
# Adjust for intensity, if possible.
if intensity > 0:
jsbautista marked this conversation as resolved.
Show resolved Hide resolved
color += 8
parsed_color = self.color_map.get(color, None)
elif (color > 231):
s = int((color - 232) * 10 + 8)
parsed_color = (s, s, s)
else:
n = color - 16
b = n % 6
g = (n - b) / 6 % 6
r = (n - b - g * 6) / 36 % 6
r = int(r * 40 + 55) if r else 0
g = int(g * 40 + 55) if g else 0
b = int(b * 40 + 55) if b else 0
parsed_color = (r, g, b)
return parsed_color


class QtAnsiCodeProcessor(AnsiCodeProcessor):
""" Translates ANSI escape codes into QTextCharFormats.
Expand Down Expand Up @@ -323,12 +348,8 @@ def get_color(self, color, intensity=0):
""" Returns a QColor for a given color code or rgb list, or None if one
cannot be constructed.
"""

if isinstance(color, int):
# Adjust for intensity, if possible.
if color < 8 and intensity > 0:
color += 8
constructor = self.color_map.get(color, None)
constructor = self._parse_ansi_color(color, intensity)
elif isinstance(color, (tuple, list)):
constructor = color
else:
Expand Down
7 changes: 6 additions & 1 deletion qtconsole/mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,7 @@ def set_syntax_style(self, syntax_style):
colors='nocolor'
elif styles.dark_style(syntax_style):
colors='linux'

else:
colors='lightbg'
self.active_frontend.syntax_style = syntax_style
Expand All @@ -809,7 +810,11 @@ def set_syntax_style(self, syntax_style):
self.active_frontend._syntax_style_changed()
self.active_frontend._style_sheet_changed()
self.active_frontend.reset(clear=True)
self.active_frontend._execute("%colors linux", True)
self.active_frontend._execute(
f"from IPython.core.ultratb import VerboseTB;"
"VerboseTB._tb_highlight_style = '{syntax_style}'",
Copy link
Collaborator

@ccordoba12 ccordoba12 Jun 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of comments about this change:

  • The _tb_highlight_style attribute was added in IPython 8.15 (see Add hack to change traceback syntax highlighting ipython/ipython#14138). So, it's necessary a check that executes the new code for that or more recent versions, and leaves the previous one for older versions.
  • It'd be a good idea to make _tb_highlight_style a public attribute of VerboseTB by submitting a PR to IPython. That would require an additional check here but it'd prevent they breaking us in the future.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _tb_highlight_style attribute was added in IPython 8.15 (see ipython/ipython#14138). So, it's necessary a check that executes the new code for that or more recent versions, and leaves the previous one for older versions.

That's quite a good point! Thinking about that, and the fact that this way of customizing things is based on a hack, I would say that we should implement the validation using some sort of fallback approach rather than checking the package version (maybe at some point that hack is removed in favor of an actual public method for customization). Maybe we could check if the attribute is available and depending on that use it or fallback to the previous code that uses the magic?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think something like this could work:

Suggested change
f"from IPython.core.ultratb import VerboseTB;"
"VerboseTB._tb_highlight_style = '{syntax_style}'",
f"""
from IPython.core.ultratb import VerboseTB
try:
VerboseTB._tb_highlight_style = '{syntax_style}'
except AttributeError:
get_ipython().run_line_magic('colors', '{colors}')
""",

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very good approach, thanks @dalthviz! However, given that _tb_highlight_style is a private attribute of VerboseTB, I think it'd be a good idea to promote it to a public one in IPython and let Matthias know that we're using it here.

Otherwise, this functionality could break easily without us being aware of it (given that now there's a fallback in case that attribute is no longer available).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created ipython/ipython#14464 to check your suggestion @ccordoba12

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, thanks! I saw Matthias answer, so now please proceed to implement his suggestions there.

True)


def close_active_frontend(self):
self.close_tab(self.active_frontend)
Expand Down
2 changes: 1 addition & 1 deletion qtconsole/tests/test_jupyter_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def test_stylesheet_changed(self):
w = JupyterWidget(kind='rich')

# By default, the background is light. White text is rendered as black
self.assertEqual(w._ansi_processor.get_color(15).name(), '#000000')
self.assertEqual(w._ansi_processor.get_color(15).name(), '#ffffff')
dalthviz marked this conversation as resolved.
Show resolved Hide resolved

# Change to a dark colorscheme. White text is rendered as white
w.syntax_style = 'monokai'
Expand Down
Loading