Skip to content

Commit

Permalink
use treewidget
Browse files Browse the repository at this point in the history
  • Loading branch information
Quentin Peter committed Jan 17, 2020
1 parent bad1990 commit 7d25ba0
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 53 deletions.
17 changes: 0 additions & 17 deletions spyder/plugins/framesexplorer/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from spyder.plugins.framesexplorer.widgets.framesbrowser import (
FramesBrowser)
from spyder.plugins.framesexplorer.confpage import FramesExplorerConfigPage
from spyder.utils.misc import get_error_match
from spyder.py3compat import to_text_string
from spyder.plugins.ipythonconsole.utils.style import create_qss_style

Expand Down Expand Up @@ -117,8 +116,6 @@ def add_shellwidget(self, shellwidget):
self.add_widget(fsb)
self.shellwidgets[shellwidget_id] = fsb
self.set_shellwidget_from_id(shellwidget_id)
# For tracebacks
fsb.text_edit._control.go_to_error.connect(self.go_to_error)
return fsb

def remove_shellwidget(self, shellwidget_id):
Expand All @@ -134,20 +131,6 @@ def set_shellwidget_from_id(self, shellwidget_id):
fsb = self.shellwidgets[shellwidget_id]
self.set_current_widget(fsb)

def go_to_error(self, text):
"""Go to error if relevant"""
match = get_error_match(to_text_string(text))
if match:
fname, lnb = match.groups()
if ("<ipython-input-" in fname and
self.run_cell_filename is not None):
fname = self.run_cell_filename
# This is needed to fix issue spyder-ide/spyder#9217.
try:
self.edit_goto.emit(osp.abspath(fname), int(lnb), '')
except ValueError:
pass

# ----- SpyderPluginWidget API --------------------------------------------
def get_plugin_title(self):
"""Return widget title"""
Expand Down
229 changes: 193 additions & 36 deletions spyder/plugins/framesexplorer/widgets/framesbrowser.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@
This is the main widget used in the Frames Explorer plugin
"""
import os.path as osp
from IPython.core.ultratb import ListTB
from qtconsole.console_widget import ConsoleWidget
import html

# Third library imports (qtpy)
from qtpy.QtCore import Signal
from qtpy.QtWidgets import (QHBoxLayout, QMenu, QWidget)
from qtpy.QtGui import QAbstractTextDocumentLayout, QTextDocument
from qtpy.QtCore import (QSize, Qt, Slot)
from qtpy.QtWidgets import (QApplication, QStyle,
QStyledItemDelegate, QStyleOptionViewItem,
QTreeWidgetItem)

# Local imports
from spyder.config.base import _
Expand All @@ -26,6 +31,12 @@
MENU_SEPARATOR)
from spyder.plugins.ipythonconsole.widgets import (
ControlWidget, PageControlWidget)
from spyder.widgets.onecolumntree import OneColumnTree
from spyder.config.gui import get_font


ON = 'on'
OFF = 'off'


class FramesBrowser(QWidget):
Expand All @@ -39,7 +50,7 @@ def __init__(self, parent, options_button=None, plugin_actions=[],

self.shellwidget = None
self.exclude_internal = True
self.text_edit = None
self.results_browser = None
self.options_button = options_button
self.actions = None
self.plugin_actions = plugin_actions
Expand All @@ -52,11 +63,12 @@ def setup(self, exclude_internal=None):
"""
assert self.shellwidget is not None

if self.text_edit is not None:
if self.results_browser is not None:
self.refresh()
return

self.text_edit = FramesEdit()
self.results_browser = ResultsBrowser(self)
self.results_browser.sig_edit_goto.connect(self.edit_goto)

# Setup toolbar layout.

Expand All @@ -69,7 +81,7 @@ def setup(self, exclude_internal=None):
self.setup_options_button()

# Setup layout.
layout = create_plugin_layout(self.tools_layout, self.text_edit)
layout = create_plugin_layout(self.tools_layout, self.results_browser)
self.setLayout(layout)

self.sig_option_changed.connect(self.option_changed)
Expand Down Expand Up @@ -145,36 +157,181 @@ def refresh(self):

def set_frames(self, frames):
"""Todo"""
if self.text_edit is not None:
self.frames = frames
text = ""
for threadId, stack in frames.items():
if len(frames) > 1:
text += "Thread {threadId}:\n".format(threadId=threadId)
if stack:
af = ListTB(color_scheme=self.color)
text += (''.join(af._format_list(stack)))
else:
text += (" idle")
text += '\n'
self.text_edit.clear()
self.text_edit._insert_plain_text(
self.text_edit._get_end_cursor(),
text)
if '(Main)' in self.frames and len(self.frames['(Main)']) > 0:
main_frame = self.frames['(Main)'][-1]

self.edit_goto.emit(osp.abspath(main_frame.filename),
main_frame.lineno, '')
if self.results_browser is not None:
self.results_browser.set_frames(frames)


class LineFrameItem(QTreeWidgetItem):

def __init__(self, parent, index, filename, line, lineno, name,
font, text_color=None):
self.index = index
self.filename = filename
self.text = line
self.lineno = lineno
self.context = name
self.text_color = text_color
self.font = font
QTreeWidgetItem.__init__(self, parent, [self.__repr__()],
QTreeWidgetItem.Type)

def __repr__(self):
if self.filename is None:
return ("<!-- LineFrameItem -->"
"<p>idle<\p>")
_str = ("<!-- LineFrameItem -->" +
"<p style=\"color:'{0}';\"><b>{1}:{2}</b>".format(
self.text_color,
html.escape(osp.basename(self.filename)),
self.lineno,))
if self.context:
_str += " ({0})".format(html.escape(self.context))

_str += (" <span style='font-family:{0};".format(self.font.family()) +
"font-size:50%;'><em>{0}</em></span></p>".format(self.text))
return _str

def __unicode__(self):
return self.__repr__()

def __str__(self):
return self.__repr__()

def __lt__(self, x):
return self.index < x.index

def __ge__(self, x):
return self.index >= x.index


class ThreadItem(QTreeWidgetItem):

def __init__(self, parent, name, text_color=None):
self.name = str(name)

title_format = to_text_string('<!-- ThreadItem -->'
'<b style="color:{1}">{0}</b>'
)
title = (title_format.format(name, text_color))
QTreeWidgetItem.__init__(self, parent, [title], QTreeWidgetItem.Type)

self.setToolTip(0, self.name)

def __lt__(self, x):
return self.name < x.name

def __ge__(self, x):
return self.name >= x.name


class ItemDelegate(QStyledItemDelegate):

class FramesEdit(ConsoleWidget):
def __init__(self, parent):
QStyledItemDelegate.__init__(self, parent)
self._margin = None

def __init__(self):
self.custom_control = ControlWidget
self.custom_page_control = PageControlWidget
self.custom_edit = True
super(FramesEdit, self).__init__()
# Set readonly mode
self._executing = False
self._finalize_input_request()
def paint(self, painter, option, index):
options = QStyleOptionViewItem(option)
self.initStyleOption(options, index)

style = (QApplication.style() if options.widget is None
else options.widget.style())

doc = QTextDocument()
text = options.text
doc.setHtml(text)
doc.setDocumentMargin(0)

# This needs to be an empty string to avoid the overlapping the
# normal text of the QTreeWidgetItem
options.text = ""
style.drawControl(QStyle.CE_ItemViewItem, options, painter)

ctx = QAbstractTextDocumentLayout.PaintContext()

textRect = style.subElementRect(QStyle.SE_ItemViewItemText,
options, None)
painter.save()

painter.translate(textRect.topLeft())
painter.setClipRect(textRect.translated(-textRect.topLeft()))
doc.documentLayout().draw(painter, ctx)
painter.restore()

def sizeHint(self, option, index):
options = QStyleOptionViewItem(option)
self.initStyleOption(options, index)
doc = QTextDocument()
doc.setHtml(options.text)
doc.setTextWidth(options.rect.width())
size = QSize(int(doc.idealWidth()), int(doc.size().height()))
return size


class ResultsBrowser(OneColumnTree):
sig_edit_goto = Signal(str, int, str)

def __init__(self, parent, text_color=None):
OneColumnTree.__init__(self, parent)
self.font = get_font()
self.data = None
self.threads = None
self.text_color = text_color

# Setup
self.set_title('')
self.setSortingEnabled(False)
self.setItemDelegate(ItemDelegate(self))
self.setUniformRowHeights(True) # Needed for performance
self.sortByColumn(0, Qt.AscendingOrder)

# Signals
self.header().sectionClicked.connect(self.sort_section)

def activated(self, item):
"""Double-click event."""
itemdata = self.data.get(id(self.currentItem()))
if itemdata is not None:
filename, lineno = itemdata
self.sig_edit_goto.emit(filename, lineno, '')

@Slot(int)
def sort_section(self, idx):
self.setSortingEnabled(True)

def clicked(self, item):
"""Click event."""
self.activated(item)

def set_frames(self, frames):
"""set frames."""
self.clear()
self.threads = {}
self.data = {}
self.frames = frames
for threadId, stack in frames.items():
if len(frames) > 1:
parent = ThreadItem(
self, threadId, self.text_color)
parent.setExpanded(True)
self.threads[threadId] = parent
else:
parent = self

if stack:
for idx, frame in enumerate(stack):
item = LineFrameItem(parent, idx,
frame.filename,
frame.line,
frame.lineno,
frame.name,
self.font, self.text_color)
self.data[id(item)] = (frame.filename, frame.lineno)
else:
item = LineFrameItem(
parent, 0, None, '', 0, '', self.font, self.text_color)

if 'MainThread' in self.frames and len(self.frames['MainThread']) > 0:
main_frame = self.frames['MainThread'][-1]
self.sig_edit_goto.emit(osp.abspath(main_frame.filename),
main_frame.lineno, '')

0 comments on commit 7d25ba0

Please sign in to comment.