Skip to content

Commit

Permalink
Migrate PyLint to new API
Browse files Browse the repository at this point in the history
  • Loading branch information
goanpeca committed Apr 7, 2020
1 parent b588134 commit 82cc8b8
Show file tree
Hide file tree
Showing 5 changed files with 454 additions and 256 deletions.
21 changes: 20 additions & 1 deletion spyder/app/mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -1082,6 +1082,7 @@ def create_edit_action(text, tr_text, icon):
from spyder.plugins.editor.plugin import Editor
self.editor = Editor(self)
self.editor.register_plugin()
self._PLUGINS[self.editor.CONF_SECTION] = self.editor

# Start code completion client
self.set_splash(_("Launching code completion client for Python..."))
Expand Down Expand Up @@ -1169,7 +1170,15 @@ def create_edit_action(text, tr_text, icon):
# Load other plugins (former external plugins)
# TODO: Use this bucle to load all internall plugins and remove
# duplicated code
other_plugins = ['breakpoints', 'profiler', 'pylint']

if CONF.get('pylint', 'enable'):
from spyder.plugins.pylint.plugin import PylintPlugin
PylintPlugin._PLUGINS = self._PLUGINS
self.pylint = PylintPlugin(self, configuration=CONF)
self.register_plugin(self.pylint)
self.thirdparty_plugins.append(self.pylint)

other_plugins = ['breakpoints', 'profiler']
for plugin_name in other_plugins:
if CONF.get(plugin_name, 'enable'):
module = importlib.import_module(
Expand Down Expand Up @@ -3464,6 +3473,16 @@ def _dialog_finished(result_code):
# Avoid a crash at startup if a plugin's config
# page fails to load.
traceback.print_exc(file=sys.stderr)
if getattr(plugin, 'CONF_WIDGET_CLASS', None):
try:
widget = self.create_plugin_conf_widget(plugin)
if widget is not None:
dlg.add_page(widget)
except Exception:
# Avoid a crash at startup if a plugin's config
# page fails to load.
traceback.print_exc(file=sys.stderr)


if self.prefs_index is not None:
dlg.set_current_index(self.prefs_index)
Expand Down
2 changes: 1 addition & 1 deletion spyder/plugins/pylint/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@
# The following statement is required to register this 3rd party plugin:
# =============================================================================

from .plugin import Pylint as PLUGIN_CLASS
from .plugin import PylintPlugin as PLUGIN_CLASS
257 changes: 145 additions & 112 deletions spyder/plugins/pylint/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@
import os.path as osp

# Third party imports
from qtpy.QtCore import Slot
from qtpy.QtWidgets import QInputDialog, QVBoxLayout
from qtpy.QtCore import Slot, Qt, Signal
from qtpy.QtWidgets import QInputDialog, QVBoxLayout, QWidget, QLabel

# Local imports
from spyder.widgets.comboboxes import (is_module_or_package,
PythonModulesComboBox)
from spyder.config.base import _
from spyder.config.gui import is_dark_interface
from spyder.api.plugins import SpyderPluginWidget
from spyder.utils import icon_manager as ima
from spyder.api.plugins import SpyderPluginWidget, SpyderDockablePlugin
from spyder.api.widgets import PluginMainWidget
from spyder.utils.programs import is_module_installed
from spyder.utils.qthelpers import create_action, MENU_SEPARATOR
from spyder.plugins.pylint.confpage import PylintConfigPage
Expand All @@ -38,135 +40,166 @@
MAIN_PREVRATE_COLOR = '#666666'


class Pylint(SpyderPluginWidget):
"""Python source code analysis based on pylint."""
# def register_plugin(self):
# """Register plugin in Spyder's main window"""
# self.pylint.treewidget.sig_edit_goto.connect(self.main.editor.load)
# self.pylint.redirect_stdio.connect(
# self.main.redirect_internalshell_stdio)
# self.add_dockwidget()

CONF_SECTION = 'pylint'
CONFIGWIDGET_CLASS = PylintConfigPage
CONF_FILE = False
# pylint_act = create_action(self, _("Run static code analysis"),
# triggered=self.run_pylint)
# pylint_act.setEnabled(is_module_installed('pylint'))
# self.register_shortcut(pylint_act, context="Pylint",
# name="Run analysis")

def __init__(self, parent=None):
super().__init__(parent)
# self.main.source_menu_actions += [MENU_SEPARATOR, pylint_act]

max_entries = self.get_option('max_entries', 50)
self.pylint = PylintWidget(self, max_entries=max_entries,
options_button=self.options_button,
text_color=MAIN_TEXT_COLOR,
prevrate_color=MAIN_PREVRATE_COLOR)

layout = QVBoxLayout()
layout.addWidget(self.pylint)
self.setLayout(layout)
# def refresh_plugin(self):
# """Refresh pylint widget"""
# self.pylint.remove_obsolete_items()

# Add history_action to treewidget context menu
history_action = create_action(self, _("History..."),
None, ima.icon('history'),
_("Set history maximum entries"),
triggered=self.change_history_depth)
self.pylint.treewidget.common_actions += (None, history_action)
# # ----- Public API --------------------------------------------------------

# Follow editorstacks tab change
self.main.editor.sig_editor_focus_changed.connect(self.set_filename)
# @Slot()
# def set_filename(self):
# """Set filename without code analysis."""
# self.pylint.set_filename(self.main.editor.get_current_filename())

# Used by Analyze button to check if file should be saved and start
# analysis
self.pylint.start_analysis.connect(self.run_pylint_from_analyze_button)
# @Slot()
# def run_pylint(self):
# """Run pylint code analysis"""
# if (self.get_option('save_before', True)
# and not self.main.editor.save()):
# return
# self.switch_to_plugin()
# self.analyze(self.main.editor.get_current_filename())

# ------ SpyderPluginWidget API -------------------------------------------
def get_plugin_title(self):
"""Return widget title"""
return _("Code Analysis")
# def analyze(self, filename):
# """Reimplement analyze method"""
# if self.dockwidget:
# self.switch_to_plugin()
# self.pylint.analyze(filename)

def get_plugin_icon(self):
"""Return widget icon"""
path = osp.join(self.PLUGIN_PATH, self.IMG_PATH)
return ima.icon('pylint', icon_path=path)
# @Slot()
# def run_pylint_from_analyze_button(self):
# """
# See if file should and can be saved and run pylint code analysis.

def get_focus_widget(self):
"""
Return the widget to give focus to when
this plugin's dockwidget is raised on top-level
"""
return self.pylint.treewidget

def get_plugin_actions(self):
"""Return a list of actions related to plugin"""
return self.pylint.treewidget.get_menu_actions()

def on_first_registration(self):
"""Action to be performed on first plugin registration"""
self.tabify(self.main.help)
self.dockwidget.hide()

def register_plugin(self):
"""Register plugin in Spyder's main window"""
self.pylint.treewidget.sig_edit_goto.connect(self.main.editor.load)
self.pylint.redirect_stdio.connect(
self.main.redirect_internalshell_stdio)
self.add_dockwidget()

pylint_act = create_action(self, _("Run static code analysis"),
triggered=self.run_pylint)
pylint_act.setEnabled(is_module_installed('pylint'))
self.register_shortcut(pylint_act, context="Pylint",
name="Run analysis")

self.main.source_menu_actions += [MENU_SEPARATOR, pylint_act]
self.main.editor.pythonfile_dependent_actions += [pylint_act]

def refresh_plugin(self):
"""Refresh pylint widget"""
self.pylint.remove_obsolete_items()

def apply_plugin_settings(self, options):
"""Apply configuration file's plugin settings"""
# The history depth option will be applied at
# next Spyder startup, which is soon enough
pass

# ----- Public API --------------------------------------------------------
@Slot()
def change_history_depth(self):
"Change history max entries"""
depth, valid = QInputDialog.getInt(
self, _('History'), _('Maximum entries'),
self.get_option('max_entries'), 10, 10000)
if valid:
self.set_option('max_entries', depth)
# Does not check that file name is valid etc, so should only be used for
# Analyze button.
# """
# if (self.get_option('save_before', True)
# and not self.main.editor.save()):
# return
# self.pylint.start()

def get_filename(self):
"""Get current filename in combobox."""
return self.pylint.get_filename()

##############################################################################
#
# New API
#
##############################################################################
class PylintPlugin(SpyderDockablePlugin):
"""
Code analysis plugin.
"""
NAME = 'pylint'
REQUIRES = ['variable_explorer']
OPTIONAL = ['editor']
TABIFY = 'help'
WIDGET_CLASS = PylintWidget
CONF_SECTION = NAME # TODO: move to use NAME and remove CONF_SECTION?
CONF_FILE = False
CONF_WIDGET_CLASS = PylintConfigPage
DISABLE_ACTIONS_WHEN_HIDDEN = False

# Signals
sig_finished = Signal()
sig_edit_goto_requested = Signal(str)
sig_started = Signal()

def get_name(self):
return _('Code Analysis')

def get_description(self):
return _('Run code quality analysis on files.')

def get_icon(self):
path = osp.join(self.get_path(), self.IMG_PATH)
return self.create_icon('pylint', path=path)

def register(self):
widget = self.get_widget()

# Plugins
editor = self.get_plugin('editor')

# Actions
run_action = self.get_action('run_analysis')
source_menu = self.get_application_menu('source')

# FIXME: Currently added to top
self.add_item_to_application_menu(
run_action,
menu=source_menu,
section='code_analysis',
)

# Signals
if editor:
# TODO: Move to new API
editor.pythonfile_dependent_actions += [run_action]
# TODO: This signal could emit information?
editor.sig_editor_focus_changed.connect(self.set_filename)
widget.sig_edit_goto_requested.connect(editor.load)

widget.sig_redirect_stdio_requested.connect(
self.sig_redirect_stdio_requested
)
widget.sig_edit_goto_requested.connect(self.sig_edit_goto_requested)
widget.sig_started.connect(self.run)

def unregister(self):
# Plugins
editor = self.get_plugin('editor')

# Signals
if editor:
editor.sig_editor_focus_changed.disconnect(self.set_filename)
# self.pylint.treewidget.sig_edit_goto.connect(self.main.editor.load)
# self.pylint.redirect_stdio.connect(self.main.redirect_internalshell_stdio)

# --- API: Main widget
# ------------------------------------------------------------------------
@Slot()
def set_filename(self):
"""Set filename without code analysis."""
self.pylint.set_filename(self.main.editor.get_current_filename())
"""
Set filename without code analysis.
"""
editor = self.get_plugin('editor')
widget = self.get_widget()

@Slot()
def run_pylint(self):
"""Run pylint code analysis"""
if (self.get_option('save_before', True)
and not self.main.editor.save()):
return
self.switch_to_plugin()
self.analyze(self.main.editor.get_current_filename())
if editor:
current_filename = editor.get_current_filename()
widget.set_filename(current_filename)

def analyze(self, filename):
"""Reimplement analyze method"""
if self.dockwidget:
self.switch_to_plugin()
self.pylint.analyze(filename)
def get_filename(self):
"""
Get current filename in combobox.
"""
return self.get_widget().get_filename()

@Slot()
def run_pylint_from_analyze_button(self):
def run(self):
"""
See if file should and can be saved and run pylint code analysis.
Does not check that file name is valid etc, so should only be used for
Analyze button.
"""
if (self.get_option('save_before', True)
and not self.main.editor.save()):
editor = self.get_plugin('editor')
if self.get_option('save_before', True) and not editor.save():
return
self.pylint.start()

self.get_widget().run()
Loading

0 comments on commit 82cc8b8

Please sign in to comment.