Hey all, I hacked at spyderlib/plugins/inspector.py to create the rich text
doc strings in a QThread, to avoid the annoying lockup while editing. I
tested it and it works great. Let me know if there is a better way to
submit these changes.
I know you all talked about switching to GitHub earlier, and I would like
to echo that sentiment. I have contributed to scikit-image and enaml, and
it has been as simple as sending them a pull request, having inline
discussions on the site, and them absorbing the changes directly. The
folks at scikit-image have posted an excellent workflow description,
modeled after the one used by the IPython developers:
http://scikit-image.org/docs/dev/gitwash/index.html. I was using
mercurial prior to working with them, but GitHub has completely sold me,
and the minor differences have not been troubling.
Regards,
Steve Silvester
--
You received this message because you are subscribed to the Google Groups
"spyder" group.
To view this discussion on the web visit
https://groups.google.com/d/msg/spyderlib/-/gnh5e8azgLwJ.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/spyderlib?hl=en.
# -*- coding: utf-8 -*-
#
# Copyright ? 2009-2010 Pierre Raybaut
# Licensed under the terms of the MIT License
# (see spyderlib/__init__.py for details)
"""Object Inspector Plugin"""
from spyderlib.qt.QtGui import (QHBoxLayout, QVBoxLayout, QLabel, QSizePolicy,
QMenu, QToolButton, QGroupBox, QFontComboBox,
QActionGroup, QFontDialog, QWidget, QComboBox,
QLineEdit, QMessageBox)
from spyderlib.qt.QtCore import SIGNAL, QUrl, QTimer, QThread
import re
import os.path as osp
import socket
import sys
# Local imports
from spyderlib.baseconfig import get_conf_path, _
from spyderlib.config import CONF
from spyderlib.guiconfig import get_icon, get_color_scheme, get_font, set_font
from spyderlib.utils import programs
from spyderlib.utils.qthelpers import (create_toolbutton, add_actions,
create_action)
from spyderlib.widgets.comboboxes import EditableComboBox
from spyderlib.widgets.sourcecode import codeeditor
from spyderlib.widgets.findreplace import FindReplace
from spyderlib.widgets.browser import WebView
from spyderlib.widgets.externalshell.pythonshell import ExtPythonShellWidget
#XXX: hardcoded dependency on optional IPython plugin component
# that requires the hack to make this work without IPython
if programs.is_module_installed('IPython.frontend.qt', '>=0.13'):
from spyderlib.widgets.ipython import IPythonControlWidget
else:
IPythonControlWidget = None
from spyderlib.plugins import SpyderPluginWidget, PluginConfigPage
try:
from sphinx import __version__ as sphinx_version
from spyderlib.utils.inspector.sphinxify import (CSS_PATH, sphinxify,
warning, generate_context)
except ImportError:
sphinxify = sphinx_version = None # analysis:ignore
class ObjectComboBox(EditableComboBox):
"""
QComboBox handling object names
"""
def __init__(self, parent):
EditableComboBox.__init__(self, parent)
self.object_inspector = parent
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.tips = {True: '', False: ''}
def is_valid(self, qstr=None):
"""Return True if string is valid"""
if qstr is None:
qstr = self.currentText()
if not re.search('^[a-zA-Z0-9_\.]*$', str(qstr), 0):
return False
objtxt = unicode(qstr)
if self.object_inspector.get_option('automatic_import'):
shell = self.object_inspector.internal_shell
if shell is not None:
return shell.is_defined(objtxt, force_import=True)
shell = self.object_inspector.get_shell()
if shell is not None:
try:
return shell.is_defined(objtxt)
except socket.error:
shell = self.object_inspector.get_shell()
try:
return shell.is_defined(objtxt)
except socket.error:
# Well... too bad!
pass
def validate_current_text(self):
self.validate(self.currentText())
class ObjectInspectorConfigPage(PluginConfigPage):
def setup_page(self):
sourcecode_group = QGroupBox(_("Source code"))
wrap_mode_box = self.create_checkbox(_("Wrap lines"), 'wrap')
names = CONF.get('color_schemes', 'names')
choices = zip(names, names)
cs_combo = self.create_combobox(_("Syntax color scheme: "),
choices, 'color_scheme_name')
sourcecode_layout = QVBoxLayout()
sourcecode_layout.addWidget(wrap_mode_box)
sourcecode_layout.addWidget(cs_combo)
sourcecode_group.setLayout(sourcecode_layout)
plain_text_font_group = self.create_fontgroup(option=None,
text=_("Plain text font style"),
fontfilters=QFontComboBox.MonospacedFonts)
rich_text_font_group = self.create_fontgroup(option='rich_text',
text=_("Rich text font style"))
features_group = QGroupBox(_("Additional features"))
math_box = self.create_checkbox(_("Render mathematical equations"),
'math')
req_sphinx = sphinx_version is not None and sphinx_version >= "1.1"
math_box.setEnabled(req_sphinx)
if not req_sphinx:
sphinx_tip = _("This feature requires Sphinx 1.1 or superior.")
if sphinx_version is not None:
sphinx_tip += "\n" + _("Sphinx %s is currently installed."
) % sphinx_version
math_box.setToolTip(sphinx_tip)
features_layout = QVBoxLayout()
features_layout.addWidget(math_box)
features_group.setLayout(features_layout)
vlayout = QVBoxLayout()
vlayout.addWidget(rich_text_font_group)
vlayout.addWidget(plain_text_font_group)
vlayout.addWidget(features_group)
vlayout.addWidget(sourcecode_group)
vlayout.addStretch(1)
self.setLayout(vlayout)
class RichText(QWidget):
"""
WebView widget with find dialog
"""
def __init__(self, parent):
QWidget.__init__(self, parent)
self.webview = WebView(self)
self.find_widget = FindReplace(self)
self.find_widget.set_editor(self.webview)
self.find_widget.hide()
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self.webview)
layout.addWidget(self.find_widget)
self.setLayout(layout)
def set_font(self, font, fixed_font=None):
"""Set font"""
self.webview.set_font(font, fixed_font=fixed_font)
def set_html(self, html_text, base_url):
"""Set html text"""
self.webview.setHtml(html_text, base_url)
def clear(self):
self.set_html('', self.webview.url())
class PlainText(QWidget):
"""
Read-only editor widget with find dialog
"""
def __init__(self, parent):
QWidget.__init__(self, parent)
self.editor = None
# Read-only editor
self.editor = codeeditor.CodeEditor(self)
self.editor.setup_editor(linenumbers=False, language='py',
scrollflagarea=False)
self.connect(self.editor, SIGNAL("focus_changed()"),
lambda: self.emit(SIGNAL("focus_changed()")))
self.editor.setReadOnly(True)
# Find/replace widget
self.find_widget = FindReplace(self)
self.find_widget.set_editor(self.editor)
self.find_widget.hide()
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self.editor)
layout.addWidget(self.find_widget)
self.setLayout(layout)
def set_font(self, font, color_scheme=None):
"""Set font"""
self.editor.set_font(font, color_scheme=color_scheme)
def set_color_scheme(self, color_scheme):
"""Set color scheme"""
self.editor.set_color_scheme(color_scheme)
def set_text(self, text, is_code):
self.editor.set_highlight_current_line(is_code)
self.editor.set_occurence_highlighting(is_code)
if is_code:
self.editor.set_language('py')
else:
self.editor.set_language(None)
self.editor.set_text(text)
self.editor.set_cursor_position('sof')
def clear(self):
self.editor.clear()
class ObjectInspector(SpyderPluginWidget):
"""
Docstrings viewer widget
"""
CONF_SECTION = 'inspector'
CONFIGWIDGET_CLASS = ObjectInspectorConfigPage
LOG_PATH = get_conf_path('.inspector')
def __init__(self, parent):
SpyderPluginWidget.__init__(self, parent)
self.internal_shell = None
# Initialize plugin
self.initialize_plugin()
self.no_doc_string = _("No documentation available")
self._last_console_cb = None
self._last_editor_cb = None
self.set_default_color_scheme()
self.plain_text = PlainText(self)
self.rich_text = RichText(self)
color_scheme = get_color_scheme(self.get_option('color_scheme_name'))
self.set_plain_text_font(self.get_plugin_font(), color_scheme)
self.plain_text.editor.toggle_wrap_mode(self.get_option('wrap'))
# Add entries to read-only editor context-menu
font_action = create_action(self, _("&Font..."), None,
'font.png', _("Set font style"),
triggered=self.change_font)
self.wrap_action = create_action(self, _("Wrap lines"),
toggled=self.toggle_wrap_mode)
self.wrap_action.setChecked(self.get_option('wrap'))
self.plain_text.editor.readonly_menu.addSeparator()
add_actions(self.plain_text.editor.readonly_menu,
(font_action, self.wrap_action))
self.set_rich_text_font(self.get_plugin_font('rich_text'))
self.shell = None
self.external_console = None
# locked = disable link with Console
self.locked = False
self._last_texts = [None, None]
self._last_rope_data = None
# Object name
layout_edit = QHBoxLayout()
layout_edit.setContentsMargins(0, 0, 0, 0)
txt = _("Source")
if sys.platform == 'darwin':
source_label = QLabel(" " + txt)
else:
source_label = QLabel(txt)
layout_edit.addWidget(source_label)
self.source_combo = QComboBox(self)
self.source_combo.addItems([_("Console"), _("Editor")])
self.connect(self.source_combo, SIGNAL('currentIndexChanged(int)'),
self.source_changed)
if not programs.is_module_installed('rope'):
self.source_combo.hide()
source_label.hide()
layout_edit.addWidget(self.source_combo)
layout_edit.addSpacing(10)
layout_edit.addWidget(QLabel(_("Object")))
self.combo = ObjectComboBox(self)
layout_edit.addWidget(self.combo)
self.object_edit = QLineEdit(self)
self.object_edit.setReadOnly(True)
layout_edit.addWidget(self.object_edit)
self.combo.setMaxCount(self.get_option('max_history_entries'))
self.combo.addItems( self.load_history() )
self.connect(self.combo, SIGNAL("valid(bool)"),
lambda valid: self.force_refresh())
# Plain text docstring option
self.docstring = True
self.rich_help = sphinxify is not None \
and self.get_option('rich_mode', True)
self.plain_text_action = create_action(self, _("Plain Text"),
toggled=self.toggle_plain_text)
# Source code option
self.show_source_action = create_action(self, _("Show Source"),
toggled=self.toggle_show_source)
# Rich text option
self.rich_text_action = create_action(self, _("Rich Text"),
toggled=self.toggle_rich_text)
# Add the help actions to an exclusive QActionGroup
help_actions = QActionGroup(self)
help_actions.setExclusive(True)
help_actions.addAction(self.plain_text_action)
help_actions.addAction(self.rich_text_action)
# Automatic import option
self.auto_import_action = create_action(self, _("Automatic import"),
toggled=self.toggle_auto_import)
auto_import_state = self.get_option('automatic_import')
self.auto_import_action.setChecked(auto_import_state)
# Lock checkbox
self.locked_button = create_toolbutton(self,
triggered=self.toggle_locked)
layout_edit.addWidget(self.locked_button)
self._update_lock_icon()
# Option menu
options_button = create_toolbutton(self, text=_("Options"),
icon=get_icon('tooloptions.png'),
text_beside_icon=True)
options_button.setPopupMode(QToolButton.InstantPopup)
menu = QMenu(self)
add_actions(menu, [self.rich_text_action, self.plain_text_action,
self.show_source_action, None,
self.auto_import_action])
options_button.setMenu(menu)
layout_edit.addWidget(options_button)
if self.rich_help:
self.switch_to_rich_text()
else:
self.switch_to_plain_text()
self.plain_text_action.setChecked(not self.rich_help)
self.rich_text_action.setChecked(self.rich_help)
self.rich_text_action.setEnabled(sphinxify is not None)
self.source_changed()
# Main layout
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.addLayout(layout_edit)
layout.addWidget(self.plain_text)
layout.addWidget(self.rich_text)
self.setLayout(layout)
self._starting_up = True
#------ SpyderPluginWidget API ---------------------------------------------
def get_plugin_title(self):
"""Return widget title"""
return _('Object inspector')
def get_plugin_icon(self):
"""Return widget icon"""
return get_icon('inspector.png')
def get_focus_widget(self):
"""
Return the widget to give focus to when
this plugin's dockwidget is raised on top-level
"""
self.combo.lineEdit().selectAll()
return self.combo
def get_plugin_actions(self):
"""Return a list of actions related to plugin"""
return []
def register_plugin(self):
"""Register plugin in Spyder's main window"""
self.connect(self, SIGNAL('focus_changed()'),
self.main.plugin_focus_changed)
self.main.add_dockwidget(self)
self.main.console.set_inspector(self)
self.internal_shell = self.main.console.shell
def closing_plugin(self, cancelable=False):
"""Perform actions before parent main window is closed"""
return True
def refresh_plugin(self):
"""Refresh widget"""
if self._starting_up:
self._starting_up = False
QTimer.singleShot(5000, self.refresh_plugin)
self.set_object_text(None, force_refresh=False)
def apply_plugin_settings(self, options):
"""Apply configuration file's plugin settings"""
color_scheme_n = 'color_scheme_name'
color_scheme_o = get_color_scheme(self.get_option(color_scheme_n))
font_n = 'plugin_font'
font_o = self.get_plugin_font()
rich_font_n = 'rich_text'
rich_font_o = self.get_plugin_font('rich_text')
wrap_n = 'wrap'
wrap_o = self.get_option(wrap_n)
self.wrap_action.setChecked(wrap_o)
math_n = 'math'
math_o = self.get_option(math_n)
if font_n in options:
scs = color_scheme_o if color_scheme_n in options else None
self.set_plain_text_font(font_o, color_scheme=scs)
if rich_font_n in options:
self.set_rich_text_font(rich_font_o)
elif color_scheme_n in options:
self.set_plain_text_color_scheme(color_scheme_o)
if wrap_n in options:
self.toggle_wrap_mode(wrap_o)
if math_n in options:
self.toggle_math_mode(math_o)
#------ Public API (related to inspector's source) -------------------------
def source_is_console(self):
"""Return True if source is Console"""
return self.source_combo.currentIndex() == 0
def switch_to_editor_source(self):
self.source_combo.setCurrentIndex(1)
def switch_to_console_source(self):
self.source_combo.setCurrentIndex(0)
def source_changed(self, index=None):
if self.source_is_console():
# Console
self.combo.show()
self.object_edit.hide()
self.show_source_action.setEnabled(True)
self.auto_import_action.setEnabled(True)
else:
# Editor
self.combo.hide()
self.object_edit.show()
self.show_source_action.setDisabled(True)
self.auto_import_action.setDisabled(True)
self.restore_text()
def save_text(self, callback):
if self.source_is_console():
self._last_console_cb = callback
else:
self._last_editor_cb = callback
def restore_text(self):
if self.source_is_console():
cb = self._last_console_cb
else:
cb = self._last_editor_cb
if cb is None:
if self.is_plain_text_mode():
self.plain_text.clear()
else:
self.rich_text.clear()
else:
func = cb[0]
args = cb[1:]
func(*args)
if func.im_self is self.rich_text:
self.switch_to_rich_text()
else:
self.switch_to_plain_text()
#------ Public API (related to rich/plain text widgets) --------------------
@property
def find_widget(self):
if self.plain_text.isVisible():
return self.plain_text.find_widget
else:
return self.rich_text.find_widget
def set_rich_text_font(self, font):
"""Set rich text mode font"""
self.rich_text.set_font(font, fixed_font=self.get_plugin_font())
def set_plain_text_font(self, font, color_scheme=None):
"""Set plain text mode font"""
self.plain_text.set_font(font, color_scheme=color_scheme)
def set_plain_text_color_scheme(self, color_scheme):
"""Set plain text mode color scheme"""
self.plain_text.set_color_scheme(color_scheme)
def change_font(self):
"""Change console font"""
font, valid = QFontDialog.getFont(get_font(self.CONF_SECTION), self,
_("Select a new font"))
if valid:
self.set_plain_text_font(font)
set_font(font, self.CONF_SECTION)
def toggle_wrap_mode(self, checked):
"""Toggle wrap mode"""
self.plain_text.editor.toggle_wrap_mode(checked)
self.set_option('wrap', checked)
def toggle_math_mode(self, checked):
"""Toggle math mode"""
self.set_option('math', checked)
def is_plain_text_mode(self):
"""Return True if plain text mode is active"""
return self.plain_text.isVisible()
def is_rich_text_mode(self):
"""Return True if rich text mode is active"""
return self.rich_text.isVisible()
def switch_to_plain_text(self):
"""Switch to plain text mode"""
self.rich_help = False
self.plain_text.show()
self.rich_text.hide()
self.plain_text_action.setChecked(True)
def switch_to_rich_text(self):
"""Switch to rich text mode"""
self.rich_help = True
self.plain_text.hide()
self.rich_text.show()
self.rich_text_action.setChecked(True)
self.show_source_action.setChecked(False)
def set_plain_text(self, text, is_code):
"""Set plain text"""
if type(text) is dict:
title = text['title']
if title:
rst_title = ''.join(['='*len(title), '\n', text['title'], '\n',
'='*len(title), '\n\n'])
else:
rst_title = ''
if text['argspec']:
definition = ''.join(['Definition: ', title, text['argspec'],
'\n'])
else:
definition = ''
if text['note']:
note = ''.join(['Type: ', text['note'], '\n\n----\n\n'])
else:
note = ''
full_text = ''.join([rst_title, definition, note, text['doc']])
else:
full_text = text
self.plain_text.set_text(full_text, is_code)
self.save_text([self.plain_text.set_text, full_text, is_code])
def set_rich_text_html(self, html_text, base_url):
"""Set rich text"""
self.rich_text.set_html(html_text, base_url)
self.save_text([self.rich_text.set_html, html_text, base_url])
#------ Public API ---------------------------------------------------------
def set_external_console(self, external_console):
self.external_console = external_console
def force_refresh(self):
if self.source_is_console():
self.set_object_text(None, force_refresh=True)
elif self._last_rope_data is not None:
text = self._last_rope_data
self.set_rope_doc(text, force_refresh=True)
def set_object_text(self, text, force_refresh=False, ignore_unknown=False):
"""Set object analyzed by Object Inspector"""
if (self.locked and not force_refresh):
return
self.switch_to_console_source()
add_to_combo = True
if text is None:
text = unicode(self.combo.currentText())
add_to_combo = False
found = self.show_help(text, ignore_unknown=ignore_unknown)
if ignore_unknown and not found:
return
if add_to_combo:
self.combo.add_text(text)
self.save_history()
if self.dockwidget is not None:
self.dockwidget.blockSignals(True)
self.__eventually_raise_inspector(text, force=force_refresh)
if self.dockwidget is not None:
self.dockwidget.blockSignals(False)
def set_rope_doc(self, text, force_refresh=False):
"""Use the object inspector to show text computed with rope
from the editor plugin"""
if (self.locked and not force_refresh):
return
self.switch_to_editor_source()
self._last_rope_data = text
self.object_edit.setText(text['obj_text'])
if self.rich_help:
self.set_sphinx_text(text)
else:
self.set_plain_text(text, is_code=False)
if self.dockwidget is not None:
self.dockwidget.blockSignals(True)
self.__eventually_raise_inspector(text['doc'], force=force_refresh)
if self.dockwidget is not None:
self.dockwidget.blockSignals(False)
def __eventually_raise_inspector(self, text, force=False):
index = self.source_combo.currentIndex()
if hasattr(self.main, 'tabifiedDockWidgets'):
# 'QMainWindow.tabifiedDockWidgets' was introduced in PyQt 4.5
if self.dockwidget and (force or self.dockwidget.isVisible()) \
and not self.ismaximized \
and (force or text != self._last_texts[index]):
dockwidgets = self.main.tabifiedDockWidgets(self.dockwidget)
if self.main.console.dockwidget not in dockwidgets and \
(hasattr(self.main, 'extconsole') and \
self.main.extconsole.dockwidget not in dockwidgets):
self.dockwidget.show()
self.dockwidget.raise_()
self._last_texts[index] = text
def load_history(self, obj=None):
"""Load history from a text file in user home directory"""
if osp.isfile(self.LOG_PATH):
history = [line.replace('\n','')
for line in file(self.LOG_PATH, 'r').readlines()]
else:
history = []
return history
def save_history(self):
"""Save history to a text file in user home directory"""
file(self.LOG_PATH, 'w').write("\n".join( \
[ unicode( self.combo.itemText(index) )
for index in range(self.combo.count()) ] ))
def toggle_plain_text(self, checked):
"""Toggle plain text docstring"""
if checked:
self.docstring = checked
self.switch_to_plain_text()
self.force_refresh()
self.set_option('rich_mode', not checked)
def toggle_show_source(self, checked):
"""Toggle show source code"""
if checked:
self.switch_to_plain_text()
self.docstring = not checked
self.force_refresh()
self.set_option('rich_mode', not checked)
def toggle_rich_text(self, checked):
"""Toggle between sphinxified docstrings or plain ones"""
if checked:
self.docstring = not checked
self.switch_to_rich_text()
self.force_refresh()
self.set_option('rich_mode', checked)
def toggle_auto_import(self, checked):
"""Toggle automatic import feature"""
self.force_refresh()
self.combo.validate_current_text()
self.set_option('automatic_import', checked)
def toggle_locked(self):
"""
Toggle locked state
locked = disable link with Console
"""
self.locked = not self.locked
self._update_lock_icon()
def _update_lock_icon(self):
"""Update locked state icon"""
icon = get_icon("lock.png" if self.locked else "lock_open.png")
self.locked_button.setIcon(icon)
tip = _("Unlock") if self.locked else _("Lock")
self.locked_button.setToolTip(tip)
def set_shell(self, shell):
"""Bind to shell"""
if IPythonControlWidget is not None:
# XXX(anatoli): hack to make Spyder run on systems without IPython
# there should be a better way
if isinstance(shell, IPythonControlWidget):
# XXX: this ignores passed argument completely
self.shell = self.external_console.get_current_shell()
else:
self.shell = shell
def get_shell(self):
"""Return shell which is currently bound to object inspector,
or another running shell if it has been terminated"""
if not isinstance(self.shell, ExtPythonShellWidget) \
or not self.shell.externalshell.is_running():
self.shell = None
if self.external_console is not None:
self.shell = self.external_console.get_running_python_shell()
if self.shell is None:
self.shell = self.internal_shell
return self.shell
def set_sphinx_text(self, text):
"""Sphinxify text and display it"""
self._sphinx_thread = thread = SphinxThread()
thread.text = text
thread.parent = self
thread.finished.connect(self._set_sphinx_text)
thread.start()
def _set_sphinx_text(self):
html_text = self._sphinx_thread.html_text
self.set_rich_text_html(html_text, QUrl.fromLocalFile(CSS_PATH))
def show_help(self, obj_text, ignore_unknown=False):
"""Show help"""
shell = self.get_shell()
if shell is None:
return
obj_text = unicode(obj_text)
if not shell.is_defined(obj_text):
if self.get_option('automatic_import') and\
self.internal_shell.is_defined(obj_text, force_import=True):
shell = self.internal_shell
else:
shell = None
doc_text = None
source_text = None
if shell is not None:
doc_text = shell.get_doc(obj_text)
if isinstance(doc_text, bool):
doc_text = None
source_text = shell.get_source(obj_text)
is_code = False
if self.rich_help:
self.set_sphinx_text(doc_text)
if ignore_unknown:
return doc_text is not None
else:
return True
elif self.docstring:
hlp_text = doc_text
if hlp_text is None:
hlp_text = source_text
if hlp_text is None:
hlp_text = self.no_doc_string
if ignore_unknown:
return False
else:
hlp_text = source_text
if hlp_text is None:
hlp_text = doc_text
if hlp_text is None:
hlp_text = _("No source code available.")
if ignore_unknown:
return False
else:
is_code = True
self.set_plain_text(hlp_text, is_code=is_code)
return True
class SphinxThread(QThread):
def run(self):
text = self.text
parent = self.parent
math_o = parent.get_option('math')
if text is not None and text['doc'] != '':
try:
context = generate_context(title=text['title'],
argspec=text['argspec'],
note=text['note'],
math=math_o)
html_text = sphinxify(text['doc'], context)
except Exception, error:
import sphinx
QMessageBox.critical(parent,
_('Object inspector'),
_("The following error occured when calling "
"<b>Sphinx %s</b>. <br>Please check if this "
"version of Sphinx is supported by Spyder."
"<br><br>Error message:<br>%s"
) % (sphinx.__version__, unicode(error)))
parent.plain_text_action.setChecked(True)
return
else:
html_text = warning(parent.no_doc_string)
self.html_text = html_text