Mon, 02 Oct 2017 14:24:58 +0200
Implemented support for asynchroneous completion lists in the editor.
--- a/Preferences/ConfigurationPages/EditorAutocompletionPage.py Wed Sep 27 18:00:37 2017 +0200 +++ b/Preferences/ConfigurationPages/EditorAutocompletionPage.py Mon Oct 02 14:24:58 2017 +0200 @@ -15,6 +15,7 @@ import Preferences +# TODO: add entry for auto-completion timeout class EditorAutocompletionPage(ConfigurationPageBase, Ui_EditorAutocompletionPage): """ @@ -33,12 +34,14 @@ Preferences.getEditor("AutoCompletionEnabled")) self.acCaseSensitivityCheckBox.setChecked( Preferences.getEditor("AutoCompletionCaseSensitivity")) + self.acReversedCheckBox.setChecked( + Preferences.getEditor("AutoCompletionReversedList")) self.acReplaceWordCheckBox.setChecked( Preferences.getEditor("AutoCompletionReplaceWord")) self.acThresholdSlider.setValue( Preferences.getEditor("AutoCompletionThreshold")) - self.acScintillaCheckBox.setChecked( - Preferences.getEditor("AutoCompletionScintillaOnFail")) + self.acTimeoutSpinBox.setValue( + Preferences.getEditor("AutoCompletionTimeout")) def save(self): """ @@ -50,6 +53,10 @@ Preferences.setEditor( "AutoCompletionCaseSensitivity", self.acCaseSensitivityCheckBox.isChecked()) + + Preferences.setEditor( + "AutoCompletionReversedList", + self.acReversedCheckBox.isChecked()) Preferences.setEditor( "AutoCompletionReplaceWord", self.acReplaceWordCheckBox.isChecked()) @@ -57,8 +64,8 @@ "AutoCompletionThreshold", self.acThresholdSlider.value()) Preferences.setEditor( - "AutoCompletionScintillaOnFail", - self.acScintillaCheckBox.isChecked()) + "AutoCompletionTimeout", + self.acTimeoutSpinBox.value()) def create(dlg):
--- a/Preferences/ConfigurationPages/EditorAutocompletionPage.ui Wed Sep 27 18:00:37 2017 +0200 +++ b/Preferences/ConfigurationPages/EditorAutocompletionPage.ui Mon Oct 02 14:24:58 2017 +0200 @@ -10,7 +10,7 @@ <height>398</height> </rect> </property> - <layout class="QVBoxLayout" name="verticalLayout_2"> + <layout class="QVBoxLayout" name="verticalLayout"> <item> <widget class="QLabel" name="headerLabel"> <property name="text"> @@ -74,6 +74,16 @@ </widget> </item> <item row="1" column="0" colspan="2"> + <widget class="QCheckBox" name="acReversedCheckBox"> + <property name="toolTip"> + <string>Select to show the list of completions in reversed order</string> + </property> + <property name="text"> + <string>Show completions reversed</string> + </property> + </widget> + </item> + <item row="2" column="0" colspan="2"> <layout class="QHBoxLayout" name="_2"> <item> <widget class="QLabel" name="textLabel1_2"> @@ -119,27 +129,54 @@ </item> </layout> </item> - </layout> - </widget> - </item> - <item> - <widget class="QGroupBox" name="groupBox_2"> - <property name="title"> - <string>Plug-In Behavior</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QCheckBox" name="acScintillaCheckBox"> - <property name="toolTip"> - <string>Select to show QScintilla provided completions, if the selected plug-ins fail</string> - </property> - <property name="whatsThis"> - <string>Qscintilla provided completions are shown, if this option is enabled and completions shall be provided by plug-ins (see completions sub-page of the plug-in) and the plugin-ins don't deliver any completions.</string> - </property> - <property name="text"> - <string>Show QScintilla completions, if plug-ins fail</string> - </property> - </widget> + <item row="3" column="0" colspan="2"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Time to wait until completion:</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="acTimeoutSpinBox"> + <property name="toolTip"> + <string>Enter the time in milliseconds after which a list with completion proposals shall be shown</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="correctionMode"> + <enum>QAbstractSpinBox::CorrectToNearestValue</enum> + </property> + <property name="suffix"> + <string> ms</string> + </property> + <property name="minimum"> + <number>0</number> + </property> + <property name="maximum"> + <number>999</number> + </property> + <property name="singleStep"> + <number>10</number> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> </item> </layout> </widget> @@ -163,8 +200,9 @@ <tabstop>acEnabledCheckBox</tabstop> <tabstop>acCaseSensitivityCheckBox</tabstop> <tabstop>acReplaceWordCheckBox</tabstop> + <tabstop>acReversedCheckBox</tabstop> <tabstop>acThresholdSlider</tabstop> - <tabstop>acScintillaCheckBox</tabstop> + <tabstop>acTimeoutSpinBox</tabstop> </tabstops> <resources/> <connections>
--- a/Preferences/__init__.py Wed Sep 27 18:00:37 2017 +0200 +++ b/Preferences/__init__.py Mon Oct 02 14:24:58 2017 +0200 @@ -416,9 +416,10 @@ "AutoCompletionShowSingle": False, "AutoCompletionSource": QsciScintilla.AcsDocument, "AutoCompletionThreshold": 2, + "AutoCompletionTimeout": 200, + # timeout in ms before auto-completion is started "AutoCompletionFillups": False, - "AutoCompletionScintillaOnFail": False, - # show QScintilla completions, if plug-in fails + "AutoCompletionReversedList": False, "CallTipsEnabled": False, "CallTipsVisible": 0, @@ -2063,8 +2064,8 @@ elif key in ["AutosaveInterval", "TabWidth", "IndentWidth", "FoldingStyle", "WarnFilesize", "EdgeMode", "EdgeColumn", "CaretWidth", "AutoCompletionSource", - "AutoCompletionThreshold", "CallTipsVisible", - "CallTipsStyle", "MarkOccurrencesTimeout", + "AutoCompletionThreshold", "AutoCompletionTimeout", + "CallTipsVisible", "CallTipsStyle", "MarkOccurrencesTimeout", "AutoSpellCheckChunkSize", "SpellCheckingMinWordSize", "PostScriptLevel", "EOLMode", "ZoomFactor", "WhitespaceSize", "OnlineSyntaxCheckInterval", "OnlineChangeTraceInterval", @@ -2087,6 +2088,10 @@ elif value in ["false", "False"]: value = 0 return QsciLexerPython.IndentationWarning(int(value)) + elif key == "AutoCompletionScintillaOnFail": + # This is obsolete, return default value for backward compatibility + # with old plug-ins. + return False else: return toBool(prefClass.settings.value( "Editor/" + key, prefClass.editorDefaults[key]))
--- a/QScintilla/Editor.py Wed Sep 27 18:00:37 2017 +0200 +++ b/QScintilla/Editor.py Mon Oct 02 14:24:58 2017 +0200 @@ -380,10 +380,21 @@ # set the text display again self.__setTextDisplay() - # set the autocompletion and calltips function + # set the auto-completion function + self.__acContext = True + self.__acText = "" + self.__acCompletions = set() + self.__acTimer = QTimer(self) + self.__acTimer.setSingleShot(True) + self.__acTimer.setInterval( + Preferences.getEditor("AutoCompletionTimeout")) + self.__acTimer.timeout.connect(self.__autoComplete) + self.__completionListHookFunctions = {} + self.__completionListAsyncHookFunctions = {} self.__setAutoCompletion() + # set the call-tips function self.__ctHookFunctions = {} self.__setCallTips() @@ -4521,7 +4532,8 @@ ## auto-completion hook interfaces ################################################################# - def addCompletionListHook(self, key, func): + # TODO: add support for asynchroneous auto-completion lists + def addCompletionListHook(self, key, func, async=False): """ Public method to set an auto-completion list provider. @@ -4531,9 +4543,14 @@ should be a function taking a reference to the editor and a boolean indicating to complete a context. It should return the possible completions as a list of strings. - @type function(editor, bool) -> list of str - """ - if key in self.__completionListHookFunctions: + @type function(editor, bool) -> list of str in case async is False + and function(editor, bool, str) returning nothing in case async + is True + @param async flag indicating an asynchroneous function + @type bool + """ + if key in self.__completionListHookFunctions or \ + key in self.__completionListAsyncHookFunctions: # it was already registered E5MessageBox.warning( self, @@ -4543,11 +4560,16 @@ .format(key)) return - if not self.__completionListHookFunctions: + if not self.__completionListHookFunctions and \ + not self.__completionListAsyncHookFunctions: if self.autoCompletionThreshold() > 0: self.setAutoCompletionThreshold(0) self.SCN_CHARADDED.connect(self.__charAdded) - self.__completionListHookFunctions[key] = func + + if async: + self.__completionListAsyncHookFunctions[key] = func + else: + self.__completionListHookFunctions[key] = func def removeCompletionListHook(self, key): """ @@ -4559,8 +4581,11 @@ """ if key in self.__completionListHookFunctions: del self.__completionListHookFunctions[key] - - if not self.__completionListHookFunctions: + elif key in self.__completionListAsyncHookFunctions: + del self.__completionListAsyncHookFunctions[key] + + if not self.__completionListHookFunctions and \ + not self.__completionListAsyncHookFunctions: self.SCN_CHARADDED.disconnect(self.__charAdded) if self.autoCompletionThreshold() == 0: self.setAutoCompletionThreshold( @@ -4575,45 +4600,81 @@ @return function providing completion list @rtype function or None """ - if key in self.__completionListHookFunctions: - return self.__completionListHookFunctions[key] - else: - return None + return (self.__completionListHookFunctions.get(key) or + self.__completionListAsyncHookFunctions.get(key)) def autoComplete(self, auto=False, context=True): """ - Public method to start autocompletion. + Public method to start auto-completion. @keyparam auto flag indicating a call from the __charAdded method (boolean) @keyparam context flag indicating to complete a context (boolean) """ if auto and self.autoCompletionThreshold() == -1: - # autocompletion is disabled + # auto-completion is disabled return - if self.__completionListHookFunctions: - if self.isListActive(): - self.cancelList() - completionsList = [] - for key in self.__completionListHookFunctions: - completionsList.extend( - self.__completionListHookFunctions[key](self, context)) - completionsList = list(set(completionsList)) - if len(completionsList) == 0: - if Preferences.getEditor("AutoCompletionScintillaOnFail") and \ - (self.autoCompletionSource() != QsciScintilla.AcsNone or - not auto): - self.autoCompleteQScintilla() + if self.__completionListHookFunctions or \ + self.__completionListAsyncHookFunctions: + if Preferences.getEditor("AutoCompletionTimeout"): + self.__acTimer.stop() + self.__acContext = context + self.__acTimer.start() else: - completionsList.sort() - self.showUserList(EditorAutoCompletionListID, - completionsList) + self.__autoComplete(context) elif not auto: self.autoCompleteQScintilla() elif self.autoCompletionSource() != QsciScintilla.AcsNone: self.autoCompleteQScintilla() + def __autoComplete(self, context=None): + """ + Private method to start auto-completion via plug-ins. + + @keyparam context flag indicating to complete a context + @type bool or None + """ + # TODO: add a cache for recent completions + if context is None: + context = self.__acContext + + line, col = self.getCursorPosition() + self.__acText = self.getWordLeft(line, col) + self.__acCompletions.clear() + + for key in self.__completionListAsyncHookFunctions: + self.__completionListAsyncHookFunctions[key]( + self, context, self.__acText) + + for key in self.__completionListHookFunctions: + completions = self.__completionListHookFunctions[key]( + self, context) + self.completionsListReady(completions, self.__acText) + + def completionsListReady(self, completions, acText): + """ + Public method to show the completions determined by a completions + provider. + + @param completions list of possible completions + @type list of str + @param acText text to be completed + @type str + """ + if acText == self.__acText: + # process the list only, if not already obsolete + if self.isListActive(): + self.cancelList() + + self.__acCompletions.update(set(completions)) + if self.__acCompletions: + self.showUserList( + EditorAutoCompletionListID, + sorted(list(self.__acCompletions), + reverse=Preferences.getEditor( + "AutoCompletionReversedList"))) + def __completionListSelected(self, listId, txt): """ Private slot to handle the selection from the completion list.