# -*- coding: utf-8 -*-
#
# Copyright © Spyder Project Contributors
# Licensed under the terms of the MIT License
#
"""
Help plugin widgets.
"""
# Standard library imports
import os
import re
import socket
import sys
# Third party imports
from qtpy.QtCore import Qt, QUrl, Signal, Slot, QPoint
from qtpy.QtGui import QColor
from qtpy.QtWebEngineWidgets import WEBENGINE, QWebEnginePage
from qtpy.QtWidgets import (QActionGroup, QComboBox, QLabel, QLineEdit,
QMessageBox, QSizePolicy, QStackedLayout,
QVBoxLayout, QWidget)
# Local imports
from spyder.api.config.decorators import on_conf_change
from spyder.api.translations import get_translation
from spyder.api.widgets.main_widget import PluginMainWidget
from spyder.api.widgets.mixins import SpyderWidgetMixin
from spyder.config.base import get_module_source_path
from spyder.plugins.help.utils.sphinxify import (CSS_PATH, generate_context,
loading, usage, warning)
from spyder.plugins.help.utils.sphinxthread import SphinxThread
from spyder.py3compat import get_meth_class_inst, to_text_string
from spyder.utils import programs
from spyder.utils.image_path_manager import get_image_path
from spyder.utils.palette import QStylePalette
from spyder.utils.qthelpers import start_file
from spyder.widgets.browser import FrameWebView
from spyder.widgets.comboboxes import EditableComboBox
from spyder.widgets.findreplace import FindReplace
from spyder.widgets.simplecodeeditor import SimpleCodeEditor
# Localization
_ = get_translation('spyder')
# --- Constants
# ----------------------------------------------------------------------------
MAIN_BG_COLOR = QStylePalette.COLOR_BACKGROUND_1
class HelpWidgetActions:
# Toggles
ToggleAutomaticImport = 'toggle_automatic_import_action'
ToggleLocked = 'toggle_locked_action'
TogglePlainMode = 'toggle_plain_mode_action'
ToggleRichMode = 'toggle_rich_mode_action'
ToggleShowSource = 'toggle_show_source_action'
ToggleWrap = 'toggle_wrap_action'
CopyAction = "help_widget_copy_action"
SelectAll = "select_all_action",
Home = 'home_action'
class HelpWidgetOptionsMenuSections:
Display = 'display_section'
Other = 'other_section'
class HelpWidgetMainToolbarSections:
Main = 'main_section'
class HelpWidgetToolbarItems:
SourceLabel = 'source_label'
SourceCombo = 'source_combo'
ObjectLabel = 'object_label'
ObjectCombo = 'object_combo'
ObjectEdit = 'object_edit'
# --- Widgets
# ----------------------------------------------------------------------------
class ObjectComboBox(EditableComboBox):
"""
QComboBox handling object names
"""
# Signals
valid = Signal(bool, bool)
def __init__(self, parent, id_=None):
EditableComboBox.__init__(self, parent)
self.help = parent
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.tips = {True: '', False: ''}
if id_ is not None:
self.ID = id_
def is_valid(self, qstr=None):
"""Return True if string is valid"""
if not self.help.source_is_console():
return True
if qstr is None:
qstr = self.currentText()
if not re.search(r'^[a-zA-Z0-9_\.]*$', str(qstr), 0):
return False
objtxt = to_text_string(qstr)
shell_is_defined = False
if self.help.get_conf('automatic_import'):
shell = self.help.internal_shell
if shell is not None:
shell_is_defined = shell.is_defined(objtxt, force_import=True)
if not shell_is_defined:
shell = self.help.get_shell()
if shell is not None:
try:
shell_is_defined = shell.is_defined(objtxt)
except socket.error:
shell = self.help.get_shell()
try:
shell_is_defined = shell.is_defined(objtxt)
except socket.error:
# Well... too bad!
pass
return shell_is_defined
def validate_current_text(self):
self.validate(self.currentText())
def validate(self, qstr, editing=True):
"""Reimplemented to avoid formatting actions"""
valid = self.is_valid(qstr)
if self.hasFocus() and valid is not None:
if editing and not valid:
# Combo box text is being modified: invalidate the entry
self.show_tip(self.tips[valid])
self.valid.emit(False, False)
else:
# A new item has just been selected
if valid:
self.selected()
# See spyder-ide/spyder#9542.
self.lineEdit().cursorWordForward(False)
else:
self.valid.emit(False, False)
class RichText(QWidget, SpyderWidgetMixin):
"""
WebView widget with find dialog
"""
sig_link_clicked = Signal(QUrl)
def __init__(self, parent):
super().__init__(parent, class_parent=parent)
self.webview = FrameWebView(self)
self.webview.setup()
if WEBENGINE:
self.webview.web_widget.page().setBackgroundColor(
QColor(MAIN_BG_COLOR))
else:
self.webview.web_widget.setStyleSheet(
"background:{}".format(MAIN_BG_COLOR))
self.webview.page().setLinkDelegationPolicy(
QWebEnginePage.DelegateAllLinks)
self.find_widget = FindReplace(self)
self.find_widget.set_editor(self.webview.web_widget)
self.find_widget.hide()
# Layout
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self.webview)
layout.addWidget(self.find_widget)
self.setLayout(layout)
# Signals
self.webview.linkClicked.connect(self.sig_link_clicked)
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 load_url(self, url):
if isinstance(url, QUrl):
qurl = url
else:
qurl = QUrl(url)
self.load(qurl)
def clear(self):
self.set_html('', self.webview.url())
class PlainText(QWidget):
"""
Read-only editor widget with find dialog
"""
# Signals
focus_changed = Signal()
sig_custom_context_menu_requested = Signal(QPoint)
def __init__(self, parent):
QWidget.__init__(self, parent)
self.editor = None
# Read-only simple code editor
self.editor = SimpleCodeEditor(self)
self.editor.setup_editor(
language='py',
highlight_current_line=False,
linenumbers=False,
)
self.editor.sig_focus_changed.connect(self.focus_changed)
self.editor.setReadOnly(True)
self.editor.setContextMenuPolicy(Qt.CustomContextMenu)
# 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)
self.editor.customContextMenuRequested.connect(
self.sig_custom_context_menu_requested)
def set_font(self, font, color_scheme=None):
"""Set font"""
self.editor.set_color_scheme(color_scheme)
self.editor.set_font(font)
def set_color_scheme(self, color_scheme):
"""Set color scheme"""
self.editor.set_color_scheme(color_scheme)
def set_text(self, text, 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()
def set_wrap_mode(self, value):
self.editor.toggle_wrap_mode(value)
def copy(self):
self.editor.copy()
def select_all(self):
self.editor.selectAll()
class HelpWidget(PluginMainWidget):
ENABLE_SPINNER = True
# Signals
sig_item_found = Signal()
"""This signal is emitted when an item is found."""
sig_render_started = Signal()
"""This signal is emitted to inform a help text rendering has started."""
sig_render_finished = Signal()
"""This signal is emitted to inform a help text rendering has finished."""
def __init__(self, name=None, plugin=None, parent=None):
super().__init__(name, plugin, parent)
# Attributes
self._starting_up = True
self._current_color_scheme = None
self._last_texts = [None, None]
self._last_editor_doc = None
self._last_console_cb = None
self._last_editor_cb = None
self.css_path = self.get_conf('css_path', CSS_PATH, 'appearance')
self.no_docs = _("No documentation available")
self.docstring = True # TODO: What is this used for?
# Widgets
self._sphinx_thread = SphinxThread(
html_text_no_doc=warning(self.no_docs, css_path=self.css_path),
css_path=self.css_path,
)
self.shell = None
self.internal_console = None
self.internal_shell = None
self.plain_text = PlainText(self)
self.rich_text = RichText(self)
self.source_label = QLabel(_("Source"))
self.source_label.ID = HelpWidgetToolbarItems.SourceLabel
self.source_combo = QComboBox(self)
self.source_combo.ID = HelpWidgetToolbarItems.SourceCombo
self.object_label = QLabel(_("Object"))
self.object_label.ID = HelpWidgetToolbarItems.ObjectLabel
self.object_combo = ObjectComboBox(
self, HelpWidgetToolbarItems.ObjectCombo)
self.object_edit = QLineEdit(self)
self.object_edit.ID = HelpWidgetToolbarItems.ObjectEdit
# Setup
self.object_edit.setReadOnly(True)
self.object_combo.setMaxCount(self.get_conf('max_history_entries'))
self.object_combo.setItemText(0, '')
self.plain_text.set_wrap_mode(self.get_conf('wrap'))
self.source_combo.addItems([_("Console"), _("Editor")])
if (not programs.is_module_installed('rope') and
not programs.is_module_installed('jedi', '>=0.11.0')):
self.source_combo.hide()
self.source_label.hide()
# Layout
self.stack_layout = layout = QStackedLayout()
layout.addWidget(self.rich_text)
layout.addWidget(self.plain_text)
self.setLayout(layout)
# Signals
self._sphinx_thread.html_ready.connect(
self._on_sphinx_thread_html_ready)
self._sphinx_thread.error_msg.connect(
self._on_sphinx_thread_error_msg)
self.object_combo.valid.connect(self.force_refresh)
self.rich_text.sig_link_clicked.connect(self.handle_link_clicks)
self.source_combo.currentIndexChanged.connect(
lambda x: self.source_changed())
self.sig_render_started.connect(self.start_spinner)
self.sig_render_finished.connect(self.stop_spinner)
# --- PluginMainWidget API
# ------------------------------------------------------------------------
def get_title(self):
return _('Help')
def setup(self):
self.wrap_action = self.create_action(
name=HelpWidgetActions.ToggleWrap,
text=_("Wrap lines"),
toggled=True,
initial=self.get_conf('wrap'),
option='wrap'
)
self.copy_action = self.create_action(
name=HelpWidgetActions.CopyAction,
text=_("Copy"),
triggered=lambda value: self.plain_text.copy(),
register_shortcut=False,
)
self.select_all_action = self.create_action(
name=HelpWidgetActions.SelectAll,
text=_("Select All"),
triggered=lambda value: self.plain_text.select_all(),
register_shortcut=False,
)
self.auto_import_action = self.create_action(
name=HelpWidgetActions.ToggleAutomaticImport,
text=_("Automatic import"),
toggled=True,
initial=self.get_conf('automatic_import'),
option='automatic_import'
)
self.show_source_action = self.create_action(
name=HelpWidgetActions.ToggleShowSource,
text=_("Show Source"),
toggled=True,
option='show_source'
)
self.rich_text_action = self.create_action(
name=HelpWidgetActions.ToggleRichMode,
text=_("Rich Text"),
toggled=True,
initial=self.get_conf('rich_mode'),
option='rich_mode'
)
self.plain_text_action = self.create_action(
name=HelpWidgetActions.TogglePlainMode,
text=_("Plain Text"),
toggled=True,
initial=self.get_conf('plain_mode'),
option='plain_mode'
)
self.locked_action = self.create_action(
name=HelpWidgetActions.ToggleLocked,
text=_("Lock/Unlock"),
toggled=True,
icon=self.create_icon('lock_open'),
initial=self.get_conf('locked'),
option='locked'
)
self.home_action = self.create_action(
name=HelpWidgetActions.Home,
text=_("Home"),
triggered=self.show_intro_message,
icon=self.create_icon('home'),
)
# 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)
# Menu
menu = self.get_options_menu()
for item in [self.rich_text_action, self.plain_text_action,
self.show_source_action]:
self.add_item_to_menu(
item,
menu=menu,
section=HelpWidgetOptionsMenuSections.Display,
)
self.add_item_to_menu(
self.auto_import_action,
menu=menu,
section=HelpWidgetOptionsMenuSections.Other,
)
# Plain text menu
self._plain_text_context_menu = self.create_menu(
"plain_text_context_menu")
self.add_item_to_menu(
self.copy_action,
self._plain_text_context_menu,
section="copy_section",
)
self.add_item_to_menu(
self.select_all_action,
self._plain_text_context_menu,
section="select_section",
)
self.add_item_to_menu(
self.wrap_action,
self._plain_text_context_menu,
section="wrap_section",
)
# Toolbar
toolbar = self.get_main_toolbar()
for item in [self.source_label, self.source_combo, self.object_label,
self.object_combo, self.object_edit, self.home_action,
self.locked_action]:
self.add_item_to_toolbar(
item,
toolbar=toolbar,
section=HelpWidgetMainToolbarSections.Main,
)
self.source_changed()
self.switch_to_rich_text()
self.show_intro_message()
# Signals
self.plain_text.sig_custom_context_menu_requested.connect(
self._show_plain_text_context_menu)
def _should_display_welcome_page(self):
"""Determine if the help welcome page should be displayed."""
return (self._last_editor_doc is None or
self._last_console_cb is None or
self._last_editor_cb is None)
@on_conf_change(option='wrap')
def on_wrap_option_update(self, value):
self.plain_text.set_wrap_mode(value)
@on_conf_change(option='locked')
def on_lock_update(self, value):
if value:
icon = self.create_icon('lock')
tip = _("Unlock")
else:
icon = self.create_icon('lock_open')
tip = _("Lock")
action = self.get_action(HelpWidgetActions.ToggleLocked)
action.setIcon(icon)
action.setToolTip(tip)
@on_conf_change(option='automatic_import')
def on_automatic_import_update(self, value):
self.object_combo.validate_current_text()
if self._should_display_welcome_page():
self.show_intro_message()
else:
self.force_refresh()
@on_conf_change(option='rich_mode')
def on_rich_mode_update(self, value):
if value:
# Plain Text OFF / Rich text ON
self.docstring = not value
self.stack_layout.setCurrentWidget(self.rich_text)
self.get_action(HelpWidgetActions.ToggleShowSource).setChecked(
False)
else:
# Plain Text ON / Rich text OFF
self.docstring = value
self.stack_layout.setCurrentWidget(self.plain_text)
if self._should_display_welcome_page():
self.show_intro_message()
else:
self.force_refresh()
@on_conf_change(option='show_source')
def on_show_source_update(self, value):
if value:
self.switch_to_plain_text()
self.get_action(HelpWidgetActions.ToggleRichMode).setChecked(
False)
self.docstring = not value
if self._should_display_welcome_page():
self.show_intro_message()
else:
self.force_refresh()
def update_actions(self):
for __, action in self.get_actions().items():
# IMPORTANT: Since we are defining the main actions in here
# and the context is WidgetWithChildrenShortcut we need to
# assign the same actions to the children widgets in order
# for shortcuts to work
for widget in [self.plain_text,
self.rich_text,
self.source_combo,
self.object_combo,
self.object_edit]:
if action not in widget.actions():
widget.addAction(action)
def get_focus_widget(self):
self.object_combo.lineEdit().selectAll()
return self.object_combo
# --- Private API
# ------------------------------------------------------------------------
@Slot(QPoint)
def _show_plain_text_context_menu(self, point):
point = self.plain_text.mapToGlobal(point)
self._plain_text_context_menu.popup(point)
def _on_sphinx_thread_html_ready(self, html_text):
"""
Set our sphinx documentation based on thread result.
Parameters
----------
html_text: str
Html results text.
"""
self._sphinx_thread.wait()
self.set_rich_text_html(html_text, QUrl.fromLocalFile(self.css_path))
self.sig_render_finished.emit()
self.stop_spinner()
def _on_sphinx_thread_error_msg(self, error_msg):
"""
Display error message on Sphinx rich text failure.
Parameters
----------
error_msg: str
Error message text.
"""
self._sphinx_thread.wait()
self.plain_text_action.setChecked(True)
sphinx_ver = programs.get_module_version('sphinx')
QMessageBox.critical(
self,
_('Help'),
_("The following error occurred when calling "
"Sphinx %s.
Incompatible Sphinx "
"version or doc string decoding failed."
"
Error message:
%s"
) % (sphinx_ver, error_msg),
)
self.sig_render_finished.emit()
# --- Public API
# ------------------------------------------------------------------------
def source_is_console(self):
"""Return True if source is Console."""
return self.source_combo.currentIndex() == 0
def switch_to_editor_source(self):
"""Switch to editor view of the help viewer."""
self.source_combo.setCurrentIndex(1)
def switch_to_console_source(self):
"""Switch to console view of the help viewer."""
self.source_combo.setCurrentIndex(0)
def source_changed(self):
"""Handle a source (plain/rich) change."""
is_console = self.source_is_console()
if is_console:
self.object_combo.show()
self.object_edit.hide()
else:
# Editor
self.object_combo.hide()
self.object_edit.show()
self.get_action(HelpWidgetActions.ToggleShowSource).setEnabled(
is_console)
self.get_action(HelpWidgetActions.ToggleAutomaticImport).setEnabled(
is_console)
self.restore_text()
def save_text(self, callback):
"""
Save help text.
Parameters
----------
callback: callable
Method to call on save.
"""
if self.source_is_console():
self._last_console_cb = callback
else:
self._last_editor_cb = callback
def restore_text(self):
"""Restore last text using callback."""
if self.source_is_console():
cb = self._last_console_cb
else:
cb = self._last_editor_cb
if cb is None:
if self.get_conf('plain_mode'):
self.switch_to_plain_text()
else:
self.switch_to_rich_text()
else:
func = cb[0]
args = cb[1:]
func(*args)
if get_meth_class_inst(func) is self.rich_text:
self.switch_to_rich_text()
else:
self.switch_to_plain_text()
@property
def find_widget(self):
"""Show find widget."""
if self.get_conf('plain_mode'):
return self.plain_text.find_widget
else:
return self.rich_text.find_widget
def switch_to_plain_text(self):
"""Switch to plain text mode."""
self.get_action(HelpWidgetActions.TogglePlainMode).setChecked(True)
def switch_to_rich_text(self):
"""Switch to rich text mode."""
self.get_action(HelpWidgetActions.ToggleRichMode).setChecked(True)
def set_plain_text(self, text, is_code):
"""
Set plain text docs.
Parameters
----------
text: str
Text content.
is_code: bool
True if it is code text.
Notes
-----
Text is coming from utils.dochelpers.getdoc
"""
if type(text) is dict:
name = text['name']
if name:
rst_title = ''.join(['='*len(name), '\n', name, '\n',
'='*len(name), '\n\n'])
else:
rst_title = ''
try:
if text['argspec']:
definition = ''.join(
['Definition: ', name, text['argspec'], '\n\n'])
else:
definition = ''
if text['note']:
note = ''.join(['Type: ', text['note'], '\n\n----\n\n'])
else:
note = ''
except TypeError:
definition = self.no_docs
note = ''
full_text = ''.join([rst_title, definition, note,
text['docstring']])
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.
Parameters
----------
html_text: str
Html string.
base_url: str
Location of stylesheets and images to load in the page.
"""
self.rich_text.set_html(html_text, base_url)
self.save_text([self.rich_text.set_html, html_text, base_url])
def show_loading_message(self):
"""Create html page to show while the documentation is generated."""
self.sig_render_started.emit()
loading_message = _("Retrieving documentation")
loading_img = get_image_path('loading_sprites')
if os.name == 'nt':
loading_img = loading_img.replace('\\', '/')
self.set_rich_text_html(
loading(loading_message, loading_img, css_path=self.css_path),
QUrl.fromLocalFile(self.css_path),
)
def show_intro_message(self):
"""Show message on Help with the right shortcuts."""
intro_message_eq = _(
"Here you can get help of any object by pressing "
"%s in front of it, either on the Editor or the "
"Console.%s")
intro_message_dif = _(
"Here you can get help of any object by pressing "
"%s in front of it on the Editor, or %s in front "
"of it on the Console.%s")
intro_message_common = _(
"Help can also be shown automatically after writing "
"a left parenthesis next to an object. You can "
"activate this behavior in %s.")
prefs = _("Preferences > Help")
shortcut_editor = self.get_conf('editor/inspect current object',
section='shortcuts')
shortcut_console = self.get_conf('console/inspect current object',
section='shortcuts')
if sys.platform == 'darwin':
shortcut_editor = shortcut_editor.replace('Ctrl', 'Cmd')
shortcut_console = shortcut_console.replace('Ctrl', 'Cmd')
if self.get_conf('rich_mode'):
title = _("Usage")
tutorial_message = _("New to Spyder? Read our")
tutorial = _("tutorial")
if shortcut_editor == shortcut_console:
intro_message = (intro_message_eq + intro_message_common) % (
""+shortcut_editor+"", "
",
""+prefs+"")
else:
intro_message = (intro_message_dif + intro_message_common) % (
""+shortcut_editor+"",
""+shortcut_console+"",
"
", ""+prefs+"")
self.set_rich_text_html(usage(title, intro_message,
tutorial_message, tutorial,
css_path=self.css_path),
QUrl.fromLocalFile(self.css_path))
else:
install_sphinx = "\n\n%s" % _("Please consider installing Sphinx "
"to get documentation rendered in "
"rich text.")
if shortcut_editor == shortcut_console:
intro_message = (intro_message_eq + intro_message_common) % (
shortcut_editor, "\n\n", prefs)
else:
intro_message = (intro_message_dif + intro_message_common) % (
shortcut_editor, shortcut_console, "\n\n", prefs)
intro_message += install_sphinx
self.set_plain_text(intro_message, is_code=False)
def show_rich_text(self, text, collapse=False, img_path=''):
"""
Show text in rich mode.
Parameters
----------
text: str
Plain text to display.
collapse: bool, optional
Show collapsable sections as collapsed/expanded. Default is False.
img_path: str, optional
Path to folder with additional images needed to correctly
display the rich text help. Default is ''.
"""
self.switch_to_rich_text()
context = generate_context(collapse=collapse, img_path=img_path,
css_path=self.css_path)
self.render_sphinx_doc(text, context)
def show_plain_text(self, text):
"""
Show text in plain mode.
Parameters
----------
text: str
Plain text to display.
"""
self.switch_to_plain_text()
self.set_plain_text(text, is_code=False)
@Slot()
def show_tutorial(self):
"""Show the Spyder tutorial."""
tutorial_path = get_module_source_path('spyder.plugins.help.utils')
tutorial = os.path.join(tutorial_path, 'tutorial.rst')
with open(tutorial, 'r') as fh:
text = fh.read()
self.show_rich_text(text, collapse=True)
def handle_link_clicks(self, url):
"""
Handle how url links should be opened.
Parameters
----------
url: QUrl
QUrl object containing the link to open.
"""
url = to_text_string(url.toString())
if url == "spy://tutorial":
self.show_tutorial()
elif url.startswith('http'):
start_file(url)
else:
self.rich_text.load_url(url)
@Slot()
@Slot(bool)
@Slot(bool, bool)
def force_refresh(self, valid=True, editing=True):
"""
Force a refresh/rerender of the help viewer content.
Parameters
----------
valid: bool, optional
Default is True.
editing: bool, optional
Default is True.
"""
if valid:
if self.source_is_console():
self.set_object_text(None, force_refresh=True)
elif self._last_editor_doc is not None:
self.set_editor_doc(self._last_editor_doc, force_refresh=True)
def set_object_text(self, text, force_refresh=False, ignore_unknown=False):
"""
Set object's name in Help's combobox.
Parameters
----------
text: str
Object name.
force_refresh: bool, optional
Force a refresh with the rendering.
ignore_unknown: bool, optional
Ignore not found object names.
See Also
--------
:py:meth:spyder.widgets.mixins.GetHelpMixin.show_object_info
"""
if self.get_conf('locked') and not force_refresh:
return
self.switch_to_console_source()
add_to_combo = True
if text is None:
text = to_text_string(self.object_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.object_combo.add_text(text)
if found:
self.sig_item_found.emit()
index = self.source_combo.currentIndex()
self._last_texts[index] = text
def set_editor_doc(self, help_data, force_refresh=False):
"""
Set content for help data sent from the editor.
Parameters
----------
help_data: dict
Dictionary with editor introspection information.
force_refresh: bool, optional
Force a refresh with the rendering.
Examples
--------
>>> help_data = {
'obj_text': str,
'name': str,
'argspec': str,
'note': str,
'docstring': str,
'path': str,
}
"""
if self.get_conf('locked') and not force_refresh:
return
self.switch_to_editor_source()
self._last_editor_doc = help_data
self.object_edit.setText(help_data['obj_text'])
if self.get_conf('rich_mode'):
self.render_sphinx_doc(help_data)
else:
self.set_plain_text(help_data, is_code=False)
index = self.source_combo.currentIndex()
self._last_texts[index] = help_data['docstring']
def set_shell(self, shell):
"""
Bind to shell.
Parameters
----------
shell: object
internal shell or ipython console shell
"""
self.shell = shell
def get_shell(self):
"""
Return shell which is currently bound to Help.
"""
if self.shell is None:
self.shell = self.internal_shell
return self.shell
def render_sphinx_doc(self, help_data, context=None, css_path=CSS_PATH):
"""
Transform help_data dictionary to HTML and show it.
Parameters
----------
help_data: str or dict
Dictionary with editor introspection information.
context: dict
Sphinx context.
css_path: str
Path to CSS file for styling.
"""
if isinstance(help_data, dict):
path = help_data.pop('path', '')
dname = os.path.dirname(path)
else:
dname = ''
# Math rendering option could have changed
self._sphinx_thread.render(help_data, context, self.get_conf('math'),
dname, css_path=self.css_path)
self.show_loading_message()
def show_help(self, obj_text, ignore_unknown=False):
"""
Show help for an object's name.
Parameters
----------
obj_text: str
Object's name.
ignore_unknown: bool, optional
Ignore unknown object's name.
"""
# TODO: This method makes active use of the shells. It would be better
# to use signals and pass information this way for better decoupling.
shell = self.get_shell()
if shell is None:
return
obj_text = to_text_string(obj_text)
if not shell.is_defined(obj_text):
if (self.get_conf('automatic_import')
and self.internal_shell.is_defined(obj_text,
force_import=True)):
shell = self.internal_shell
else:
shell = None
doc = None
source_text = None
if shell is not None:
doc = shell.get_doc(obj_text)
source_text = shell.get_source(obj_text)
is_code = False
if self.get_conf('rich_mode'):
self.render_sphinx_doc(doc, css_path=self.css_path)
return doc is not None
elif self.docstring:
hlp_text = doc
if hlp_text is None:
hlp_text = source_text
if hlp_text is None:
return False
else:
hlp_text = source_text
if hlp_text is None:
hlp_text = doc
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
def set_rich_text_font(self, font, fixed_font):
"""
Set rich text mode font.
Parameters
----------
fixed_font: QFont
The current rich text font to use.
"""
self.rich_text.set_font(font, fixed_font=fixed_font)
def set_plain_text_font(self, font, color_scheme=None):
"""
Set plain text mode font.
Parameters
----------
font: QFont
The current plain text font to use.
color_scheme: str
The selected color scheme.
"""
if color_scheme is None:
color_scheme = self._current_color_scheme
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.
Parameters
----------
color_scheme: str
The selected color scheme.
"""
self._current_color_scheme = color_scheme
self.plain_text.set_color_scheme(color_scheme)
def set_history(self, history):
"""
Set list of strings on object combo box.
Parameters
----------
history: list
List of strings of objects.
"""
self.object_combo.addItems(history)
def get_history(self):
"""
Return list of strings on object combo box.
"""
history = []
for index in range(self.object_combo.count()):
history.append(to_text_string(self.object_combo.itemText(index)))
return history
def set_internal_console(self, console):
"""
Set the internal console shell.
Parameters
----------
console: :py:class:spyder.plugins.console.plugin.Console
Console plugin.
"""
self.internal_console = console
self.internal_shell = console.get_widget().shell