# -*- coding: utf-8 -*- # Copyright © Spyder Project Contributors # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) """Kite completion HTTP client.""" # Standard library imports import logging import functools import os import os.path as osp # Qt imports from qtpy.QtCore import Slot from qtpy.QtWidgets import QMessageBox # Local imports from spyder.api.config.decorators import on_conf_change from spyder.config.base import _, running_under_pytest from spyder.plugins.completion.api import SpyderCompletionProvider from spyder.plugins.mainmenu.api import ApplicationMenus, ToolsMenuSections from spyder.plugins.completion.providers.kite.client import KiteClient from spyder.plugins.completion.providers.kite.utils.status import ( check_if_kite_running, check_if_kite_installed, check_kite_installers_availability) from spyder.plugins.completion.providers.kite.widgets import ( KiteInstallationErrorMessage, KiteStatusWidget) from spyder.utils.icon_manager import ima from spyder.utils.programs import run_program logger = logging.getLogger(__name__) class KiteProviderActions: Installation = 'kite_installation' class KiteProvider(SpyderCompletionProvider): COMPLETION_PROVIDER_NAME = 'kite' DEFAULT_ORDER = 1 SLOW = True CONF_DEFAULTS = [ ('spyder_runs', 1), ('show_installation_dialog', True), ('show_onboarding', True), ('show_installation_error_message', True), ('installers_available', True) ] CONF_VERSION = "0.1.0" def __init__(self, parent, config): super().__init__(parent, config) self.available_languages = [] self.client = KiteClient(None) self.kite_process = None # Signals self.client.sig_client_started.connect(self.http_client_ready) self.client.sig_status_response_ready[str].connect( self.set_status) self.client.sig_status_response_ready[dict].connect( self.set_status) self.client.sig_response_ready.connect( functools.partial(self.sig_response_ready.emit, self.COMPLETION_PROVIDER_NAME)) self.client.sig_client_wrong_response.connect( self._wrong_response_error) # Status bar widget self.STATUS_BAR_CLASSES = [ self.create_statusbar ] # Config self.update_kite_configuration(self.config) # Menus self.setup_menus() # ------------------ SpyderCompletionProvider methods --------------------- def get_name(self): return 'Kite' def send_request(self, language, req_type, req, req_id): if language in self.available_languages: self.client.sig_perform_request.emit(req_id, req_type, req) else: self.sig_response_ready.emit(self.COMPLETION_PROVIDER_NAME, req_id, {}) def start_completion_services_for_language(self, language): return language in self.available_languages def start(self): try: installed, path = check_if_kite_installed() if not installed: return logger.debug('Kite was found on the system: {0}'.format(path)) running = check_if_kite_running() if running: return logger.debug('Starting Kite service...') self.kite_process = run_program(path) except OSError: installed, path = check_if_kite_installed() logger.debug( 'Error starting Kite service at {path}...'.format(path=path)) if self.get_conf('show_installation_error_message'): err_str = _( "It seems that your Kite installation is faulty. " "If you want to use Kite, please remove the " "directory that appears bellow, " "and try a reinstallation:

" "{kite_dir}").format( kite_dir=osp.dirname(path)) dialog_wrapper = KiteInstallationErrorMessage.instance( err_str, self.set_conf) self.sig_show_widget.emit(dialog_wrapper) finally: # Always start client to support possibly undetected Kite builds self.client.start() def shutdown(self): self.client.stop() if self.kite_process is not None: self.kite_process.kill() def on_mainwindow_visible(self): self.sig_call_statusbar.emit( KiteStatusWidget.ID, 'mainwindow_setup_finished', tuple(), {}) self.client.sig_response_ready.connect(self._kite_onboarding) self.client.sig_status_response_ready.connect(self._kite_onboarding) self.client.sig_onboarding_response_ready.connect( self._show_onboarding_file) @Slot(list) def http_client_ready(self, languages): logger.debug('Kite client is available for {0}'.format(languages)) self.available_languages = languages self.sig_provider_ready.emit(self.COMPLETION_PROVIDER_NAME) self._kite_onboarding() @Slot(str) @Slot(dict) def set_status(self, status): """Show Kite status for the current file.""" self.sig_call_statusbar.emit( KiteStatusWidget.ID, 'set_value', (status,), {}) def file_opened_closed_or_updated(self, filename, _language): """Request status for the given file.""" self.client.sig_perform_status_request.emit(filename) @on_conf_change( section='completions', option=('enabled_providers', 'kite')) def on_kite_enable_changed(self, value): self.sig_call_statusbar.emit( KiteStatusWidget.ID, 'set_value', (None,), {}) @on_conf_change(section='completions', option='enable_code_snippets') def on_code_snippets_changed(self, value): if running_under_pytest(): if not os.environ.get('SPY_TEST_USE_INTROSPECTION'): return self.client.enable_code_snippets = self.get_conf( 'enable_code_snippets', section='completions') @on_conf_change def update_kite_configuration(self, config): if running_under_pytest(): if not os.environ.get('SPY_TEST_USE_INTROSPECTION'): return self._show_onboarding = self.get_conf('show_onboarding') def _kite_onboarding(self): """Request the onboarding file.""" # No need to check installed status, # since the get_onboarding_file call fails fast. if not self._show_onboarding: return if not self.available_languages: return # Don't send another request until this request fails. self._show_onboarding = False self.client.sig_perform_onboarding_request.emit() @Slot(str) def _show_onboarding_file(self, onboarding_file): """ Opens the onboarding file, which is retrieved from the Kite HTTP endpoint. This skips onboarding if onboarding is not possible yet or has already been displayed before. """ if not onboarding_file: # retry self._show_onboarding = True return self.sig_open_file.emit(onboarding_file) self.set_conf('show_onboarding', False) @Slot(str, object) def _wrong_response_error(self, method, resp): err_msg = _( "The Kite completion engine returned an unexpected result " "for the request {0}:

{1}

" "Please make sure that your Kite installation is correct. " "In the meantime, Spyder will disable the Kite client to " "prevent further errors. For more information, please " "visit the Kite help " "center").format(method, resp) def wrap_message(parent): return QMessageBox.critical(parent, _('Kite error'), err_msg) self.sig_show_widget.emit(wrap_message) self.sig_disable_provider.emit(self.COMPLETION_PROVIDER_NAME) def create_statusbar(self, parent): return KiteStatusWidget(parent, self) def show_kite_installation(self): self.sig_call_statusbar.emit( KiteStatusWidget.ID, 'show_installation_dialog', tuple(), {}) def setup_menus(self): installers_available = check_kite_installers_availability() self.set_conf('installers_available', installers_available) is_kite_installed, kite_path = check_if_kite_installed() if not is_kite_installed and installers_available: install_kite_action = self.create_action( KiteProviderActions.Installation, _("Install Kite completion engine"), icon=ima.icon('kite'), triggered=self.show_kite_installation) self.add_item_to_application_menu( install_kite_action, menu_id=ApplicationMenus.Tools, section=ToolsMenuSections.External, before_section=ToolsMenuSections.Extras)