Files
qt-creator/src/plugins/qmljseditor/qmljsautocompleter.cpp
David Schulz 6750607244 Editor: Skip auto completed character only if it was recently inserted.
This means you can skip automatically inserted characters as long as you
don't explicitly move the text cursor and the editor doesn't lose the
focus. This will be visualized by highlighting the automatically
inserted character as long as you can perform the skipping.

This will reduce unexpected skipping in the case a cursor was explicitly
placed before an closing brace and a closing brace is typed.

Change-Id: I28e29e79ba10c9c48e8bc8817405fea630cca9bd
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
Reviewed-by: Nikolai Kosjar <nikolai.kosjar@qt.io>
Reviewed-by: David Schulz <david.schulz@theqtcompany.com>
2016-06-21 11:56:56 +00:00

331 lines
8.9 KiB
C++

/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "qmljsautocompleter.h"
#include <qmljs/qmljsscanner.h>
#include <QChar>
#include <QLatin1Char>
#include <QTextDocument>
#include <QTextCursor>
#include <QTextBlock>
using namespace QmlJSEditor;
using namespace Internal;
using namespace QmlJS;
static int blockStartState(const QTextBlock &block)
{
int state = block.previous().userState();
if (state == -1)
return 0;
else
return state & 0xff;
}
static Token tokenUnderCursor(const QTextCursor &cursor)
{
const QString blockText = cursor.block().text();
const int blockState = blockStartState(cursor.block());
Scanner tokenize;
const QList<Token> tokens = tokenize(blockText, blockState);
const int pos = cursor.positionInBlock();
int tokenIndex = 0;
for (; tokenIndex < tokens.size(); ++tokenIndex) {
const Token &token = tokens.at(tokenIndex);
if (token.is(Token::Comment) || token.is(Token::String)) {
if (pos > token.begin() && pos <= token.end())
break;
} else {
if (pos >= token.begin() && pos < token.end())
break;
}
}
if (tokenIndex != tokens.size())
return tokens.at(tokenIndex);
return Token();
}
static bool shouldInsertMatchingText(QChar lookAhead)
{
switch (lookAhead.unicode()) {
case '{': case '}':
case ']': case ')':
case ';': case ',':
case '"': case '\'':
return true;
default:
if (lookAhead.isSpace())
return true;
return false;
} // switch
}
static bool shouldInsertMatchingText(const QTextCursor &tc)
{
QTextDocument *doc = tc.document();
return shouldInsertMatchingText(doc->characterAt(tc.selectionEnd()));
}
static bool shouldInsertNewline(const QTextCursor &tc)
{
QTextDocument *doc = tc.document();
int pos = tc.selectionEnd();
// count the number of empty lines.
int newlines = 0;
for (int e = doc->characterCount(); pos != e; ++pos) {
const QChar ch = doc->characterAt(pos);
if (! ch.isSpace())
break;
else if (ch == QChar::ParagraphSeparator)
++newlines;
}
if (newlines <= 1 && doc->characterAt(pos) != QLatin1Char('}'))
return true;
return false;
}
static bool isCompleteStringLiteral(const QStringRef &text)
{
if (text.length() < 2)
return false;
const QChar quote = text.at(0);
if (text.at(text.length() - 1) == quote)
return text.at(text.length() - 2) != QLatin1Char('\\'); // ### not exactly.
return false;
}
AutoCompleter::AutoCompleter()
{}
AutoCompleter::~AutoCompleter()
{}
bool AutoCompleter::contextAllowsAutoBrackets(const QTextCursor &cursor,
const QString &textToInsert) const
{
QChar ch;
if (! textToInsert.isEmpty())
ch = textToInsert.at(0);
switch (ch.unicode()) {
case '(':
case '[':
case '{':
case ')':
case ']':
case '}':
case ';':
break;
default:
if (ch.isNull())
break;
return false;
} // end of switch
const Token token = tokenUnderCursor(cursor);
switch (token.kind) {
case Token::Comment:
return false;
case Token::RightBrace:
return false;
case Token::String: {
const QString blockText = cursor.block().text();
const QStringRef tokenText = blockText.midRef(token.offset, token.length);
QChar quote = tokenText.at(0);
// if a string literal doesn't start with a quote, it must be multiline
if (quote != QLatin1Char('"') && quote != QLatin1Char('\'')) {
const int startState = blockStartState(cursor.block());
if ((startState & Scanner::MultiLineMask) == Scanner::MultiLineStringDQuote)
quote = QLatin1Char('"');
else if ((startState & Scanner::MultiLineMask) == Scanner::MultiLineStringSQuote)
quote = QLatin1Char('\'');
}
// never insert ' into string literals, it adds spurious ' when writing contractions
if (ch == QLatin1Char('\''))
return false;
if (ch != quote || isCompleteStringLiteral(tokenText))
break;
return false;
}
default:
break;
} // end of switch
return true;
}
bool AutoCompleter::contextAllowsAutoQuotes(const QTextCursor &cursor,
const QString &textToInsert) const
{
if (!isQuote(textToInsert))
return false;
const Token token = tokenUnderCursor(cursor);
switch (token.kind) {
case Token::Comment:
return false;
case Token::RightBrace:
return false;
case Token::String: {
const QString blockText = cursor.block().text();
const QStringRef tokenText = blockText.midRef(token.offset, token.length);
QChar quote = tokenText.at(0);
// if a string literal doesn't start with a quote, it must be multiline
if (quote != QLatin1Char('"') && quote != QLatin1Char('\'')) {
const int startState = blockStartState(cursor.block());
if ((startState & Scanner::MultiLineMask) == Scanner::MultiLineStringDQuote)
quote = QLatin1Char('"');
else if ((startState & Scanner::MultiLineMask) == Scanner::MultiLineStringSQuote)
quote = QLatin1Char('\'');
}
// never insert ' into string literals, it adds spurious ' when writing contractions
if (textToInsert.at(0) == QLatin1Char('\''))
return false;
if (textToInsert.at(0) != quote || isCompleteStringLiteral(tokenText))
break;
return false;
}
default:
break;
} // end of switch
return true;
}
bool AutoCompleter::contextAllowsElectricCharacters(const QTextCursor &cursor) const
{
Token token = tokenUnderCursor(cursor);
switch (token.kind) {
case Token::Comment:
case Token::String:
return false;
default:
return true;
}
}
bool AutoCompleter::isInComment(const QTextCursor &cursor) const
{
return tokenUnderCursor(cursor).is(Token::Comment);
}
QString AutoCompleter::insertMatchingBrace(const QTextCursor &cursor,
const QString &text,
QChar lookAhead,
bool skipChars,
int *skippedChars) const
{
if (text.length() != 1)
return QString();
if (! shouldInsertMatchingText(cursor))
return QString();
const QChar ch = text.at(0);
switch (ch.unicode()) {
case '(':
return QString(QLatin1Char(')'));
case '[':
return QString(QLatin1Char(']'));
case '{':
return QString(); // nothing to do.
case ')':
case ']':
case '}':
case ';':
if (lookAhead == ch && skipChars)
++*skippedChars;
break;
default:
break;
} // end of switch
return QString();
}
QString AutoCompleter::insertMatchingQuote(const QTextCursor &/*tc*/, const QString &text,
QChar lookAhead, bool skipChars, int *skippedChars) const
{
if (isQuote(text)) {
if (lookAhead == text && skipChars)
++*skippedChars;
else
return text;
}
return QString();
}
QString AutoCompleter::insertParagraphSeparator(const QTextCursor &cursor) const
{
if (shouldInsertNewline(cursor)) {
QTextCursor selCursor = cursor;
selCursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
if (! selCursor.selectedText().trimmed().isEmpty())
return QString();
return QLatin1String("}\n");
}
return QLatin1String("}");
}