diff --git a/spyder/api/config/decorators.py b/spyder/api/config/decorators.py index 1325b63522f..dfd3031910e 100644 --- a/spyder/api/config/decorators.py +++ b/spyder/api/config/decorators.py @@ -10,8 +10,7 @@ # Standard library imports import functools -from typing import Callable, Type, Any, Optional, Union, List -import inspect +from typing import Callable, Optional, Union, List # Local imports from spyder.config.types import ConfigurationKey diff --git a/spyder/api/preferences.py b/spyder/api/preferences.py index 70602e81d4b..83c1a0a8213 100644 --- a/spyder/api/preferences.py +++ b/spyder/api/preferences.py @@ -10,10 +10,7 @@ # Standard library imports import types -from typing import Tuple, Union, Set - -# Third party imports -from qtpy.QtWidgets import QWidget +from typing import Set # Local imports from spyder.config.manager import CONF diff --git a/spyder/plugins/completion/plugin.py b/spyder/plugins/completion/plugin.py index 8e7cb79834a..929d86cd1d3 100644 --- a/spyder/plugins/completion/plugin.py +++ b/spyder/plugins/completion/plugin.py @@ -12,17 +12,15 @@ """ # Standard library imports -from collections import defaultdict import functools import inspect import logging import os from pkg_resources import parse_version, iter_entry_points -from typing import List, Any, Union, Optional, Tuple +from typing import List, Union # Third-party imports from qtpy.QtCore import QMutex, QMutexLocker, QTimer, Slot, Signal -from qtpy.QtWidgets import QMessageBox # Local imports from spyder.config.manager import CONF @@ -388,8 +386,11 @@ def after_configuration_update(self, options: List[Union[tuple, str]]): provider tabs. """ providers_to_update = set({}) - options = [x[1] if isinstance(x, tuple) and - len(x) == 2 and x[0] is None else x for x in options] + options = [ + x[1] if isinstance(x, tuple) and + len(x) == 2 and x[0] is None or 'editor' + else x for x in options + ] for option in options: if option == 'completions_wait_for_ms': self.wait_for_ms = self.get_conf( @@ -408,49 +409,6 @@ def after_configuration_update(self, options: List[Union[tuple, str]]): elif option_name == 'provider_configuration': providers_to_update |= {provider_name} - # FIXME: Remove this after migrating the ConfManager to an observer - # pattern. - editor_method_sec_opts = { - 'set_code_snippets_enabled': (self.CONF_SECTION, - 'enable_code_snippets'), - 'set_hover_hints_enabled': (self.CONF_SECTION, - 'provider_configuration', - 'lsp', - 'values', - 'enable_hover_hints'), - 'set_format_on_save': (self.CONF_SECTION, - 'provider_configuration', - 'lsp', - 'values', - 'format_on_save'), - 'set_automatic_completions_enabled': ('editor', - 'automatic_completions'), - 'set_completions_hint_enabled': ('editor', 'completions_hint'), - 'set_completions_hint_after_ms': ('editor', - 'completions_hint_after_ms'), - 'set_underline_errors_enabled': ('editor', 'underline_errors'), - 'set_automatic_completions_after_chars': ( - 'editor', 'automatic_completions_after_chars'), - 'set_automatic_completions_after_ms': ( - 'editor', 'automatic_completions_after_ms'), - 'set_edgeline_columns': (self.CONF_SECTION, - 'provider_configuration', - 'lsp', - 'values', - 'pycodestyle/max_line_length'), - 'set_edgeline_enabled': ('editor', 'edge_line'), - } - - for method_name, (sec, *opt) in editor_method_sec_opts.items(): - opt = tuple(opt) - if len(opt) == 1: - opt = opt[0] - if opt in options: - opt_value = self.get_conf(opt, section=sec) - self.sig_editor_rpc.emit('call_all_editorstacks', - (method_name, (opt_value,),), - {}) - # Update entries in the source menu # FIXME: Delete this after CONF is moved to an observer pattern. # and the editor migration starts diff --git a/spyder/plugins/editor/plugin.py b/spyder/plugins/editor/plugin.py index 519cc225289..3449246cf59 100644 --- a/spyder/plugins/editor/plugin.py +++ b/spyder/plugins/editor/plugin.py @@ -28,6 +28,8 @@ QToolBar, QVBoxLayout, QWidget) # Local imports +from spyder.api.config.decorators import on_conf_change +from spyder.api.config.mixins import SpyderConfigurationObserver from spyder.api.panel import Panel from spyder.api.plugins import Plugins, SpyderPluginWidget from spyder.config.base import _, get_conf_path, running_under_pytest @@ -64,7 +66,7 @@ logger = logging.getLogger(__name__) -class Editor(SpyderPluginWidget): +class Editor(SpyderPluginWidget, SpyderConfigurationObserver): """ Multi-file Editor widget """ @@ -1454,7 +1456,6 @@ def register_editorstack(self, editorstack): ('set_scrollpastend_enabled', 'scroll_past_end'), ('set_linenumbers_enabled', 'line_numbers'), ('set_edgeline_enabled', 'edge_line'), - ('set_edgeline_columns', 'edge_line_columns'), ('set_indent_guides', 'indent_guides'), ('set_code_folding_enabled', 'code_folding'), ('set_focus_to_editor', 'focus_to_editor'), @@ -1509,8 +1510,16 @@ def register_editorstack(self, editorstack): False ) + edge_line_columns = CONF.get( + 'completions', + ('provider_configuration', 'lsp', 'values', + 'pycodestyle/max_line_length'), + 79 + ) + editorstack.set_hover_hints_enabled(hover_hints) editorstack.set_format_on_save(format_on_save) + editorstack.set_edgeline_columns(edge_line_columns) color_scheme = self.get_color_scheme() editorstack.set_default_font(self.get_font(), color_scheme) @@ -1630,12 +1639,6 @@ def file_renamed_in_data_in_editorstack(self, editorstack_id_str, if str(id(editorstack)) != editorstack_id_str: editorstack.rename_in_data(original_filename, filename) - def call_all_editorstacks(self, method, args, **kwargs): - """Call a method with arguments on all editorstacks.""" - for editorstack in self.editorstacks: - method = getattr(editorstack, method) - method(*args, **kwargs) - #------ Handling editor windows def setup_other_windows(self): """Setup toolbars and menus for 'New window' instances""" @@ -3005,6 +3008,9 @@ def zoom(self, factor): def apply_plugin_settings(self, options): """Apply configuration file's plugin settings""" if self.editorstacks is not None: + # Get option names from the tuples sent by Preferences + options = list({option[1] for option in options}) + # --- syntax highlight and text rendering settings color_scheme_n = 'color_scheme_name' color_scheme_o = self.get_color_scheme() @@ -3047,10 +3053,6 @@ def apply_plugin_settings(self, options): blanks_o = self.get_option(blanks_n) scrollpastend_n = 'scroll_past_end' scrollpastend_o = self.get_option(scrollpastend_n) - edgeline_n = 'edge_line' - edgeline_o = self.get_option(edgeline_n) - edgelinecols_n = 'edge_line_columns' - edgelinecols_o = self.get_option(edgelinecols_n) wrap_n = 'wrap' wrap_o = self.get_option(wrap_n) indentguides_n = 'indent_guides' @@ -3063,10 +3065,6 @@ def apply_plugin_settings(self, options): stripindent_o = self.get_option(stripindent_n) ibackspace_n = 'intelligent_backspace' ibackspace_o = self.get_option(ibackspace_n) - autocompletions_n = 'automatic_completions' - autocompletions_o = self.get_option(autocompletions_n) - completionshint_n = 'completions_hint' - completionshint_o = self.get_option(completionshint_n) removetrail_n = 'always_remove_trailing_spaces' removetrail_o = self.get_option(removetrail_n) add_newline_n = 'add_newline' @@ -3098,7 +3096,6 @@ def apply_plugin_settings(self, options): finfo = self.get_current_finfo() - for editorstack in self.editorstacks: # Checkable options if blanks_n in options: @@ -3112,21 +3109,11 @@ def apply_plugin_settings(self, options): if classfuncdropdown_n in options: editorstack.set_classfunc_dropdown_visible( classfuncdropdown_o) - if tabbar_n in options: editorstack.set_tabbar_visible(tabbar_o) if linenb_n in options: editorstack.set_linenumbers_enabled(linenb_o, current_finfo=finfo) - if autocompletions_n in options: - editorstack.set_automatic_completions_enabled( - autocompletions_o) - if completionshint_n in options: - editorstack.set_completions_hint_enabled(completionshint_o) - if edgeline_n in options: - editorstack.set_edgeline_enabled(edgeline_o) - if edgelinecols_n in options: - editorstack.set_edgeline_columns(edgelinecols_o) if wrap_n in options: editorstack.set_wrap_enabled(wrap_o) if tabindent_n in options: @@ -3187,6 +3174,94 @@ def apply_plugin_settings(self, options): if todo_n in options and todo_o: finfo.run_todo_finder() + @on_conf_change(option='edge_line') + def set_edgeline_enabled(self, value): + if self.editorstacks is not None: + logger.debug(f"Set edge line to {value}") + for editorstack in self.editorstacks: + editorstack.set_edgeline_enabled(value) + + @on_conf_change( + option=('provider_configuration', 'lsp', 'values', + 'pycodestyle/max_line_length'), + section='completions' + ) + def set_edgeline_columns(self, value): + if self.editorstacks is not None: + logger.debug(f"Set edge line columns to {value}") + for editorstack in self.editorstacks: + editorstack.set_edgeline_columns(value) + + @on_conf_change(option='enable_code_snippets', section='completions') + def set_code_snippets_enabled(self, value): + if self.editorstacks is not None: + logger.debug(f"Set code snippets to {value}") + for editorstack in self.editorstacks: + editorstack.set_code_snippets_enabled(value) + + @on_conf_change(option='automatic_completions') + def set_automatic_completions_enabled(self, value): + if self.editorstacks is not None: + logger.debug(f"Set automatic completions to {value}") + for editorstack in self.editorstacks: + editorstack.set_automatic_completions_enabled(value) + + @on_conf_change(option='automatic_completions_after_chars') + def set_automatic_completions_after_chars(self, value): + if self.editorstacks is not None: + logger.debug(f"Set chars for automatic completions to {value}") + for editorstack in self.editorstacks: + editorstack.set_automatic_completions_after_chars(value) + + @on_conf_change(option='automatic_completions_after_ms') + def set_automatic_completions_after_ms(self, value): + if self.editorstacks is not None: + logger.debug(f"Set automatic completions after {value} ms") + for editorstack in self.editorstacks: + editorstack.set_automatic_completions_after_ms(value) + + @on_conf_change(option='completions_hint') + def set_completions_hint_enabled(self, value): + if self.editorstacks is not None: + logger.debug(f"Set completions hint to {value}") + for editorstack in self.editorstacks: + editorstack.set_completions_hint_enabled(value) + + @on_conf_change(option='completions_hint_after_ms') + def set_completions_hint_after_ms(self, value): + if self.editorstacks is not None: + logger.debug(f"Set completions hint after {value} ms") + for editorstack in self.editorstacks: + editorstack.set_completions_hint_after_ms(value) + + @on_conf_change( + option=('provider_configuration', 'lsp', 'values', + 'enable_hover_hints'), + section='completions' + ) + def set_hover_hints_enabled(self, value): + if self.editorstacks is not None: + logger.debug(f"Set hover hints to {value}") + for editorstack in self.editorstacks: + editorstack.set_hover_hints_enabled(value) + + @on_conf_change( + option=('provider_configuration', 'lsp', 'values', 'format_on_save'), + section='completions' + ) + def set_format_on_save(self, value): + if self.editorstacks is not None: + logger.debug(f"Set format on save to {value}") + for editorstack in self.editorstacks: + editorstack.set_format_on_save(value) + + @on_conf_change(option='underline_errors') + def set_underline_errors_enabled(self, value): + if self.editorstacks is not None: + logger.debug(f"Set underline errors to {value}") + for editorstack in self.editorstacks: + editorstack.set_underline_errors_enabled(value) + # --- Open files def get_open_filenames(self): """Get the list of open files in the current stack""" diff --git a/spyder/plugins/editor/widgets/codeeditor.py b/spyder/plugins/editor/widgets/codeeditor.py index ae97518e1df..ade8c9a0aa5 100644 --- a/spyder/plugins/editor/widgets/codeeditor.py +++ b/spyder/plugins/editor/widgets/codeeditor.py @@ -4472,7 +4472,10 @@ def event(self, event): return super(CodeEditor, self).event(event) def _start_completion_timer(self): - """Helper to start timer or complete.""" + """Helper to start timer for automatic completions or handle them.""" + if not self.automatic_completions: + return + if self.automatic_completions_after_ms > 0: self._timer_autocomplete.start( self.automatic_completions_after_ms) @@ -4633,7 +4636,8 @@ def keyPressEvent(self, event): # redefine this basic action which should have been implemented # natively self.stdkey_end(shift, ctrl) - elif text in self.auto_completion_characters: + elif (text in self.auto_completion_characters and + self.automatic_completions): self.insert_text(text) if text == ".": if not self.in_comment_or_string(): @@ -4711,6 +4715,9 @@ def keyPressEvent(self, event): def _handle_completions(self): """Handle on the fly completions after delay.""" + if not self.automatic_completions: + return + cursor = self.textCursor() pos = cursor.position() cursor.select(QTextCursor.WordUnderCursor) @@ -4758,7 +4765,7 @@ def _handle_completions(self): if (len(text) >= self.automatic_completions_after_chars and self._last_key_pressed_text or is_backspace): # Perform completion on the fly - if self.automatic_completions and not self.in_comment_or_string(): + if not self.in_comment_or_string(): # Variables can include numbers and underscores if (text.isalpha() or text.isalnum() or '_' in text or '.' in text): diff --git a/spyder/plugins/preferences/api.py b/spyder/plugins/preferences/api.py index 6f239fc63ec..c25a2dadfe7 100644 --- a/spyder/plugins/preferences/api.py +++ b/spyder/plugins/preferences/api.py @@ -10,25 +10,22 @@ # Standard library imports import ast -import functools import os.path as osp # Third party imports from qtpy import API from qtpy.compat import (getexistingdirectory, getopenfilename, from_qvariant, to_qvariant) -from qtpy.QtCore import QSize, Qt, Signal, Slot, QRegExp +from qtpy.QtCore import Qt, Signal, Slot, QRegExp from qtpy.QtGui import QColor, QRegExpValidator, QTextOption -from qtpy.QtWidgets import (QButtonGroup, QCheckBox, QComboBox, QDialog, - QDialogButtonBox, QDoubleSpinBox, QFontComboBox, - QGridLayout, QGroupBox, QHBoxLayout, QLabel, - QLineEdit, QListView, QListWidget, QListWidgetItem, - QMessageBox, QPushButton, QRadioButton, - QScrollArea, QSpinBox, QSplitter, QStackedWidget, - QVBoxLayout, QWidget, QPlainTextEdit, QTabWidget) +from qtpy.QtWidgets import (QButtonGroup, QCheckBox, QComboBox, QDoubleSpinBox, + QFontComboBox, QGridLayout, QGroupBox, QHBoxLayout, + QLabel, QLineEdit, QMessageBox, QPushButton, + QRadioButton, QSpinBox, QVBoxLayout, QWidget, + QPlainTextEdit, QTabWidget) # Local imports -from spyder.config.base import _, load_lang_conf +from spyder.config.base import _ from spyder.config.manager import CONF from spyder.config.user import NoDefault from spyder.py3compat import to_text_string