diff --git a/src/plugins/cpptools/cppcodecompletion.cpp b/src/plugins/cpptools/cppcodecompletion.cpp index 733671fa51507155aaaf152adef324cbc51f471c..9ea8d987dc17d1060eb16193bb659a328b951d86 100644 --- a/src/plugins/cpptools/cppcodecompletion.cpp +++ b/src/plugins/cpptools/cppcodecompletion.cpp @@ -1723,7 +1723,33 @@ QList<TextEditor::CompletionItem> CppCodeCompletion::getCompletions() return completionItems; } -void CppCodeCompletion::complete(const TextEditor::CompletionItem &item) +bool CppCodeCompletion::typedCharCompletes(const TextEditor::CompletionItem &item, QChar typedChar) +{ + if (item.data.canConvert<QString>()) // snippet + return false; + + if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) + return typedChar == QLatin1Char('(') + || typedChar == QLatin1Char(','); + + if (m_completionOperator == T_STRING_LITERAL || m_completionOperator == T_ANGLE_STRING_LITERAL) + return typedChar == QLatin1Char('/') + && item.text.endsWith(QLatin1Char('/')); + + if (item.data.value<Symbol *>()) + return typedChar == QLatin1Char(':') + || typedChar == QLatin1Char(';') + || typedChar == QLatin1Char('.') + || typedChar == QLatin1Char(',') + || typedChar == QLatin1Char('('); + + if (item.data.canConvert<CompleteFunctionDeclaration>()) + return typedChar == QLatin1Char('('); + + return false; +} + +void CppCodeCompletion::complete(const TextEditor::CompletionItem &item, QChar typedChar) { Symbol *symbol = 0; @@ -1749,10 +1775,15 @@ void CppCodeCompletion::complete(const TextEditor::CompletionItem &item) if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) { toInsert = item.text; extraChars += QLatin1Char(')'); + + if (typedChar == QLatin1Char('(')) // Eat the opening parenthesis + typedChar = QChar(); } else if (m_completionOperator == T_STRING_LITERAL || m_completionOperator == T_ANGLE_STRING_LITERAL) { toInsert = item.text; if (!toInsert.endsWith(QLatin1Char('/'))) extraChars += QLatin1Char((m_completionOperator == T_ANGLE_STRING_LITERAL) ? '>' : '"'); + else if (typedChar == QLatin1Char('/')) // Eat the slash + typedChar = QChar(); } else { toInsert = item.text; @@ -1768,7 +1799,7 @@ void CppCodeCompletion::complete(const TextEditor::CompletionItem &item) if (! function->hasReturnType() && (function->identity() && !function->identity()->isDestructorNameId())) { // Don't insert any magic, since the user might have just wanted to select the class - } else if (function->templateParameterCount() != 0) { + } else if (function->templateParameterCount() != 0 && typedChar != QLatin1Char('(')) { // If there are no arguments, then we need the template specification if (function->argumentCount() == 0) { extraChars += QLatin1Char('<'); @@ -1777,32 +1808,50 @@ void CppCodeCompletion::complete(const TextEditor::CompletionItem &item) if (completionSettings().m_spaceAfterFunctionName) extraChars += QLatin1Char(' '); extraChars += QLatin1Char('('); + if (typedChar == QLatin1Char('(')) + typedChar = QChar(); // If the function doesn't return anything, automatically place the semicolon, // unless we're doing a scope completion (then it might be function definition). - bool endWithSemicolon = function->returnType()->isVoidType() && m_completionOperator != T_COLON_COLON; + const QChar characterAtCursor = m_editor->characterAt(m_editor->position()); + bool endWithSemicolon = typedChar == QLatin1Char(';') + || (function->returnType()->isVoidType() && m_completionOperator != T_COLON_COLON); + const QChar semicolon = typedChar.isNull() ? QLatin1Char(';') : typedChar; + + if (endWithSemicolon && characterAtCursor == semicolon) { + endWithSemicolon = false; + typedChar = QChar(); + } // If the function takes no arguments, automatically place the closing parenthesis if (item.duplicateCount == 0 && ! function->hasArguments()) { extraChars += QLatin1Char(')'); - if (endWithSemicolon) - extraChars += QLatin1Char(';'); + if (endWithSemicolon) { + extraChars += semicolon; + typedChar = QChar(); + } } else if (autoParenthesesEnabled) { const QChar lookAhead = m_editor->characterAt(m_editor->position() + 1); if (MatchingText::shouldInsertMatchingText(lookAhead)) { extraChars += QLatin1Char(')'); --cursorOffset; if (endWithSemicolon) { - extraChars += QLatin1Char(';'); + extraChars += semicolon; --cursorOffset; + typedChar = QChar(); } } + // TODO: When an opening parenthesis exists, the "semicolon" should really be + // inserted after the matching closing parenthesis. } } } } if (autoInsertBrackets && item.data.canConvert<CompleteFunctionDeclaration>()) { + if (typedChar == QLatin1Char('(')) + typedChar = QChar(); + // everything from the closing parenthesis on are extra chars, to // make sure an auto-inserted ")" gets replaced by ") const" if necessary int closingParen = toInsert.lastIndexOf(QLatin1Char(')')); @@ -1811,6 +1860,13 @@ void CppCodeCompletion::complete(const TextEditor::CompletionItem &item) } } + // Append an unhandled typed character, adjusting cursor offset when it had been adjusted before + if (!typedChar.isNull()) { + extraChars += typedChar; + if (cursorOffset != 0) + --cursorOffset; + } + // Avoid inserting characters that are already there for (int i = 0; i < extraChars.length(); ++i) { const QChar a = extraChars.at(i); @@ -1836,7 +1892,7 @@ bool CppCodeCompletion::partiallyComplete(const QList<TextEditor::CompletionItem if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) { return false; } else if (completionItems.count() == 1) { - complete(completionItems.first()); + complete(completionItems.first(), QChar()); return true; } else if (m_completionOperator != T_LPAREN) { return TextEditor::ICompletionCollector::partiallyComplete(completionItems); diff --git a/src/plugins/cpptools/cppcodecompletion.h b/src/plugins/cpptools/cppcodecompletion.h index 513b038407a969fa04edfd87d69389c97112f8fc..406d4a2f872cfe0fce51e942fde1f81491529b01 100644 --- a/src/plugins/cpptools/cppcodecompletion.h +++ b/src/plugins/cpptools/cppcodecompletion.h @@ -78,7 +78,8 @@ public: int startCompletion(TextEditor::ITextEditable *editor); void completions(QList<TextEditor::CompletionItem> *completions); - void complete(const TextEditor::CompletionItem &item); + bool typedCharCompletes(const TextEditor::CompletionItem &item, QChar typedChar); + void complete(const TextEditor::CompletionItem &item, QChar typedChar); bool partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems); void cleanup(); diff --git a/src/plugins/qmljseditor/qmljscodecompletion.cpp b/src/plugins/qmljseditor/qmljscodecompletion.cpp index 1bd3c77a52fc4fd020ff7170400a0c226a72ac9a..5f28d8acac37444e04447a8494f8aaeeb2e5e534 100644 --- a/src/plugins/qmljseditor/qmljscodecompletion.cpp +++ b/src/plugins/qmljseditor/qmljscodecompletion.cpp @@ -878,8 +878,19 @@ void CodeCompletion::completions(QList<TextEditor::CompletionItem> *completions) } } -void CodeCompletion::complete(const TextEditor::CompletionItem &item) +bool CodeCompletion::typedCharCompletes(const TextEditor::CompletionItem &item, QChar typedChar) { + if (item.data.canConvert<QString>()) // snippet + return false; + + return (item.text.endsWith(QLatin1String(": ")) && typedChar == QLatin1Char(':')) + || (item.text.endsWith(QLatin1Char('.')) && typedChar == QLatin1Char('.')); +} + +void CodeCompletion::complete(const TextEditor::CompletionItem &item, QChar typedChar) +{ + Q_UNUSED(typedChar) // Currently always included in the completion item when used + QString toInsert = item.text; if (QmlJSTextEditor *edit = qobject_cast<QmlJSTextEditor *>(m_editor->widget())) { @@ -895,7 +906,7 @@ void CodeCompletion::complete(const TextEditor::CompletionItem &item) QString replacableChars; if (toInsert.endsWith(QLatin1String(": "))) replacableChars = QLatin1String(": "); - else if (toInsert.endsWith(QLatin1String("."))) + else if (toInsert.endsWith(QLatin1Char('.'))) replacableChars = QLatin1String("."); int replacedLength = 0; @@ -924,7 +935,7 @@ bool CodeCompletion::partiallyComplete(const QList<TextEditor::CompletionItem> & const TextEditor::CompletionItem item = completionItems.first(); if (!item.data.canConvert<QString>()) { - complete(item); + complete(item, QChar()); return true; } } diff --git a/src/plugins/qmljseditor/qmljscodecompletion.h b/src/plugins/qmljseditor/qmljscodecompletion.h index 0cae652ad2cbab19d6548716f63801386a0ae56e..f435badf24dddce3aa939af05f3950301a3e789e 100644 --- a/src/plugins/qmljseditor/qmljscodecompletion.h +++ b/src/plugins/qmljseditor/qmljscodecompletion.h @@ -68,7 +68,8 @@ public: virtual bool triggersCompletion(TextEditor::ITextEditable *editor); virtual int startCompletion(TextEditor::ITextEditable *editor); virtual void completions(QList<TextEditor::CompletionItem> *completions); - virtual void complete(const TextEditor::CompletionItem &item); + virtual bool typedCharCompletes(const TextEditor::CompletionItem &item, QChar typedChar); + virtual void complete(const TextEditor::CompletionItem &item, QChar typedChar); virtual bool partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems); virtual QList<TextEditor::CompletionItem> getCompletions(); diff --git a/src/plugins/texteditor/completionsupport.cpp b/src/plugins/texteditor/completionsupport.cpp index 3b06580a7412204c41d85ab5eacdd752172f314c..bd6cc6c12ec9227307259a095d9da6ddabc4b6ce 100644 --- a/src/plugins/texteditor/completionsupport.cpp +++ b/src/plugins/texteditor/completionsupport.cpp @@ -67,7 +67,7 @@ CompletionSupport::CompletionSupport() void CompletionSupport::performCompletion(const CompletionItem &item) { - item.collector->complete(item); + item.collector->complete(item, m_completionList->typedChar()); m_checkCompletionTrigger = true; } diff --git a/src/plugins/texteditor/completionwidget.cpp b/src/plugins/texteditor/completionwidget.cpp index c8a6eae08a47d0056f3ee31ae0fb07daea225102..5b005c17b07f16e141b6e2047c92e0f001829ca6 100644 --- a/src/plugins/texteditor/completionwidget.cpp +++ b/src/plugins/texteditor/completionwidget.cpp @@ -199,6 +199,11 @@ void CompletionWidget::showCompletions(int startPos) setFocus(); } +QChar CompletionWidget::typedChar() const +{ + return m_completionListView->m_typedChar; +} + void CompletionWidget::updatePositionAndSize(int startPos) { // Determine size by calculating the space of the visible items @@ -415,6 +420,16 @@ bool CompletionListView::event(QEvent *e) } if (forwardKeys && ! m_quickFix) { + if (ke->text().length() == 1 && currentIndex().isValid() && qApp->focusWidget() == this) { + QChar typedChar = ke->text().at(0); + const CompletionItem &item = m_model->itemAt(currentIndex()); + if (item.collector->typedCharCompletes(item, typedChar)) { + m_typedChar = typedChar; + m_completionWidget->closeList(currentIndex()); + return true; + } + } + m_blockFocusOut = true; QApplication::sendEvent(m_editorWidget, e); m_blockFocusOut = false; diff --git a/src/plugins/texteditor/completionwidget.h b/src/plugins/texteditor/completionwidget.h index ece02904b115bb125910bc3149f903bcb697e378..813fd89f3071842b5246ebcd0bc76b507323dd75 100644 --- a/src/plugins/texteditor/completionwidget.h +++ b/src/plugins/texteditor/completionwidget.h @@ -61,6 +61,8 @@ public: void setCompletionItems(const QList<TextEditor::CompletionItem> &completionitems); void showCompletions(int startPos); + QChar typedChar() const; + signals: void itemSelected(const TextEditor::CompletionItem &item); void completionListClosed(); @@ -115,6 +117,7 @@ private: CompletionSupport *m_support; QPointer<CompletionInfoFrame> m_infoFrame; QTimer m_infoTimer; + QChar m_typedChar; }; } // namespace Internal diff --git a/src/plugins/texteditor/icompletioncollector.h b/src/plugins/texteditor/icompletioncollector.h index 354e45c75db9d3567a8954be57f91fcdb6ae353e..dc8549635b7a00d929485c319b564586ad18b560 100644 --- a/src/plugins/texteditor/icompletioncollector.h +++ b/src/plugins/texteditor/icompletioncollector.h @@ -111,9 +111,19 @@ public: */ virtual void completions(QList<CompletionItem> *completions) = 0; - /* This method should complete the given completion item. + /** + * This method should return true when the given typed character should cause + * the selected completion item to be completed. */ - virtual void complete(const CompletionItem &item) = 0; + virtual bool typedCharCompletes(const CompletionItem &item, QChar typedChar) = 0; + + /** + * This method should complete the given completion item. + * + * \param typedChar Non-null when completion was triggered by typing a + * character. Possible values depend on typedCharCompletes() + */ + virtual void complete(const CompletionItem &item, QChar typedChar) = 0; /* This method gives the completion collector a chance to partially complete * based on a set of items. The general use case is to complete the common @@ -153,6 +163,17 @@ public: IQuickFixCollector(QObject *parent = 0) : ICompletionCollector(parent) {} virtual ~IQuickFixCollector() {} + virtual bool typedCharCompletes(const CompletionItem &, QChar) + { return false; } + + virtual void fix(const TextEditor::CompletionItem &item) = 0; + + virtual void complete(const CompletionItem &item, QChar typedChar) + { + Q_UNUSED(typedChar) + fix(item); + } + virtual bool triggersCompletion(TextEditor::ITextEditable *) { return false; } diff --git a/src/plugins/texteditor/quickfix.cpp b/src/plugins/texteditor/quickfix.cpp index 9918ad4f5b4d192b983437de001e89b90db76a6a..8e5e024750e9c305d19f9b16e61002b09cd2d59f 100644 --- a/src/plugins/texteditor/quickfix.cpp +++ b/src/plugins/texteditor/quickfix.cpp @@ -171,7 +171,7 @@ void QuickFixCollector::completions(QList<TextEditor::CompletionItem> *quickFixI } } -void QuickFixCollector::complete(const TextEditor::CompletionItem &item) +void QuickFixCollector::fix(const TextEditor::CompletionItem &item) { const int index = item.data.toInt(); diff --git a/src/plugins/texteditor/quickfix.h b/src/plugins/texteditor/quickfix.h index fd731c6c5a1533bf93b0832eeb4acc85ca7268d8..2ad179e0ac9d19e5420ba572b1381b334bb36fad 100644 --- a/src/plugins/texteditor/quickfix.h +++ b/src/plugins/texteditor/quickfix.h @@ -111,7 +111,7 @@ public: virtual bool triggersCompletion(TextEditor::ITextEditable *editor); virtual int startCompletion(TextEditor::ITextEditable *editor); virtual void completions(QList<TextEditor::CompletionItem> *completions); - virtual void complete(const TextEditor::CompletionItem &item); + virtual void fix(const TextEditor::CompletionItem &item); virtual void cleanup(); virtual TextEditor::QuickFixState *initializeCompletion(TextEditor::ITextEditable *editable) = 0;