1 /****************************************************************************
2 **
3 ** Copyright (C) 2018 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "documentcontentcompletion.h"
27
28 #include "assistinterface.h"
29 #include "assistproposalitem.h"
30 #include "genericproposal.h"
31 #include "genericproposalmodel.h"
32 #include "iassistprocessor.h"
33 #include "../snippets/snippetassistcollector.h"
34 #include "../completionsettings.h"
35 #include "../texteditorsettings.h"
36
37 #include <utils/algorithm.h>
38 #include <utils/runextensions.h>
39
40 #include <QElapsedTimer>
41 #include <QRegularExpression>
42 #include <QSet>
43 #include <QTextBlock>
44 #include <QTextDocument>
45
46 using namespace TextEditor;
47
48 class DocumentContentCompletionProcessor final : public IAssistProcessor
49 {
50 public:
51 DocumentContentCompletionProcessor(const QString &snippetGroupId);
52 ~DocumentContentCompletionProcessor() final;
53
54 IAssistProposal *perform(const AssistInterface *interface) override;
running()55 bool running() final { return m_watcher.isRunning(); }
56 void cancel() final;
57
58 private:
59 QString m_snippetGroup;
60 QFutureWatcher<QStringList> m_watcher;
61 };
62
DocumentContentCompletionProvider(const QString & snippetGroup)63 DocumentContentCompletionProvider::DocumentContentCompletionProvider(const QString &snippetGroup)
64 : m_snippetGroup(snippetGroup)
65 { }
66
runType() const67 IAssistProvider::RunType DocumentContentCompletionProvider::runType() const
68 {
69 return Asynchronous;
70 }
71
createProcessor() const72 IAssistProcessor *DocumentContentCompletionProvider::createProcessor() const
73 {
74 return new DocumentContentCompletionProcessor(m_snippetGroup);
75 }
76
DocumentContentCompletionProcessor(const QString & snippetGroupId)77 DocumentContentCompletionProcessor::DocumentContentCompletionProcessor(const QString &snippetGroupId)
78 : m_snippetGroup(snippetGroupId)
79 { }
80
~DocumentContentCompletionProcessor()81 DocumentContentCompletionProcessor::~DocumentContentCompletionProcessor()
82 {
83 cancel();
84 }
85
createProposal(QFutureInterface<QStringList> & future,const QString & text,const QString & wordUnderCursor)86 static void createProposal(QFutureInterface<QStringList> &future, const QString &text,
87 const QString &wordUnderCursor)
88 {
89 const QRegularExpression wordRE("([a-zA-Z_][a-zA-Z0-9_]{2,})");
90
91 QSet<QString> words;
92 QRegularExpressionMatchIterator it = wordRE.globalMatch(text);
93 int wordUnderCursorFound = 0;
94 while (it.hasNext()) {
95 if (future.isCanceled())
96 return;
97 QRegularExpressionMatch match = it.next();
98 const QString &word = match.captured();
99 if (word == wordUnderCursor) {
100 // Only add the word under cursor if it
101 // already appears elsewhere in the text
102 if (++wordUnderCursorFound < 2)
103 continue;
104 }
105
106 if (!words.contains(word))
107 words.insert(word);
108 }
109
110 future.reportResult(Utils::toList(words));
111 }
112
perform(const AssistInterface * interface)113 IAssistProposal *DocumentContentCompletionProcessor::perform(const AssistInterface *interface)
114 {
115 QScopedPointer<const AssistInterface> assistInterface(interface);
116 if (running())
117 return nullptr;
118
119 int pos = interface->position();
120
121 QChar chr;
122 // Skip to the start of a name
123 do {
124 chr = interface->characterAt(--pos);
125 } while (chr.isLetterOrNumber() || chr == '_');
126
127 ++pos;
128 int length = interface->position() - pos;
129
130 if (interface->reason() == IdleEditor) {
131 QChar characterUnderCursor = interface->characterAt(interface->position());
132 if (characterUnderCursor.isLetterOrNumber()
133 || length < TextEditorSettings::completionSettings().m_characterThreshold) {
134 return nullptr;
135 }
136 }
137
138 const QString wordUnderCursor = interface->textAt(pos, length);
139 const QString text = interface->textDocument()->toPlainText();
140
141 m_watcher.setFuture(Utils::runAsync(&createProposal, text, wordUnderCursor));
142 QObject::connect(&m_watcher, &QFutureWatcher<QStringList>::resultReadyAt,
143 &m_watcher, [this, pos](int index){
144 const TextEditor::SnippetAssistCollector snippetCollector(
145 m_snippetGroup, QIcon(":/texteditor/images/snippet.png"));
146 QList<AssistProposalItemInterface *> items = snippetCollector.collect();
147 for (const QString &word : m_watcher.resultAt(index)) {
148 auto item = new AssistProposalItem();
149 item->setText(word);
150 items.append(item);
151 }
152 setAsyncProposalAvailable(new GenericProposal(pos, items));
153 });
154 return nullptr;
155 }
156
cancel()157 void DocumentContentCompletionProcessor::cancel()
158 {
159 if (running())
160 m_watcher.cancel();
161 }
162