TextEditor: Make threshold for automatic completion configurable

Fixes: QTCREATORBUG-19920
Change-Id: Id6be79485b1fb8c2ab4cce2706816949ae5e217b
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Kandeler
2020-08-03 16:19:19 +02:00
parent 7576ead1dd
commit 760d91665f
11 changed files with 214 additions and 136 deletions

View File

@@ -43,6 +43,7 @@
#include <texteditor/codeassist/functionhintproposal.h> #include <texteditor/codeassist/functionhintproposal.h>
#include <texteditor/codeassist/genericproposal.h> #include <texteditor/codeassist/genericproposal.h>
#include <texteditor/codeassist/ifunctionhintproposalmodel.h> #include <texteditor/codeassist/ifunctionhintproposalmodel.h>
#include <texteditor/texteditorsettings.h>
#include <cplusplus/BackwardsScanner.h> #include <cplusplus/BackwardsScanner.h>
#include <cplusplus/ExpressionUnderCursor.h> #include <cplusplus/ExpressionUnderCursor.h>
@@ -405,11 +406,11 @@ bool ClangCompletionAssistProcessor::accepts() const
return true; return true;
} else { } else {
// Trigger completion after three characters of a name have been typed, when not editing an existing name // Trigger completion after n characters of a name have been typed, when not editing an existing name
QChar characterUnderCursor = m_interface->characterAt(pos); QChar characterUnderCursor = m_interface->characterAt(pos);
if (!characterUnderCursor.isLetterOrNumber() && characterUnderCursor != QLatin1Char('_')) { if (!characterUnderCursor.isLetterOrNumber() && characterUnderCursor != QLatin1Char('_')) {
const int startOfName = findStartOfName(pos); const int startOfName = findStartOfName(pos);
if (pos - startOfName >= 3) { if (pos - startOfName >= TextEditorSettings::completionSettings().m_characterThreshold) {
const QChar firstCharacter = m_interface->characterAt(startOfName); const QChar firstCharacter = m_interface->characterAt(startOfName);
if (firstCharacter.isLetter() || firstCharacter == QLatin1Char('_')) { if (firstCharacter.isLetter() || firstCharacter == QLatin1Char('_')) {
// Finally check that we're not inside a comment or string (code copied from startOfOperator) // Finally check that we're not inside a comment or string (code copied from startOfOperator)

View File

@@ -851,12 +851,12 @@ bool InternalCppCompletionAssistProcessor::accepts() const
return true; return true;
} else { } else {
// Trigger completion after three characters of a name have been typed, when not editing an existing name // Trigger completion after n characters of a name have been typed, when not editing an existing name
QChar characterUnderCursor = m_interface->characterAt(pos); QChar characterUnderCursor = m_interface->characterAt(pos);
if (!isValidIdentifierChar(characterUnderCursor)) { if (!isValidIdentifierChar(characterUnderCursor)) {
const int startOfName = findStartOfName(pos); const int startOfName = findStartOfName(pos);
if (pos - startOfName >= 3) { if (pos - startOfName >= TextEditorSettings::completionSettings().m_characterThreshold) {
const QChar firstCharacter = m_interface->characterAt(startOfName); const QChar firstCharacter = m_interface->characterAt(startOfName);
if (isValidFirstIdentifierChar(firstCharacter)) { if (isValidFirstIdentifierChar(firstCharacter)) {
// Finally check that we're not inside a comment or string (code copied from startOfOperator) // Finally check that we're not inside a comment or string (code copied from startOfOperator)

View File

@@ -40,6 +40,7 @@
#include <texteditor/codeassist/genericproposalmodel.h> #include <texteditor/codeassist/genericproposalmodel.h>
#include <texteditor/codeassist/genericproposal.h> #include <texteditor/codeassist/genericproposal.h>
#include <texteditor/codeassist/functionhintproposal.h> #include <texteditor/codeassist/functionhintproposal.h>
#include <texteditor/texteditorsettings.h>
#include <cplusplus/ExpressionUnderCursor.h> #include <cplusplus/ExpressionUnderCursor.h>
#include <cplusplus/Icons.h> #include <cplusplus/Icons.h>
@@ -528,7 +529,8 @@ bool GlslCompletionAssistProcessor::acceptsIdleEditor() const
++pos; ++pos;
const QString word = m_interface->textAt(pos, cursorPosition - pos); const QString word = m_interface->textAt(pos, cursorPosition - pos);
if (word.length() > 2 && checkStartOfIdentifier(word)) { if (word.length() >= TextEditorSettings::completionSettings().m_characterThreshold
&& checkStartOfIdentifier(word)) {
for (auto character : word) { for (auto character : word) {
if (!isIdentifierChar(character)) if (!isIdentifierChar(character))
return false; return false;

View File

@@ -34,6 +34,7 @@
#include <texteditor/codeassist/iassistprocessor.h> #include <texteditor/codeassist/iassistprocessor.h>
#include <texteditor/codeassist/genericproposal.h> #include <texteditor/codeassist/genericproposal.h>
#include <texteditor/codeassist/genericproposalmodel.h> #include <texteditor/codeassist/genericproposalmodel.h>
#include <texteditor/texteditorsettings.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/textutils.h> #include <utils/textutils.h>
#include <utils/utilsicons.h> #include <utils/utilsicons.h>
@@ -314,13 +315,13 @@ IAssistProposal *LanguageClientCompletionAssistProcessor::perform(const AssistIn
QTC_ASSERT(m_client, return nullptr); QTC_ASSERT(m_client, return nullptr);
m_pos = interface->position(); m_pos = interface->position();
if (interface->reason() == IdleEditor) { if (interface->reason() == IdleEditor) {
// Trigger an automatic completion request only when we are on a word with more than 2 "identifier" character // Trigger an automatic completion request only when we are on a word with at least n "identifier" characters
const QRegularExpression regexp("[_a-zA-Z0-9]+"); const QRegularExpression regexp("[_a-zA-Z0-9]+");
auto hasMatch = [&regexp](const QString &txt) { return regexp.match(txt).hasMatch(); }; auto hasMatch = [&regexp](const QString &txt) { return regexp.match(txt).hasMatch(); };
int delta = 0; int delta = 0;
while (m_pos - delta > 0 && hasMatch(interface->textAt(m_pos - delta - 1, delta + 1))) while (m_pos - delta > 0 && hasMatch(interface->textAt(m_pos - delta - 1, delta + 1)))
++delta; ++delta;
if (delta < 3) if (delta < TextEditorSettings::completionSettings().m_characterThreshold)
return nullptr; return nullptr;
if (m_client->documentUpdatePostponed(interface->fileName())) { if (m_client->documentUpdatePostponed(interface->fileName())) {
m_postponedUpdateConnection m_postponedUpdateConnection

View File

@@ -891,7 +891,8 @@ bool QmlJSCompletionAssistProcessor::acceptsIdleEditor() const
++startPos; ++startPos;
const QString &word = m_interface->textAt(startPos, cursorPos - startPos); const QString &word = m_interface->textAt(startPos, cursorPos - startPos);
if (word.length() > 2 && isIdentifierChar(word.at(0), true)) { if (word.length() >= TextEditorSettings::completionSettings().m_characterThreshold
&& isIdentifierChar(word.at(0), true)) {
for (int i = 1; i < word.length(); ++i) { for (int i = 1; i < word.length(); ++i) {
if (!isIdentifierChar(word.at(i))) if (!isIdentifierChar(word.at(i)))
return false; return false;

View File

@@ -31,6 +31,8 @@
#include "genericproposalmodel.h" #include "genericproposalmodel.h"
#include "iassistprocessor.h" #include "iassistprocessor.h"
#include "../snippets/snippetassistcollector.h" #include "../snippets/snippetassistcollector.h"
#include "../completionsettings.h"
#include "../texteditorsettings.h"
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/runextensions.h> #include <utils/runextensions.h>
@@ -127,8 +129,10 @@ IAssistProposal *DocumentContentCompletionProcessor::perform(const AssistInterfa
if (interface->reason() == IdleEditor) { if (interface->reason() == IdleEditor) {
QChar characterUnderCursor = interface->characterAt(interface->position()); QChar characterUnderCursor = interface->characterAt(interface->position());
if (characterUnderCursor.isLetterOrNumber() || length < 3) if (characterUnderCursor.isLetterOrNumber()
|| length < TextEditorSettings::completionSettings().m_characterThreshold) {
return nullptr; return nullptr;
}
} }
const QString wordUnderCursor = interface->textAt(pos, length); const QString wordUnderCursor = interface->textAt(pos, length);

View File

@@ -195,7 +195,8 @@ IAssistProposal *KeywordsCompletionAssistProcessor::perform(const AssistInterfac
if (interface->reason() == IdleEditor) { if (interface->reason() == IdleEditor) {
QChar characterUnderCursor = interface->characterAt(interface->position()); QChar characterUnderCursor = interface->characterAt(interface->position());
if (characterUnderCursor.isLetterOrNumber() || interface->position() - startPosition < 3) { if (characterUnderCursor.isLetterOrNumber() || interface->position() - startPosition
< TextEditorSettings::completionSettings().m_characterThreshold) {
QList<AssistProposalItemInterface *> items; QList<AssistProposalItemInterface *> items;
if (m_dynamicCompletionFunction) if (m_dynamicCompletionFunction)
m_dynamicCompletionFunction(interface, &items, startPosition); m_dynamicCompletionFunction(interface, &items, startPosition);

View File

@@ -31,6 +31,7 @@ static const char settingsGroup[] = "CppTools/Completion";
static const char caseSensitivityKey[] = "CaseSensitivity"; static const char caseSensitivityKey[] = "CaseSensitivity";
static const char completionTriggerKey[] = "CompletionTrigger"; static const char completionTriggerKey[] = "CompletionTrigger";
static const char automaticProposalTimeoutKey[] = "AutomaticProposalTimeout"; static const char automaticProposalTimeoutKey[] = "AutomaticProposalTimeout";
static const char characterThresholdKey[] = "CharacterThreshold";
static const char autoInsertBracesKey[] = "AutoInsertBraces"; static const char autoInsertBracesKey[] = "AutoInsertBraces";
static const char surroundingAutoBracketsKey[] = "SurroundingAutoBrackets"; static const char surroundingAutoBracketsKey[] = "SurroundingAutoBrackets";
static const char autoInsertQuotesKey[] = "AutoInsertQuotes"; static const char autoInsertQuotesKey[] = "AutoInsertQuotes";
@@ -52,6 +53,7 @@ void CompletionSettings::toSettings(QSettings *s) const
s->setValue(caseSensitivityKey, (int) m_caseSensitivity); s->setValue(caseSensitivityKey, (int) m_caseSensitivity);
s->setValue(completionTriggerKey, (int) m_completionTrigger); s->setValue(completionTriggerKey, (int) m_completionTrigger);
s->setValue(automaticProposalTimeoutKey, m_automaticProposalTimeoutInMs); s->setValue(automaticProposalTimeoutKey, m_automaticProposalTimeoutInMs);
s->setValue(characterThresholdKey, m_characterThreshold);
s->setValue(autoInsertBracesKey, m_autoInsertBrackets); s->setValue(autoInsertBracesKey, m_autoInsertBrackets);
s->setValue(surroundingAutoBracketsKey, m_surroundingAutoBrackets); s->setValue(surroundingAutoBracketsKey, m_surroundingAutoBrackets);
s->setValue(autoInsertQuotesKey, m_autoInsertQuotes); s->setValue(autoInsertQuotesKey, m_autoInsertQuotes);
@@ -78,6 +80,8 @@ void CompletionSettings::fromSettings(QSettings *s)
s->value(completionTriggerKey, m_completionTrigger).toInt(); s->value(completionTriggerKey, m_completionTrigger).toInt();
m_automaticProposalTimeoutInMs = m_automaticProposalTimeoutInMs =
s->value(automaticProposalTimeoutKey, m_automaticProposalTimeoutInMs).toInt(); s->value(automaticProposalTimeoutKey, m_automaticProposalTimeoutInMs).toInt();
m_characterThreshold =
s->value(characterThresholdKey, m_characterThreshold).toInt();
m_autoInsertBrackets = m_autoInsertBrackets =
s->value(autoInsertBracesKey, m_autoInsertBrackets).toBool(); s->value(autoInsertBracesKey, m_autoInsertBrackets).toBool();
m_surroundingAutoBrackets = m_surroundingAutoBrackets =
@@ -110,6 +114,7 @@ bool CompletionSettings::equals(const CompletionSettings &cs) const
return m_caseSensitivity == cs.m_caseSensitivity return m_caseSensitivity == cs.m_caseSensitivity
&& m_completionTrigger == cs.m_completionTrigger && m_completionTrigger == cs.m_completionTrigger
&& m_automaticProposalTimeoutInMs == cs.m_automaticProposalTimeoutInMs && m_automaticProposalTimeoutInMs == cs.m_automaticProposalTimeoutInMs
&& m_characterThreshold == cs.m_characterThreshold
&& m_autoInsertBrackets == cs.m_autoInsertBrackets && m_autoInsertBrackets == cs.m_autoInsertBrackets
&& m_surroundingAutoBrackets == cs.m_surroundingAutoBrackets && m_surroundingAutoBrackets == cs.m_surroundingAutoBrackets
&& m_autoInsertQuotes == cs.m_autoInsertQuotes && m_autoInsertQuotes == cs.m_autoInsertQuotes

View File

@@ -59,6 +59,7 @@ public:
CaseSensitivity m_caseSensitivity = CaseInsensitive; CaseSensitivity m_caseSensitivity = CaseInsensitive;
CompletionTrigger m_completionTrigger = AutomaticCompletion; CompletionTrigger m_completionTrigger = AutomaticCompletion;
int m_automaticProposalTimeoutInMs = 400; int m_automaticProposalTimeoutInMs = 400;
int m_characterThreshold = 3;
bool m_autoInsertBrackets = true; bool m_autoInsertBrackets = true;
bool m_surroundingAutoBrackets = true; bool m_surroundingAutoBrackets = true;
bool m_autoInsertQuotes = true; bool m_autoInsertQuotes = true;

View File

@@ -100,6 +100,7 @@ CompletionSettingsPageWidget::CompletionSettingsPageWidget(CompletionSettingsPag
m_ui.completionTrigger->setCurrentIndex(completionTriggerIndex); m_ui.completionTrigger->setCurrentIndex(completionTriggerIndex);
m_ui.automaticProposalTimeoutSpinBox m_ui.automaticProposalTimeoutSpinBox
->setValue(m_owner->m_completionSettings.m_automaticProposalTimeoutInMs); ->setValue(m_owner->m_completionSettings.m_automaticProposalTimeoutInMs);
m_ui.thresholdSpinBox->setValue(m_owner->m_completionSettings.m_characterThreshold);
m_ui.insertBrackets->setChecked(m_owner->m_completionSettings.m_autoInsertBrackets); m_ui.insertBrackets->setChecked(m_owner->m_completionSettings.m_autoInsertBrackets);
m_ui.surroundBrackets->setChecked(m_owner->m_completionSettings.m_surroundingAutoBrackets); m_ui.surroundBrackets->setChecked(m_owner->m_completionSettings.m_surroundingAutoBrackets);
m_ui.insertQuotes->setChecked(m_owner->m_completionSettings.m_autoInsertQuotes); m_ui.insertQuotes->setChecked(m_owner->m_completionSettings.m_autoInsertQuotes);
@@ -173,6 +174,7 @@ void CompletionSettingsPageWidget::settingsFromUi(CompletionSettings &completion
completion.m_completionTrigger = completionTrigger(); completion.m_completionTrigger = completionTrigger();
completion.m_automaticProposalTimeoutInMs completion.m_automaticProposalTimeoutInMs
= m_ui.automaticProposalTimeoutSpinBox->value(); = m_ui.automaticProposalTimeoutSpinBox->value();
completion.m_characterThreshold = m_ui.thresholdSpinBox->value();
completion.m_autoInsertBrackets = m_ui.insertBrackets->isChecked(); completion.m_autoInsertBrackets = m_ui.insertBrackets->isChecked();
completion.m_surroundingAutoBrackets = m_ui.surroundBrackets->isChecked(); completion.m_surroundingAutoBrackets = m_ui.surroundBrackets->isChecked();
completion.m_autoInsertQuotes = m_ui.insertQuotes->isChecked(); completion.m_autoInsertQuotes = m_ui.insertQuotes->isChecked();

View File

@@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>551</width> <width>823</width>
<height>507</height> <height>756</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
@@ -16,138 +16,198 @@
<property name="title"> <property name="title">
<string>Behavior</string> <string>Behavior</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QVBoxLayout" name="verticalLayout_3">
<item row="1" column="0"> <item>
<widget class="QLabel" name="completionTriggerLabel"> <layout class="QFormLayout" name="formLayout">
<property name="text"> <item row="0" column="0">
<string>Activate completion:</string> <widget class="QLabel" name="caseSensitivityLabel">
</property> <property name="text">
</widget> <string>&amp;Case-sensitivity:</string>
</item> </property>
<item row="0" column="2" colspan="4"> <property name="buddy">
<spacer name="horizontalSpacer"> <cstring>caseSensitivity</cstring>
<property name="orientation"> </property>
<enum>Qt::Horizontal</enum> </widget>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>70</width>
<height>24</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0">
<widget class="QLabel" name="caseSensitivityLabel">
<property name="text">
<string>&amp;Case-sensitivity:</string>
</property>
<property name="buddy">
<cstring>caseSensitivity</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="caseSensitivity">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>Full</string>
</property>
</item> </item>
<item> <item row="0" column="1">
<property name="text"> <layout class="QHBoxLayout" name="horizontalLayout">
<string>None</string> <item>
</property> <widget class="QComboBox" name="caseSensitivity">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>Full</string>
</property>
</item>
<item>
<property name="text">
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>First Letter</string>
</property>
</item>
</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> </item>
<item> <item row="1" column="0">
<property name="text"> <widget class="QLabel" name="completionTriggerLabel">
<string>First Letter</string> <property name="text">
</property> <string>Activate completion:</string>
</property>
</widget>
</item> </item>
</widget> <item row="1" column="1">
</item> <layout class="QHBoxLayout" name="horizontalLayout_2">
<item row="1" column="1" colspan="2"> <item>
<widget class="QComboBox" name="completionTrigger"> <widget class="QComboBox" name="completionTrigger">
<item> <item>
<property name="text"> <property name="text">
<string>Manually</string> <string>Manually</string>
</property> </property>
</item>
<item>
<property name="text">
<string>When Triggered</string>
</property>
</item>
<item>
<property name="text">
<string>Always</string>
</property>
</item>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<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> </item>
<item> <item row="2" column="0">
<property name="text"> <widget class="QLabel" name="automaticProposalTimeoutLabel">
<string>When Triggered</string> <property name="text">
</property> <string>Timeout in ms:</string>
</property>
</widget>
</item> </item>
<item> <item row="2" column="1">
<property name="text"> <layout class="QHBoxLayout" name="horizontalLayout_7">
<string>Always</string> <item>
</property> <widget class="QSpinBox" name="automaticProposalTimeoutSpinBox">
<property name="maximum">
<number>2000</number>
</property>
<property name="singleStep">
<number>50</number>
</property>
<property name="value">
<number>400</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<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> </item>
</widget> <item row="3" column="0">
</item> <widget class="QLabel" name="thresholdLabel">
<item row="1" column="3"> <property name="text">
<widget class="QLabel" name="automaticProposalTimeoutLabel"> <string>Character threshold:</string>
<property name="text"> </property>
<string>Timeout in ms:</string> </widget>
</property> </item>
</widget> <item row="3" column="1">
</item> <layout class="QHBoxLayout" name="horizontalLayout_6">
<item row="1" column="4"> <item>
<widget class="QSpinBox" name="automaticProposalTimeoutSpinBox"> <widget class="QSpinBox" name="thresholdSpinBox">
<property name="maximum"> <property name="minimum">
<number>2000</number> <number>1</number>
</property> </property>
<property name="singleStep"> </widget>
<number>50</number> </item>
</property> <item>
<property name="value"> <spacer name="horizontalSpacer_4">
<number>400</number> <property name="orientation">
</property> <enum>Qt::Horizontal</enum>
</widget> </property>
</item> <property name="sizeHint" stdset="0">
<item row="1" column="5"> <size>
<spacer name="horizontalSpacer_3"> <width>40</width>
<property name="orientation"> <height>20</height>
<enum>Qt::Horizontal</enum> </size>
</property> </property>
<property name="sizeHint" stdset="0"> </spacer>
<size> </item>
<width>40</width> </layout>
<height>24</height> </item>
</size> <item row="4" column="0">
</property> <widget class="QCheckBox" name="partiallyComplete">
</spacer> <property name="toolTip">
</item> <string>Inserts the common prefix of available completion items.</string>
<item row="2" column="0"> </property>
<widget class="QCheckBox" name="partiallyComplete"> <property name="text">
<property name="toolTip"> <string>Autocomplete common &amp;prefix</string>
<string>Inserts the common prefix of available completion items.</string> </property>
</property> <property name="checked">
<property name="text"> <bool>true</bool>
<string>Autocomplete common &amp;prefix</string> </property>
</property> </widget>
<property name="checked"> </item>
<bool>true</bool> <item row="5" column="0">
</property> <widget class="QCheckBox" name="autoSplitStrings">
</widget> <property name="toolTip">
</item> <string>Splits a string into two lines by adding an end quote at the cursor position when you press Enter and a start quote to the next line, before the rest of the string.
<item row="3" column="0">
<widget class="QCheckBox" name="autoSplitStrings">
<property name="toolTip">
<string>Splits a string into two lines by adding an end quote at the cursor position when you press Enter and a start quote to the next line, before the rest of the string.
In addition, Shift+Enter inserts an escape character at the cursor position and moves the rest of the string to the next line.</string> In addition, Shift+Enter inserts an escape character at the cursor position and moves the rest of the string to the next line.</string>
</property> </property>
<property name="text"> <property name="text">
<string>Automatically split strings</string> <string>Automatically split strings</string>
</property> </property>
</widget> </widget>
</item>
</layout>
</item> </item>
</layout> </layout>
</widget> </widget>