Changeset View
Changeset View
Standalone View
Standalone View
language/assistant/staticassistantsmanager.cpp
1 | /* | 1 | /* | ||
---|---|---|---|---|---|
2 | Copyright 2009 David Nolden <david.nolden.kdevelop@art-master.de> | 2 | Copyright 2009 David Nolden <david.nolden.kdevelop@art-master.de> | ||
3 | Copyright 2014 Kevin Funk <kfunk@kde.org> | 3 | Copyright 2014 Kevin Funk <kfunk@kde.org> | ||
4 | 4 | | |||
5 | This library is free software; you can redistribute it and/or | 5 | This library is free software; you can redistribute it and/or | ||
6 | modify it under the terms of the GNU Library General Public | 6 | modify it under the terms of the GNU Library General Public | ||
7 | License version 2 as published by the Free Software Foundation. | 7 | License version 2 as published by the Free Software Foundation. | ||
8 | 8 | | |||
9 | This library is distributed in the hope that it will be useful, | 9 | This library is distributed in the hope that it will be useful, | ||
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
12 | Library General Public License for more details. | 12 | Library General Public License for more details. | ||
13 | 13 | | |||
14 | You should have received a copy of the GNU Library General Public License | 14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to | 15 | along with this library; see the file COPYING.LIB. If not^, write to | ||
16 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | 16 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||
17 | Boston, MA 02110-1301, USA. | 17 | Boston, MA 02110-1301, USA. | ||
18 | */ | 18 | */ | ||
19 | 19 | | |||
20 | #include "staticassistantsmanager.h" | 20 | #include "staticassistantsmanager.h" | ||
21 | #include <interfaces/icodehighlighting.h> | ||||
21 | #include "util/debug.h" | 22 | #include "util/debug.h" | ||
22 | 23 | | |||
23 | #include <QTimer> | 24 | #include <QTimer> | ||
24 | 25 | | |||
25 | #include <KTextEditor/Document> | 26 | #include <KTextEditor/Document> | ||
26 | #include <KTextEditor/View> | 27 | #include <KTextEditor/View> | ||
27 | 28 | | |||
28 | #include <interfaces/icore.h> | 29 | #include <interfaces/icore.h> | ||
Show All 10 Lines | |||||
39 | 40 | | |||
40 | using namespace KDevelop; | 41 | using namespace KDevelop; | ||
41 | using namespace KTextEditor; | 42 | using namespace KTextEditor; | ||
42 | 43 | | |||
43 | struct StaticAssistantsManager::Private | 44 | struct StaticAssistantsManager::Private | ||
44 | { | 45 | { | ||
45 | Private(StaticAssistantsManager* qq) | 46 | Private(StaticAssistantsManager* qq) | ||
46 | : q(qq) | 47 | : q(qq) | ||
47 | { | 48 | { } | ||
48 | connect(DUChain::self(), &DUChain::updateReady, | | |||
49 | q, [this] (const IndexedString& url, const ReferencedTopDUContext& topContext) { | | |||
50 | updateReady(url, topContext); | | |||
51 | }); | | |||
52 | } | | |||
53 | | ||||
54 | void eventuallyStartAssistant(); | | |||
55 | void startAssistant(KDevelop::IAssistant::Ptr assistant); | | |||
56 | void checkAssistantForProblems(KDevelop::TopDUContext* top); | | |||
57 | 49 | | |||
50 | void updateReady(const IndexedString& document, const KDevelop::ReferencedTopDUContext& topContext); | ||||
58 | void documentLoaded(KDevelop::IDocument*); | 51 | void documentLoaded(KDevelop::IDocument*); | ||
59 | void textInserted(Document* document, const Cursor& cursor, const QString& text); | 52 | void textInserted(KTextEditor::Document* document, const Cursor& cursor, const QString& text); | ||
60 | void textRemoved(Document* document, const Range& cursor, const QString& removedText); | 53 | void textRemoved(KTextEditor::Document* document, const Range& cursor, const QString& removedText); | ||
61 | void updateReady(const IndexedString& document, const ReferencedTopDUContext& topContext); | | |||
62 | void documentActivated(KDevelop::IDocument*); | | |||
63 | void cursorPositionChanged(KTextEditor::View*, const KTextEditor::Cursor&); | | |||
64 | void timeout(); | | |||
65 | 54 | | |||
66 | StaticAssistantsManager* q; | 55 | StaticAssistantsManager* q; | ||
67 | 56 | | |||
68 | QPointer<KTextEditor::View> m_currentView; | 57 | QVector<StaticAssistant::Ptr> m_registeredAssistants; | ||
69 | KTextEditor::Cursor m_assistantStartedAt; | | |||
70 | KDevelop::IndexedString m_currentDocument; | | |||
71 | QExplicitlySharedDataPointer<KDevelop::IAssistant> m_activeAssistant; | | |||
72 | QList<StaticAssistant::Ptr> m_registeredAssistants; | | |||
73 | bool m_activeProblemAssistant = false; | | |||
74 | QTimer* m_timer; | | |||
75 | | ||||
76 | SafeDocumentPointer m_eventualDocument; | | |||
77 | KTextEditor::Range m_eventualRange; | | |||
78 | QString m_eventualRemovedText; | | |||
79 | QMetaObject::Connection m_cursorPositionChangeConnection; | | |||
80 | }; | 58 | }; | ||
81 | 59 | | |||
82 | StaticAssistantsManager::StaticAssistantsManager(QObject* parent) | 60 | StaticAssistantsManager::StaticAssistantsManager(QObject* parent) | ||
83 | : QObject(parent) | 61 | : QObject(parent) | ||
84 | , d(new Private(this)) | 62 | , d(new Private(this)) | ||
85 | { | 63 | { | ||
86 | d->m_timer = new QTimer(this); | | |||
87 | d->m_timer->setSingleShot(true); | | |||
88 | d->m_timer->setInterval(400); | | |||
89 | connect(d->m_timer, &QTimer::timeout, this, [&] { d->timeout(); }); | | |||
90 | | ||||
91 | connect(KDevelop::ICore::self()->documentController(), | 64 | connect(KDevelop::ICore::self()->documentController(), | ||
92 | &IDocumentController::documentLoaded, | 65 | &IDocumentController::documentLoaded, | ||
93 | this, [&] (IDocument* document) { d->documentLoaded(document); }); | 66 | this, [&] (IDocument* document) { d->documentLoaded(document); }); | ||
94 | connect(KDevelop::ICore::self()->documentController(), | | |||
95 | &IDocumentController::documentActivated, | | |||
96 | this, [&] (IDocument* doc) { d->documentActivated(doc); }); | | |||
97 | | ||||
98 | foreach (IDocument* document, ICore::self()->documentController()->openDocuments()) { | 67 | foreach (IDocument* document, ICore::self()->documentController()->openDocuments()) { | ||
99 | d->documentLoaded(document); | 68 | d->documentLoaded(document); | ||
100 | } | 69 | } | ||
101 | } | 70 | } | ||
102 | 71 | | |||
103 | StaticAssistantsManager::~StaticAssistantsManager() | 72 | StaticAssistantsManager::~StaticAssistantsManager() | ||
104 | { | 73 | { | ||
105 | } | 74 | } | ||
106 | 75 | | |||
107 | QExplicitlySharedDataPointer<IAssistant> StaticAssistantsManager::activeAssistant() | | |||
108 | { | | |||
109 | return d->m_activeAssistant; | | |||
110 | } | | |||
111 | | ||||
112 | void StaticAssistantsManager::registerAssistant(const StaticAssistant::Ptr assistant) | 76 | void StaticAssistantsManager::registerAssistant(const StaticAssistant::Ptr assistant) | ||
113 | { | 77 | { | ||
114 | if (d->m_registeredAssistants.contains(assistant)) | 78 | if (d->m_registeredAssistants.contains(assistant)) | ||
115 | return; | 79 | return; | ||
116 | 80 | | |||
117 | d->m_registeredAssistants << assistant; | 81 | d->m_registeredAssistants << assistant; | ||
118 | } | 82 | } | ||
119 | 83 | | |||
120 | void StaticAssistantsManager::unregisterAssistant(const StaticAssistant::Ptr assistant) | 84 | void StaticAssistantsManager::unregisterAssistant(const StaticAssistant::Ptr assistant) | ||
121 | { | 85 | { | ||
122 | d->m_registeredAssistants.removeOne(assistant); | 86 | d->m_registeredAssistants.removeOne(assistant); | ||
123 | } | 87 | } | ||
124 | 88 | | |||
125 | QList<StaticAssistant::Ptr> StaticAssistantsManager::registeredAssistants() const | 89 | QVector<StaticAssistant::Ptr> StaticAssistantsManager::registeredAssistants() const | ||
126 | { | 90 | { | ||
127 | return d->m_registeredAssistants; | 91 | return d->m_registeredAssistants; | ||
128 | } | 92 | } | ||
129 | 93 | | |||
130 | void StaticAssistantsManager::Private::documentLoaded(IDocument* document) | 94 | void StaticAssistantsManager::Private::documentLoaded(IDocument* document) | ||
131 | { | 95 | { | ||
132 | if (document->textDocument()) { | 96 | if (document->textDocument()) { | ||
133 | connect(document->textDocument(), | 97 | auto doc = document->textDocument(); | ||
134 | &Document::textInserted, q, | 98 | connect(doc, &KTextEditor::Document::textInserted, q, | ||
135 | [&] (Document* document, const Cursor& cursor, const QString& text) { textInserted(document, cursor, text); }); | 99 | [&] (KTextEditor::Document* doc, const Cursor& cursor, const QString& text) { | ||
136 | connect(document->textDocument(), | 100 | textInserted(doc, cursor, text); | ||
137 | &Document::textRemoved, q, | 101 | }); | ||
138 | [&] (Document* document, const Range& range, const QString& removedText) { textRemoved(document, range, removedText); }); | 102 | connect(doc, &KTextEditor::Document::textRemoved, q, | ||
103 | [&] (KTextEditor::Document* doc, const Range& range, const QString& removedText) { | ||||
104 | textRemoved(doc, range, removedText); | ||||
105 | }); | ||||
139 | } | 106 | } | ||
140 | } | 107 | } | ||
141 | 108 | | |||
142 | void StaticAssistantsManager::hideAssistant() | 109 | void StaticAssistantsManager::Private::textInserted(Document* doc, const Cursor& cursor, const QString& text) | ||
143 | { | 110 | { | ||
144 | d->m_activeAssistant = QExplicitlySharedDataPointer<KDevelop::IAssistant>(); | 111 | Q_FOREACH ( auto assistant, m_registeredAssistants ) { | ||
145 | d->m_activeProblemAssistant = false; | 112 | auto range = Range(cursor, cursor+Cursor(0, text.size())); | ||
146 | emit activeAssistantChanged(); | 113 | assistant->textChanged(doc, range, {}); | ||
147 | } | 114 | } | ||
148 | | ||||
149 | void StaticAssistantsManager::Private::textInserted(Document* document, const Cursor& cursor, const QString& text) | | |||
150 | { | | |||
151 | m_eventualDocument = document; | | |||
152 | m_eventualRange = Range(cursor, text.size()); | | |||
153 | m_eventualRemovedText.clear(); | | |||
154 | QMetaObject::invokeMethod(q, "eventuallyStartAssistant", Qt::QueuedConnection); | | |||
155 | } | 115 | } | ||
156 | 116 | | |||
157 | void StaticAssistantsManager::Private::textRemoved(Document* document, const Range& range, | 117 | void StaticAssistantsManager::Private::textRemoved(Document* doc, const Range& range, | ||
158 | const QString& removedText) | 118 | const QString& removedText) | ||
159 | { | 119 | { | ||
160 | m_eventualDocument = document; | 120 | Q_FOREACH ( auto assistant, m_registeredAssistants ) { | ||
161 | m_eventualRange = range; | 121 | assistant->textChanged(doc, range, removedText); | ||
162 | m_eventualRemovedText = removedText; | 122 | } | ||
163 | QMetaObject::invokeMethod(q, "eventuallyStartAssistant", Qt::QueuedConnection); | | |||
164 | } | 123 | } | ||
165 | 124 | | |||
166 | void StaticAssistantsManager::Private::eventuallyStartAssistant() | 125 | void StaticAssistantsManager::notifyAssistants(const IndexedString& url, const KDevelop::ReferencedTopDUContext& context) | ||
167 | { | 126 | { | ||
168 | if (!m_eventualDocument) { | 127 | Q_FOREACH ( auto assistant, d->m_registeredAssistants ) { | ||
169 | return; | 128 | assistant->updateReady(url, context); | ||
129 | } | ||||
170 | } | 130 | } | ||
171 | 131 | | |||
132 | QVector<KDevelop::Problem::Ptr> KDevelop::StaticAssistantsManager::problemsForContext(const KDevelop::ReferencedTopDUContext& top) | ||||
133 | { | ||||
172 | View* view = ICore::self()->documentController()->activeTextDocumentView(); | 134 | View* view = ICore::self()->documentController()->activeTextDocumentView(); | ||
173 | if (!view) { | 135 | if (!view || !top || IndexedString(view->document()->url()) != top->url()) { | ||
174 | return; | 136 | return {}; | ||
175 | } | | |||
176 | if (view->document() != m_eventualDocument) { | | |||
177 | qWarning(LANGUAGE) << "Active view does not belong to document of last observed change!"; | | |||
178 | return; | | |||
179 | } | 137 | } | ||
180 | 138 | | |||
181 | auto language = ICore::self()->languageController()->languagesForUrl(m_eventualDocument.data()->url()).value(0); | 139 | auto doc = top->url(); | ||
140 | auto language = ICore::self()->languageController()->languagesForUrl(doc.toUrl()).value(0); | ||||
182 | if (!language) { | 141 | if (!language) { | ||
183 | return; | 142 | return {}; | ||
184 | } | 143 | } | ||
185 | 144 | | |||
145 | auto ret = QVector<KDevelop::Problem::Ptr>(); | ||||
186 | qCDebug(LANGUAGE) << "Trying to find assistants for language" << language->name(); | 146 | qCDebug(LANGUAGE) << "Trying to find assistants for language" << language->name(); | ||
187 | foreach (const auto& assistant, m_registeredAssistants) { | 147 | foreach (const auto& assistant, d->m_registeredAssistants) { | ||
188 | if (assistant->supportedLanguage() != language) | 148 | if (assistant->supportedLanguage() != language) | ||
189 | continue; | 149 | continue; | ||
190 | 150 | | |||
191 | // notify assistant about editor changes | | |||
192 | assistant->textChanged(view, m_eventualRange, m_eventualRemovedText); | | |||
193 | | ||||
194 | if (assistant->isUseful()) { | 151 | if (assistant->isUseful()) { | ||
195 | startAssistant(IAssistant::Ptr(assistant.data())); | 152 | qDebug() << "assistant is now useful:" << assistant.data(); | ||
196 | break; | | |||
197 | } | | |||
198 | } | | |||
199 | | ||||
200 | // optimize, esp. for setText() calls as done in e.g. reformat source | | |||
201 | // only start the assitant once for multiple textRemoved/textInserted signals | | |||
202 | m_eventualDocument.clear(); | | |||
203 | m_eventualRange = Range::invalid(); | | |||
204 | m_eventualRemovedText.clear(); | | |||
205 | } | | |||
206 | | ||||
207 | void StaticAssistantsManager::Private::startAssistant(IAssistant::Ptr assistant) | | |||
208 | { | | |||
209 | if (assistant == m_activeAssistant) { | | |||
210 | return; | | |||
211 | } | | |||
212 | | ||||
213 | qCDebug(LANGUAGE()) << "Starting assistant:" << assistant->title(); | | |||
214 | | ||||
215 | if (m_activeAssistant) { | | |||
216 | m_activeAssistant->doHide(); | | |||
217 | } | | |||
218 | | ||||
219 | if (!m_currentView) | | |||
220 | return; | | |||
221 | | ||||
222 | m_activeAssistant = assistant; | | |||
223 | if (m_activeAssistant) { | | |||
224 | connect(m_activeAssistant.data(), &IAssistant::hide, q, &StaticAssistantsManager::hideAssistant, Qt::UniqueConnection); | | |||
225 | ICore::self()->uiController()->popUpAssistant(IAssistant::Ptr(m_activeAssistant.data())); | | |||
226 | | ||||
227 | m_assistantStartedAt = m_currentView.data()->cursorPosition(); | | |||
228 | } | | |||
229 | | ||||
230 | emit q->activeAssistantChanged(); | | |||
231 | } | | |||
232 | | ||||
233 | void StaticAssistantsManager::Private::updateReady(const IndexedString& url, const ReferencedTopDUContext& topContext) | | |||
234 | { | | |||
235 | if (ICore::self()->shuttingDown()) { | | |||
236 | return; | | |||
237 | } | | |||
238 | | ||||
239 | if (url != m_currentDocument) { | | |||
240 | return; | | |||
241 | } | | |||
242 | | ||||
243 | if (m_activeAssistant) { | | |||
244 | if (m_activeProblemAssistant) { | | |||
245 | m_activeAssistant->doHide(); //Hide the assistant, as we will create a new one if the problem is still there | | |||
246 | } else { | | |||
247 | return; | | |||
248 | } | | |||
249 | } | | |||
250 | | ||||
251 | DUChainReadLocker lock(DUChain::lock(), 300); | | |||
252 | if (!lock.locked()) { | | |||
253 | return; | | |||
254 | } | | |||
255 | | ||||
256 | if (topContext) { | | |||
257 | checkAssistantForProblems(topContext); | | |||
258 | } | | |||
259 | } | | |||
260 | | ||||
261 | void StaticAssistantsManager::Private::cursorPositionChanged(View*, const Cursor& pos) | | |||
262 | { | | |||
263 | if (m_activeAssistant && m_assistantStartedAt.isValid() | | |||
264 | && abs(m_assistantStartedAt.line() - pos.line()) >= 1) | | |||
265 | { | | |||
266 | m_activeAssistant->doHide(); | | |||
267 | } | | |||
268 | | ||||
269 | m_timer->start(); | | |||
270 | } | | |||
271 | | ||||
272 | void StaticAssistantsManager::Private::documentActivated(IDocument* doc) | | |||
273 | { | | |||
274 | if (doc) { | | |||
275 | m_currentDocument = IndexedString(doc->url()); | | |||
276 | } | | |||
277 | | ||||
278 | if (m_currentView) { | | |||
279 | QObject::disconnect(m_cursorPositionChangeConnection); | | |||
280 | m_currentView.clear(); | | |||
281 | } | | |||
282 | | ||||
283 | m_currentView = ICore::self()->documentController()->activeTextDocumentView(); | | |||
284 | 153 | | |||
285 | if (m_currentView) { | 154 | auto p = new KDevelop::StaticAssistantProblem(); | ||
286 | m_cursorPositionChangeConnection = connect(m_currentView.data(), | 155 | auto range = assistant->displayRange(); | ||
287 | &View::cursorPositionChanged, q, | 156 | qDebug() << "range:" << range; | ||
288 | [&] (View* v, const Cursor& pos) { cursorPositionChanged(v, pos); }); | 157 | p->setFinalLocation(DocumentRange(doc, range)); | ||
289 | } | 158 | p->setSource(KDevelop::IProblem::SemanticAnalysis); | ||
290 | } | 159 | p->setSeverity(KDevelop::IProblem::Warning); | ||
160 | p->setDescription(assistant->title()); | ||||
161 | p->setSolutionAssistant(IAssistant::Ptr(assistant.data())); | ||||
291 | 162 | | |||
292 | void StaticAssistantsManager::Private::checkAssistantForProblems(TopDUContext* top) | 163 | ret.append(KDevelop::Problem::Ptr(p)); | ||
293 | { | | |||
294 | foreach (ProblemPointer problem, top->problems()) { | | |||
295 | if (m_currentView && m_currentView.data()->cursorPosition().line() == problem->range().start.line) { | | |||
296 | IAssistant::Ptr solution = problem->solutionAssistant(); | | |||
297 | if(solution) { | | |||
298 | startAssistant(solution); | | |||
299 | m_activeProblemAssistant = true; | | |||
300 | break; | | |||
301 | } | | |||
302 | } | 164 | } | ||
303 | } | 165 | } | ||
166 | return ret; | ||||
304 | } | 167 | } | ||
305 | 168 | | |||
306 | void StaticAssistantsManager::Private::timeout() | | |||
307 | { | | |||
308 | if (!m_activeAssistant && m_currentView) { | | |||
309 | DUChainReadLocker lock(DUChain::lock(), 300); | | |||
310 | if (!lock.locked()) { | | |||
311 | return; | | |||
312 | } | | |||
313 | 169 | | |||
314 | TopDUContext* top = DUChainUtils::standardContextForUrl(m_currentDocument.toUrl()); | | |||
315 | if (top) { | | |||
316 | checkAssistantForProblems(top); | | |||
317 | } | | |||
318 | } | | |||
319 | } | | |||
320 | 170 | | |||
321 | #include "moc_staticassistantsmanager.cpp" | 171 | #include "moc_staticassistantsmanager.cpp" |