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

PR: Enhance the display of warnings and errors #9422

Merged
merged 10 commits into from May 30, 2019
50 changes: 34 additions & 16 deletions spyder/plugins/editor/panels/linenumber.py
Expand Up @@ -38,10 +38,13 @@ def __init__(self, editor):

# Markers
self._markers_margin = True
self._markers_margin_width = 15
self.error_pixmap = ima.icon('error').pixmap(QSize(14, 14))
self.warning_pixmap = ima.icon('warning').pixmap(QSize(14, 14))
self.todo_pixmap = ima.icon('todo').pixmap(QSize(14, 14))

# Icons
self.error_icon = ima.icon('error')
self.warning_icon = ima.icon('warning')
self.info_icon = ima.icon('information')
self.hint_icon = ima.icon('hint')
self.todo_icon = ima.icon('todo')

# Line number area management
self._margin = True
Expand All @@ -68,13 +71,13 @@ def paintEvent(self, event):
active_block = self.editor.textCursor().block()
active_line_number = active_block.blockNumber() + 1

def draw_pixmap(ytop, pixmap):
def draw_pixmap(xleft, ytop, pixmap):
if not QT55_VERSION:
pixmap_height = pixmap.height()
else:
# scale pixmap height to device independent pixels
pixmap_height = pixmap.height() / pixmap.devicePixelRatio()
painter.drawPixmap(0, ytop + (font_height-pixmap_height) / 2,
painter.drawPixmap(xleft, ytop + (font_height-pixmap_height) / 2,
pixmap)

for top, line_number, block in self.editor.visible_blocks:
Expand All @@ -93,19 +96,33 @@ def draw_pixmap(ytop, pixmap):
Qt.AlignRight | Qt.AlignBottom,
to_text_string(line_number))

size = self.get_markers_margin() - 2
icon_size = QSize(size, size)

data = block.userData()
if self._markers_margin and data:
if data.code_analysis:
for source, code, severity, message in data.code_analysis:
error = severity == DiagnosticSeverity.ERROR
if error:
break
if error:
draw_pixmap(top, self.error_pixmap)
else:
draw_pixmap(top, self.warning_pixmap)
errors = 0
warnings = 0
infos = 0
hints = 0
for _, _, sev, _ in data.code_analysis:
errors += sev == DiagnosticSeverity.ERROR
warnings += sev == DiagnosticSeverity.WARNING
infos += sev == DiagnosticSeverity.INFORMATION
hints += sev == DiagnosticSeverity.HINT

if errors:
draw_pixmap(1, top, self.error_icon.pixmap(icon_size))
elif warnings:
draw_pixmap(1, top, self.warning_icon.pixmap(icon_size))
elif infos:
draw_pixmap(1, top, self.info_icon.pixmap(icon_size))
elif hints:
draw_pixmap(1, top, self.hint_icon.pixmap(icon_size))

if data.todo:
draw_pixmap(top, self.todo_pixmap)
draw_pixmap(1, top, self.todo_icon.pixmap(icon_size))

def leaveEvent(self, event):
"""Override Qt method."""
Expand Down Expand Up @@ -173,7 +190,8 @@ def compute_width(self):

def get_markers_margin(self):
if self._markers_margin:
return self._markers_margin_width
font_height = self.editor.fontMetrics().height() + 2
return font_height
else:
return 0

Expand Down
60 changes: 46 additions & 14 deletions spyder/plugins/editor/widgets/codeeditor.py
Expand Up @@ -534,16 +534,18 @@ def _handle_hover(self):
uri, cursor = self.get_uri_at(pos)
text = self.get_word_at(pos)
if uri:
ctrl_text = ' Cmd' if sys.platform == "darwin" else ' Ctrl'
ctrl_text = 'Cmd' if sys.platform == "darwin" else 'Ctrl'

if uri.startswith('file://'):
hint_text = ctrl_text + ' + click to open file \n'
hint_text = ctrl_text + ' + click to open file'
elif uri.startswith('mailto:'):
hint_text = ctrl_text + ' + click to send email \n'
hint_text = ctrl_text + ' + click to send email'
elif uri.startswith('http'):
hint_text = ctrl_text + ' + click to open url \n'
hint_text = ctrl_text + ' + click to open url'
else:
hint_text = ctrl_text + ' + click to open \n'
hint_text = ctrl_text + ' + click to open'

hint_text = '<span>&nbsp;{}&nbsp;</span>'.format(hint_text)

self.show_tooltip(text=hint_text, at_point=pos)
return
Expand Down Expand Up @@ -1857,18 +1859,48 @@ def hide_tooltip(self):

def show_code_analysis_results(self, line_number, block_data):
"""Show warning/error messages."""
from spyder.config.base import get_image_path
# Diagnostic severity
icons = {
DiagnosticSeverity.ERROR: 'error',
DiagnosticSeverity.WARNING: 'warning',
DiagnosticSeverity.INFORMATION: 'information',
DiagnosticSeverity.HINT: 'hint',
}

code_analysis = block_data.code_analysis
template = '{0} [{1}]: {2}'
msglist = [template.format(src, code, msg) for src, code, _sev, msg
in code_analysis]

self.show_tooltip(
title=_("Code analysis"),
text='\n'.join(msglist),
title_color='#129625',
at_line=line_number,
# Size must be adapted from font
fm = self.fontMetrics()
size = fm.height()
template = (
'<img src="data:image/png;base64, {}"'
' height="{size}" width="{size}" />&nbsp;'
'{} <i>({} {})</i>'
)
self.highlight_line_warning(block_data)

msglist = []
sorted_code_analysis = sorted(code_analysis, key=lambda i: i[2])
for src, code, sev, msg in sorted_code_analysis:
if '[' in msg and ']' in msg:
# Remove extra redundant info from pyling messages
msg = msg.split(']')[-1]

msg = msg.strip()
# Avoid messing TODO, FIXME
msg = msg[0].upper() + msg[1:]
base_64 = ima.base64_from_icon(icons[sev], size, size)
msglist.append(template.format(base_64, msg, src,
code, size=size))

if msglist:
self.show_tooltip(
title=_("Code analysis"),
text='\n'.join(msglist),
title_color='#129625',
at_line=line_number,
)
self.highlight_line_warning(block_data)

def highlight_line_warning(self, block_data):
self.clear_extra_selections('code_analysis')
Expand Down
16 changes: 15 additions & 1 deletion spyder/utils/icon_manager.py
Expand Up @@ -5,13 +5,15 @@
# (see spyder/__init__.py for details)

# Standard library imports
import base64
import os
import os.path as osp
import mimetypes as mime
import sys

# Third party imports
from qtpy.QtGui import QIcon
from qtpy.QtCore import QBuffer, QByteArray
from qtpy.QtGui import QIcon, QImage
from qtpy.QtWidgets import QStyle, QWidget

# Local imports
Expand Down Expand Up @@ -161,6 +163,8 @@
'gotoline': [('fa.sort-numeric-asc',), {'color': MAIN_FG_COLOR}],
'error': [('fa.times-circle',), {'color': 'darkred'}],
'warning': [('fa.warning',), {'color': 'orange'}],
'information': [('fa.info-circle',), {'color': '#3775a9'}],
'hint': [('fa.lightbulb-o',), {'color': 'yellow'}],
goanpeca marked this conversation as resolved.
Show resolved Hide resolved
'todo': [('fa.exclamation',), {'color': '#3775a9'}],
'ipython_console': [('spyder.ipython-logo-alt',), {'color': MAIN_FG_COLOR}],
'ipython_console_t': [('spyder.ipython-logo-alt',), {'color':'gray'}],
Expand Down Expand Up @@ -464,3 +468,13 @@ def get_icon_by_extension(fname, scale_factor):
icon_by_extension = icon(
application_icons[bin_name], scale_factor)
return icon_by_extension


def base64_from_icon(icon_name, width, height):
"""Convert icon to base64 encoding."""
icon_obj = icon(icon_name)
image = QImage(icon_obj.pixmap(width, height).toImage())
byte_array = QByteArray()
buffer = QBuffer(byte_array)
image.save(buffer, "PNG")
return byte_array.toBase64().data().decode()
10 changes: 7 additions & 3 deletions spyder/widgets/mixins.py
Expand Up @@ -204,7 +204,6 @@ def _format_text(self, title=None, signature=None, text=None,
lines = text.split('\n')
if len(lines) > max_lines:
text = '\n'.join(lines[:max_lines]) + ' ...'
text = '\n' + text

text = text.replace('\n', '<br>')
template += BASE_TEMPLATE.format(
Expand All @@ -217,8 +216,13 @@ def _format_text(self, title=None, signature=None, text=None,
help_text = ''
if inspect_word:
if display_link:
help_text = ('Click anywhere in this tooltip for '
'additional help')
help_text = (
'<span style="font-size:{size}pt;">'
'Click anywhere in this tooltip for additional help'
'</span>'.format(
size=title_size + 1,
)
)
else:
shortcut = self._get_inspect_shortcut()
if shortcut:
Expand Down