clangassistproposalitem.cpp 13.2 KB
Newer Older
1 2
/****************************************************************************
**
3 4
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
5 6 7 8 9 10 11
**
** 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
12 13 14
** 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.
15
**
16 17 18 19 20 21 22
** 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.
23 24 25 26 27
**
****************************************************************************/

#include "clangassistproposalitem.h"

28
#include "clangcompletionchunkstotextconverter.h"
29

30
#include <cplusplus/Icons.h>
31 32 33 34
#include <cplusplus/MatchingText.h>
#include <cplusplus/Token.h>

#include <texteditor/completionsettings.h>
35
#include <texteditor/textdocument.h>
36 37 38 39 40 41 42 43 44
#include <texteditor/texteditor.h>
#include <texteditor/texteditorsettings.h>

using namespace CPlusPlus;
using namespace ClangBackEnd;

namespace ClangCodeModel {
namespace Internal {

45
bool ClangAssistProposalItem::prematurelyApplies(const QChar &typedCharacter) const
46
{
Marco Bubke's avatar
Marco Bubke committed
47
    bool applies = false;
48 49

    if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT)
50
        applies = QString::fromLatin1("(,").contains(typedCharacter);
51
    else if (m_completionOperator == T_STRING_LITERAL || m_completionOperator == T_ANGLE_STRING_LITERAL)
52
        applies = (typedCharacter == QLatin1Char('/')) && text().endsWith(QLatin1Char('/'));
53
    else if (codeCompletion().completionKind() == CodeCompletion::ObjCMessageCompletionKind)
54
        applies = QString::fromLatin1(";.,").contains(typedCharacter);
55
    else
56
        applies = QString::fromLatin1(";.,:(").contains(typedCharacter);
57

Marco Bubke's avatar
Marco Bubke committed
58
    if (applies)
59
        m_typedCharacter = typedCharacter;
60

Marco Bubke's avatar
Marco Bubke committed
61
    return applies;
62 63
}

64 65 66 67 68
bool ClangAssistProposalItem::implicitlyApplies() const
{
    return false;
}

69
void ClangAssistProposalItem::apply(TextEditor::TextDocumentManipulatorInterface &manipulator,
70
                                    int basePosition) const
71
{
72
    const CodeCompletion ccr = codeCompletion();
73

74
    QString textToBeInserted = text();
75
    QString extraCharacters;
76 77
    int extraLength = 0;
    int cursorOffset = 0;
78
    bool setAutoCompleteSkipPos = false;
79 80 81

    bool autoParenthesesEnabled = true;
    if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) {
82 83 84 85
        extraCharacters += QLatin1Char(')');
        if (m_typedCharacter == QLatin1Char('(')) // Eat the opening parenthesis
            m_typedCharacter = QChar();
    } else  if (ccr.completionKind() == CodeCompletion::KeywordCompletionKind) {
86
        CompletionChunksToTextConverter converter;
Nikolai Kosjar's avatar
Nikolai Kosjar committed
87
        converter.setupForKeywords();
88 89 90

        converter.parseChunks(ccr.chunks());

91
        textToBeInserted = converter.text();
92 93
        if (converter.hasPlaceholderPositions())
            cursorOffset = converter.placeholderPositions().at(0) - converter.text().size();
94 95 96 97 98 99
    } else if (ccr.completionKind() == CodeCompletion::NamespaceCompletionKind) {
        CompletionChunksToTextConverter converter;

        converter.parseChunks(ccr.chunks()); // Appends "::" after name space name

        textToBeInserted = converter.text();
100 101 102 103 104 105 106 107 108 109 110 111 112
    } else if (!ccr.text().isEmpty()) {
        const TextEditor::CompletionSettings &completionSettings =
                TextEditor::TextEditorSettings::instance()->completionSettings();
        const bool autoInsertBrackets = completionSettings.m_autoInsertBrackets;

        if (autoInsertBrackets &&
                (ccr.completionKind() == CodeCompletion::FunctionCompletionKind
                 || ccr.completionKind() == CodeCompletion::DestructorCompletionKind
                 || ccr.completionKind() == CodeCompletion::SignalCompletionKind
                 || ccr.completionKind() == CodeCompletion::SlotCompletionKind)) {
            // When the user typed the opening parenthesis, he'll likely also type the closing one,
            // in which case it would be annoying if we put the cursor after the already automatically
            // inserted closing parenthesis.
113
            const bool skipClosingParenthesis = m_typedCharacter != QLatin1Char('(');
114 115

            if (completionSettings.m_spaceAfterFunctionName)
116 117 118 119
                extraCharacters += QLatin1Char(' ');
            extraCharacters += QLatin1Char('(');
            if (m_typedCharacter == QLatin1Char('('))
                m_typedCharacter = QChar();
120 121 122

            // If the function doesn't return anything, automatically place the semicolon,
            // unless we're doing a scope completion (then it might be function definition).
123
            const QChar characterAtCursor = manipulator.characterAt(manipulator.currentPosition());
124
            bool endWithSemicolon = m_typedCharacter == QLatin1Char(';')/*
125
                                            || (function->returnType()->isVoidType() && m_completionOperator != T_COLON_COLON)*/; //###
126
            const QChar semicolon = m_typedCharacter.isNull() ? QLatin1Char(';') : m_typedCharacter;
127 128 129

            if (endWithSemicolon && characterAtCursor == semicolon) {
                endWithSemicolon = false;
130
                m_typedCharacter = QChar();
131 132 133 134
            }

            // If the function takes no arguments, automatically place the closing parenthesis
            if (!isOverloaded() && !ccr.hasParameters() && skipClosingParenthesis) {
135
                extraCharacters += QLatin1Char(')');
136
                if (endWithSemicolon) {
137 138
                    extraCharacters += semicolon;
                    m_typedCharacter = QChar();
139 140
                }
            } else if (autoParenthesesEnabled) {
141
                const QChar lookAhead = manipulator.characterAt(manipulator.currentPosition() + 1);
142
                if (MatchingText::shouldInsertMatchingText(lookAhead)) {
143
                    extraCharacters += QLatin1Char(')');
144
                    --cursorOffset;
145
                    setAutoCompleteSkipPos = true;
146
                    if (endWithSemicolon) {
147
                        extraCharacters += semicolon;
148
                        --cursorOffset;
149
                        m_typedCharacter = QChar();
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
                    }
                }
            }
        }

#if 0
        if (autoInsertBrackets && data().canConvert<CompleteFunctionDeclaration>()) {
            if (m_typedChar == QLatin1Char('('))
                m_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(')'));
            extraChars = toInsert.mid(closingParen);
            toInsert.truncate(closingParen);
        }
#endif
    }

    // Append an unhandled typed character, adjusting cursor offset when it had been adjusted before
170 171
    if (!m_typedCharacter.isNull()) {
        extraCharacters += m_typedCharacter;
172 173 174 175 176
        if (cursorOffset != 0)
            --cursorOffset;
    }

    // Avoid inserting characters that are already there
177 178
    const int endsPosition = manipulator.positionAt(TextEditor::EndOfLinePosition);
    const QString existingText = manipulator.textAt(manipulator.currentPosition(), endsPosition - manipulator.currentPosition());
179
    int existLength = 0;
180
    if (!existingText.isEmpty() && ccr.completionKind() != CodeCompletion::KeywordCompletionKind) {
181
        // Calculate the exist length in front of the extra chars
182
        existLength = textToBeInserted.length() - (manipulator.currentPosition() - basePosition);
183
        while (!existingText.startsWith(textToBeInserted.right(existLength))) {
184 185 186 187
            if (--existLength == 0)
                break;
        }
    }
188 189
    for (int i = 0; i < extraCharacters.length(); ++i) {
        const QChar a = extraCharacters.at(i);
190
        const QChar b = manipulator.characterAt(manipulator.currentPosition() + i + existLength);
191 192 193 194 195
        if (a == b)
            ++extraLength;
        else
            break;
    }
196

197
    textToBeInserted += extraCharacters;
198

199
    const int length = manipulator.currentPosition() - basePosition + existLength + extraLength;
200

201 202
    const bool isReplaced = manipulator.replace(basePosition, length, textToBeInserted);
    if (isReplaced) {
203
        if (cursorOffset)
204
            manipulator.setCursorPosition(manipulator.currentPosition() + cursorOffset);
205 206
        if (setAutoCompleteSkipPos)
            manipulator.setAutoCompleteSkipPosition(manipulator.currentPosition());
207 208 209

        if (ccr.completionKind() == CodeCompletion::KeywordCompletionKind)
            manipulator.autoIndent(basePosition, textToBeInserted.size());
210
    }
211 212
}

213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
void ClangAssistProposalItem::setText(const QString &text)
{
    m_text = text;
}

QString ClangAssistProposalItem::text() const
{
    return m_text;
}

QIcon ClangAssistProposalItem::icon() const
{
    using CPlusPlus::Icons;
    static const char SNIPPET_ICON_PATH[] = ":/texteditor/images/snippet.png";
    static const QIcon snippetIcon = QIcon(QLatin1String(SNIPPET_ICON_PATH));

    switch (m_codeCompletion.completionKind()) {
        case CodeCompletion::ClassCompletionKind:
        case CodeCompletion::TemplateClassCompletionKind:
        case CodeCompletion::TypeAliasCompletionKind:
233
            return Icons::iconForType(Icons::ClassIconType);
234
        case CodeCompletion::EnumerationCompletionKind:
235
            return Icons::iconForType(Icons::EnumIconType);
236
        case CodeCompletion::EnumeratorCompletionKind:
237
            return Icons::iconForType(Icons::EnumeratorIconType);
238 239 240 241 242 243 244 245
        case CodeCompletion::ConstructorCompletionKind:
        case CodeCompletion::DestructorCompletionKind:
        case CodeCompletion::FunctionCompletionKind:
        case CodeCompletion::TemplateFunctionCompletionKind:
        case CodeCompletion::ObjCMessageCompletionKind:
            switch (m_codeCompletion.availability()) {
                case CodeCompletion::Available:
                case CodeCompletion::Deprecated:
246
                    return Icons::iconForType(Icons::FuncPublicIconType);
247
                default:
248
                    return Icons::iconForType(Icons::FuncPrivateIconType);
249 250
            }
        case CodeCompletion::SignalCompletionKind:
251
            return Icons::iconForType(Icons::SignalIconType);
252 253 254 255
        case CodeCompletion::SlotCompletionKind:
            switch (m_codeCompletion.availability()) {
                case CodeCompletion::Available:
                case CodeCompletion::Deprecated:
256
                    return Icons::iconForType(Icons::SlotPublicIconType);
257 258
                case CodeCompletion::NotAccessible:
                case CodeCompletion::NotAvailable:
259
                    return Icons::iconForType(Icons::SlotPrivateIconType);
260 261
            }
        case CodeCompletion::NamespaceCompletionKind:
262
            return Icons::iconForType(Icons::NamespaceIconType);
263
        case CodeCompletion::PreProcessorCompletionKind:
264
            return Icons::iconForType(Icons::MacroIconType);
265 266 267 268
        case CodeCompletion::VariableCompletionKind:
            switch (m_codeCompletion.availability()) {
                case CodeCompletion::Available:
                case CodeCompletion::Deprecated:
269
                    return Icons::iconForType(Icons::VarPublicIconType);
270
                default:
271
                    return Icons::iconForType(Icons::VarPrivateIconType);
272 273
            }
        case CodeCompletion::KeywordCompletionKind:
274
            return Icons::iconForType(Icons::KeywordIconType);
275 276 277
        case CodeCompletion::ClangSnippetKind:
            return snippetIcon;
        case CodeCompletion::Other:
278
            return Icons::iconForType(Icons::UnknownIconType);
279 280
        default:
            break;
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
    }

    return QIcon();
}

QString ClangAssistProposalItem::detail() const
{
    QString detail = CompletionChunksToTextConverter::convertToToolTipWithHtml(m_codeCompletion.chunks());

    if (!m_codeCompletion.briefComment().isEmpty())
        detail += QStringLiteral("\n\n") + m_codeCompletion.briefComment().toString();

    return detail;
}

bool ClangAssistProposalItem::isSnippet() const
{
    return false;
}

bool ClangAssistProposalItem::isValid() const
{
    return true;
}

quint64 ClangAssistProposalItem::hash() const
{
    return 0;
}

311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
void ClangAssistProposalItem::keepCompletionOperator(unsigned compOp)
{
    m_completionOperator = compOp;
}

bool ClangAssistProposalItem::isOverloaded() const
{
    return !m_overloads.isEmpty();
}

void ClangAssistProposalItem::addOverload(const CodeCompletion &ccr)
{
    m_overloads.append(ccr);
}

326
void ClangAssistProposalItem::setCodeCompletion(const CodeCompletion &codeCompletion)
327
{
328
    m_codeCompletion = codeCompletion;
329 330
}

331
const ClangBackEnd::CodeCompletion &ClangAssistProposalItem::codeCompletion() const
332
{
333
    return m_codeCompletion;
334 335 336 337 338
}

} // namespace Internal
} // namespace ClangCodeModel