1 /****************************************************************************
2 **
3 ** Copyright (C) 2017 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 "clangbackendcommunicator.h"
27
28 #include "clangbackendlogging.h"
29 #include "clangcompletionassistprocessor.h"
30 #include "clangmodelmanagersupport.h"
31 #include "clangutils.h"
32
33 #include <coreplugin/editormanager/editormanager.h>
34 #include <coreplugin/icore.h>
35 #include <coreplugin/messagemanager.h>
36
37 #include <cpptools/abstracteditorsupport.h>
38 #include <cpptools/baseeditordocumentprocessor.h>
39 #include <cpptools/cppmodelmanager.h>
40 #include <cpptools/editordocumenthandle.h>
41 #include <cpptools/projectinfo.h>
42 #include <cpptools/cpptoolsbridge.h>
43
44 #include <texteditor/codeassist/functionhintproposal.h>
45 #include <texteditor/codeassist/iassistprocessor.h>
46 #include <texteditor/texteditor.h>
47
48 #include <clangsupport/filecontainer.h>
49 #include <clangsupport/clangcodemodelservermessages.h>
50
51 #include <utils/globalfilechangeblocker.h>
52 #include <utils/qtcassert.h>
53
54 #include <QDateTime>
55 #include <QDir>
56 #include <QTextBlock>
57
58 using namespace CPlusPlus;
59 using namespace ClangBackEnd;
60 using namespace TextEditor;
61 using namespace Utils;
62
63 enum { backEndStartTimeOutInMs = 10000 };
64
backendProcessPath()65 static QString backendProcessPath()
66 {
67 return Core::ICore::libexecPath("clangbackend" QTC_HOST_EXE_SUFFIX).toString();
68 }
69
70 namespace ClangCodeModel {
71 namespace Internal {
72
73 class DummyBackendSender : public ClangBackEnd::ClangCodeModelServerInterface
74 {
75 public:
end()76 void end() override {}
77
documentsOpened(const DocumentsOpenedMessage &)78 void documentsOpened(const DocumentsOpenedMessage &) override {}
documentsChanged(const DocumentsChangedMessage &)79 void documentsChanged(const DocumentsChangedMessage &) override {}
documentsClosed(const DocumentsClosedMessage &)80 void documentsClosed(const DocumentsClosedMessage &) override {}
documentVisibilityChanged(const DocumentVisibilityChangedMessage &)81 void documentVisibilityChanged(const DocumentVisibilityChangedMessage &) override {}
82
unsavedFilesUpdated(const UnsavedFilesUpdatedMessage &)83 void unsavedFilesUpdated(const UnsavedFilesUpdatedMessage &) override {}
unsavedFilesRemoved(const UnsavedFilesRemovedMessage &)84 void unsavedFilesRemoved(const UnsavedFilesRemovedMessage &) override {}
85
requestCompletions(const RequestCompletionsMessage &)86 void requestCompletions(const RequestCompletionsMessage &) override {}
requestAnnotations(const RequestAnnotationsMessage &)87 void requestAnnotations(const RequestAnnotationsMessage &) override {}
requestReferences(const RequestReferencesMessage &)88 void requestReferences(const RequestReferencesMessage &) override {}
requestFollowSymbol(const RequestFollowSymbolMessage &)89 void requestFollowSymbol(const RequestFollowSymbolMessage &) override {}
requestToolTip(const RequestToolTipMessage &)90 void requestToolTip(const RequestToolTipMessage &) override {}
91 };
92
BackendCommunicator()93 BackendCommunicator::BackendCommunicator()
94 : m_connection(&m_receiver)
95 , m_sender(new DummyBackendSender())
96 {
97 m_backendStartTimeOut.setSingleShot(true);
98 connect(&m_backendStartTimeOut, &QTimer::timeout,
99 this, &BackendCommunicator::logStartTimeOut);
100
101 m_receiver.setAliveHandler([this]() { m_connection.resetProcessAliveTimer(); });
102
103 connect(Core::EditorManager::instance(), &Core::EditorManager::editorAboutToClose,
104 this, &BackendCommunicator::onEditorAboutToClose);
105 connect(Core::ICore::instance(), &Core::ICore::coreAboutToClose,
106 this, &BackendCommunicator::setupDummySender);
107 auto globalFCB = GlobalFileChangeBlocker::instance();
108 m_postponeBackendJobs = globalFCB->isBlocked();
109 connect(globalFCB, &GlobalFileChangeBlocker::stateChanged,
110 this, &BackendCommunicator::setBackendJobsPostponed);
111
112 initializeBackend();
113 }
114
~BackendCommunicator()115 BackendCommunicator::~BackendCommunicator()
116 {
117 disconnect(&m_connection, nullptr, this, nullptr);
118 }
119
initializeBackend()120 void BackendCommunicator::initializeBackend()
121 {
122 const QString clangBackEndProcessPath = backendProcessPath();
123 if (!QFileInfo::exists(clangBackEndProcessPath)) {
124 logExecutableDoesNotExist();
125 return;
126 }
127 qCDebug(ipcLog) << "Starting" << clangBackEndProcessPath;
128
129 m_connection.setProcessAliveTimerInterval(30 * 1000);
130 m_connection.setProcessPath(clangBackEndProcessPath);
131
132 connect(&m_connection, &ConnectionClient::connectedToLocalSocket,
133 this, &BackendCommunicator::onConnectedToBackend);
134 connect(&m_connection, &ConnectionClient::disconnectedFromLocalSocket,
135 this, &BackendCommunicator::setupDummySender);
136
137 m_connection.startProcessAndConnectToServerAsynchronously();
138 m_backendStartTimeOut.start(backEndStartTimeOutInMs);
139 }
140
141 namespace {
removeDuplicates(Utf8StringVector & visibleEditorDocumentsFilePaths)142 void removeDuplicates(Utf8StringVector &visibleEditorDocumentsFilePaths)
143 {
144 std::sort(visibleEditorDocumentsFilePaths.begin(),
145 visibleEditorDocumentsFilePaths.end());
146 const auto end = std::unique(visibleEditorDocumentsFilePaths.begin(),
147 visibleEditorDocumentsFilePaths.end());
148 visibleEditorDocumentsFilePaths.erase(end,
149 visibleEditorDocumentsFilePaths.end());
150 }
151
removeNonCppEditors(QList<Core::IEditor * > & visibleEditors)152 void removeNonCppEditors(QList<Core::IEditor*> &visibleEditors)
153 {
154 const auto isNotCppEditor = [] (Core::IEditor *editor) {
155 return !CppTools::CppModelManager::isCppEditor(editor);
156 };
157
158 const auto end = std::remove_if(visibleEditors.begin(),
159 visibleEditors.end(),
160 isNotCppEditor);
161
162 visibleEditors.erase(end, visibleEditors.end());
163 }
164
visibleCppEditorDocumentsFilePaths()165 Utf8StringVector visibleCppEditorDocumentsFilePaths()
166 {
167 auto visibleEditors = CppTools::CppToolsBridge::visibleEditors();
168
169 removeNonCppEditors(visibleEditors);
170
171 Utf8StringVector visibleCppEditorDocumentsFilePaths;
172 visibleCppEditorDocumentsFilePaths.reserve(visibleEditors.size());
173
174 const auto editorFilePaths = [] (Core::IEditor *editor) {
175 return Utf8String(editor->document()->filePath().toString());
176 };
177
178 std::transform(visibleEditors.begin(),
179 visibleEditors.end(),
180 std::back_inserter(visibleCppEditorDocumentsFilePaths),
181 editorFilePaths);
182
183 removeDuplicates(visibleCppEditorDocumentsFilePaths);
184
185 return visibleCppEditorDocumentsFilePaths;
186 }
187
188 }
189
documentVisibilityChanged()190 void BackendCommunicator::documentVisibilityChanged()
191 {
192 documentVisibilityChanged(currentCppEditorDocumentFilePath(),
193 visibleCppEditorDocumentsFilePaths());
194 }
195
isNotWaitingForCompletion() const196 bool BackendCommunicator::isNotWaitingForCompletion() const
197 {
198 return !m_receiver.isExpectingCompletionsMessage();
199 }
200
setBackendJobsPostponed(bool postponed)201 void BackendCommunicator::setBackendJobsPostponed(bool postponed)
202 {
203 if (postponed) {
204 documentVisibilityChanged(Utf8String(), {});
205 m_postponeBackendJobs = postponed;
206 } else {
207 m_postponeBackendJobs = postponed;
208 documentVisibilityChanged();
209 }
210 }
211
documentVisibilityChanged(const Utf8String & currentEditorFilePath,const Utf8StringVector & visibleEditorsFilePaths)212 void BackendCommunicator::documentVisibilityChanged(const Utf8String ¤tEditorFilePath,
213 const Utf8StringVector &visibleEditorsFilePaths)
214 {
215 if (m_postponeBackendJobs)
216 return;
217
218 const DocumentVisibilityChangedMessage message(currentEditorFilePath, visibleEditorsFilePaths);
219 m_sender->documentVisibilityChanged(message);
220 }
221
restoreCppEditorDocuments()222 void BackendCommunicator::restoreCppEditorDocuments()
223 {
224 resetCppEditorDocumentProcessors();
225 CppTools::CppModelManager::instance()->updateCppEditorDocuments();
226 }
227
resetCppEditorDocumentProcessors()228 void BackendCommunicator::resetCppEditorDocumentProcessors()
229 {
230 using namespace CppTools;
231
232 const auto cppEditorDocuments = CppModelManager::instance()->cppEditorDocuments();
233 foreach (CppEditorDocumentHandle *cppEditorDocument, cppEditorDocuments)
234 cppEditorDocument->resetProcessor();
235 }
236
unsavedFilesUpdatedForUiHeaders()237 void BackendCommunicator::unsavedFilesUpdatedForUiHeaders()
238 {
239 using namespace CppTools;
240
241 const auto editorSupports = CppModelManager::instance()->abstractEditorSupports();
242 foreach (const AbstractEditorSupport *es, editorSupports) {
243 const QString mappedPath
244 = ClangModelManagerSupport::instance()->dummyUiHeaderOnDiskPath(es->fileName());
245 unsavedFilesUpdated(mappedPath, es->contents(), es->revision());
246 }
247 }
248
documentsChangedFromCppEditorDocument(const QString & filePath)249 void BackendCommunicator::documentsChangedFromCppEditorDocument(const QString &filePath)
250 {
251 const CppTools::CppEditorDocumentHandle *document = cppDocument(filePath);
252 QTC_ASSERT(document, return);
253 documentsChanged(filePath, document->contents(), document->revision());
254 }
255
unsavedFilesUpdatedFromCppEditorDocument(const QString & filePath)256 void BackendCommunicator::unsavedFilesUpdatedFromCppEditorDocument(const QString &filePath)
257 {
258 const CppTools::CppEditorDocumentHandle *document = cppDocument(filePath);
259 QTC_ASSERT(document, return);
260 unsavedFilesUpdated(filePath, document->contents(), document->revision());
261 }
262
documentsChanged(const QString & filePath,const QByteArray & contents,uint documentRevision)263 void BackendCommunicator::documentsChanged(const QString &filePath,
264 const QByteArray &contents,
265 uint documentRevision)
266 {
267 const bool hasUnsavedContent = true;
268
269 documentsChanged({{filePath,
270 Utf8String::fromByteArray(contents),
271 hasUnsavedContent,
272 documentRevision}});
273 }
274
unsavedFilesUpdated(const QString & filePath,const QByteArray & contents,uint documentRevision)275 void BackendCommunicator::unsavedFilesUpdated(const QString &filePath,
276 const QByteArray &contents,
277 uint documentRevision)
278 {
279 const bool hasUnsavedContent = true;
280
281 // TODO: Send new only if changed
282 unsavedFilesUpdated({{filePath,
283 Utf8String::fromByteArray(contents),
284 hasUnsavedContent,
285 documentRevision}});
286 }
287
documentHasChanged(const QString & filePath,uint revision)288 static bool documentHasChanged(const QString &filePath, uint revision)
289 {
290 if (CppTools::CppEditorDocumentHandle *document = cppDocument(filePath))
291 return document->sendTracker().shouldSendRevision(revision);
292
293 return true;
294 }
295
documentsChangedWithRevisionCheck(const FileContainer & fileContainer)296 void BackendCommunicator::documentsChangedWithRevisionCheck(const FileContainer &fileContainer)
297 {
298 if (documentHasChanged(fileContainer.filePath, fileContainer.documentRevision)) {
299 documentsChanged({fileContainer});
300 setLastSentDocumentRevision(fileContainer.filePath,
301 fileContainer.documentRevision);
302 }
303 }
304
requestAnnotations(const FileContainer & fileContainer)305 void BackendCommunicator::requestAnnotations(const FileContainer &fileContainer)
306 {
307 const RequestAnnotationsMessage message(fileContainer);
308 m_sender->requestAnnotations(message);
309 }
310
requestReferences(const FileContainer & fileContainer,quint32 line,quint32 column,const CppTools::SemanticInfo::LocalUseMap & localUses)311 QFuture<CppTools::CursorInfo> BackendCommunicator::requestReferences(
312 const FileContainer &fileContainer,
313 quint32 line,
314 quint32 column,
315 const CppTools::SemanticInfo::LocalUseMap &localUses)
316 {
317 const RequestReferencesMessage message(fileContainer, line, column);
318 m_sender->requestReferences(message);
319
320 return m_receiver.addExpectedReferencesMessage(message.ticketNumber, localUses);
321 }
322
requestLocalReferences(const FileContainer & fileContainer,quint32 line,quint32 column)323 QFuture<CppTools::CursorInfo> BackendCommunicator::requestLocalReferences(
324 const FileContainer &fileContainer,
325 quint32 line,
326 quint32 column)
327 {
328 const RequestReferencesMessage message(fileContainer, line, column, true);
329 m_sender->requestReferences(message);
330
331 return m_receiver.addExpectedReferencesMessage(message.ticketNumber);
332 }
333
requestToolTip(const FileContainer & fileContainer,quint32 line,quint32 column)334 QFuture<CppTools::ToolTipInfo> BackendCommunicator::requestToolTip(
335 const FileContainer &fileContainer, quint32 line, quint32 column)
336 {
337 const RequestToolTipMessage message(fileContainer, line, column);
338 m_sender->requestToolTip(message);
339
340 return m_receiver.addExpectedToolTipMessage(message.ticketNumber);
341 }
342
requestFollowSymbol(const FileContainer & curFileContainer,quint32 line,quint32 column)343 QFuture<CppTools::SymbolInfo> BackendCommunicator::requestFollowSymbol(
344 const FileContainer &curFileContainer,
345 quint32 line,
346 quint32 column)
347 {
348 const RequestFollowSymbolMessage message(curFileContainer, line, column);
349 m_sender->requestFollowSymbol(message);
350
351 return m_receiver.addExpectedRequestFollowSymbolMessage(message.ticketNumber);
352 }
353
documentsChangedWithRevisionCheck(Core::IDocument * document)354 void BackendCommunicator::documentsChangedWithRevisionCheck(Core::IDocument *document)
355 {
356 const auto textDocument = qobject_cast<TextDocument*>(document);
357 const auto filePath = textDocument->filePath().toString();
358
359 documentsChangedWithRevisionCheck(
360 FileContainer(filePath, {}, {}, textDocument->document()->revision()));
361 }
362
updateChangeContentStartPosition(const QString & filePath,int position)363 void BackendCommunicator::updateChangeContentStartPosition(const QString &filePath, int position)
364 {
365 if (CppTools::CppEditorDocumentHandle *document = cppDocument(filePath))
366 document->sendTracker().applyContentChange(position);
367 }
368
documentsChangedIfNotCurrentDocument(Core::IDocument * document)369 void BackendCommunicator::documentsChangedIfNotCurrentDocument(Core::IDocument *document)
370 {
371 QTC_ASSERT(document, return);
372 if (Core::EditorManager::currentDocument() != document)
373 documentsChanged(document);
374 }
375
documentsChanged(Core::IDocument * document)376 void BackendCommunicator::documentsChanged(Core::IDocument *document)
377 {
378 documentsChangedFromCppEditorDocument(document->filePath().toString());
379 }
380
unsavedFilesUpdated(Core::IDocument * document)381 void BackendCommunicator::unsavedFilesUpdated(Core::IDocument *document)
382 {
383 QTC_ASSERT(document, return);
384
385 unsavedFilesUpdatedFromCppEditorDocument(document->filePath().toString());
386 }
387
onConnectedToBackend()388 void BackendCommunicator::onConnectedToBackend()
389 {
390 m_backendStartTimeOut.stop();
391
392 ++m_connectedCount;
393 if (m_connectedCount > 1)
394 logRestartedDueToUnexpectedFinish();
395
396 m_receiver.reset();
397 m_sender.reset(new BackendSender(&m_connection));
398
399 initializeBackendWithCurrentData();
400 }
401
onEditorAboutToClose(Core::IEditor * editor)402 void BackendCommunicator::onEditorAboutToClose(Core::IEditor *editor)
403 {
404 if (auto *textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor))
405 m_receiver.deleteProcessorsOfEditorWidget(textEditor->editorWidget());
406 }
407
setupDummySender()408 void BackendCommunicator::setupDummySender()
409 {
410 m_sender.reset(new DummyBackendSender);
411 }
412
logExecutableDoesNotExist()413 void BackendCommunicator::logExecutableDoesNotExist()
414 {
415 const QString msg
416 = tr("Clang Code Model: Error: "
417 "The clangbackend executable \"%1\" does not exist.")
418 .arg(QDir::toNativeSeparators(backendProcessPath()));
419
420 logError(msg);
421 }
422
logStartTimeOut()423 void BackendCommunicator::logStartTimeOut()
424 {
425 const QString msg
426 = tr("Clang Code Model: Error: "
427 "The clangbackend executable \"%1\" could not be started (timeout after %2ms).")
428 .arg(QDir::toNativeSeparators(backendProcessPath()))
429 .arg(backEndStartTimeOutInMs);
430
431 logError(msg);
432 }
433
logRestartedDueToUnexpectedFinish()434 void BackendCommunicator::logRestartedDueToUnexpectedFinish()
435 {
436 const QString msg
437 = tr("Clang Code Model: Error: "
438 "The clangbackend process has finished unexpectedly and was restarted.");
439
440 logError(msg);
441 }
442
logError(const QString & text)443 void BackendCommunicator::logError(const QString &text)
444 {
445 const QString textWithTimestamp = QDateTime::currentDateTime().toString(Qt::ISODate)
446 + ' ' + text;
447 Core::MessageManager::writeFlashing(textWithTimestamp);
448 qWarning("%s", qPrintable(textWithTimestamp));
449 }
450
initializeBackendWithCurrentData()451 void BackendCommunicator::initializeBackendWithCurrentData()
452 {
453 unsavedFilesUpdatedForUiHeaders();
454 restoreCppEditorDocuments();
455 documentVisibilityChanged();
456 }
457
documentsOpened(const FileContainers & fileContainers)458 void BackendCommunicator::documentsOpened(const FileContainers &fileContainers)
459 {
460 Utf8String currentDocument;
461 Utf8StringVector visibleDocuments;
462 if (!m_postponeBackendJobs) {
463 currentDocument = currentCppEditorDocumentFilePath();
464 visibleDocuments = visibleCppEditorDocumentsFilePaths();
465 }
466
467 const DocumentsOpenedMessage message(fileContainers, currentDocument, visibleDocuments);
468 m_sender->documentsOpened(message);
469 }
470
documentsChanged(const FileContainers & fileContainers)471 void BackendCommunicator::documentsChanged(const FileContainers &fileContainers)
472 {
473 const DocumentsChangedMessage message(fileContainers);
474 m_sender->documentsChanged(message);
475 }
476
documentsClosed(const FileContainers & fileContainers)477 void BackendCommunicator::documentsClosed(const FileContainers &fileContainers)
478 {
479 const DocumentsClosedMessage message(fileContainers);
480 m_sender->documentsClosed(message);
481 documentVisibilityChanged(); // QTCREATORBUG-25193
482 }
483
unsavedFilesUpdated(const FileContainers & fileContainers)484 void BackendCommunicator::unsavedFilesUpdated(const FileContainers &fileContainers)
485 {
486 const UnsavedFilesUpdatedMessage message(fileContainers);
487 m_sender->unsavedFilesUpdated(message);
488 }
489
unsavedFilesRemoved(const FileContainers & fileContainers)490 void BackendCommunicator::unsavedFilesRemoved(const FileContainers &fileContainers)
491 {
492 const UnsavedFilesRemovedMessage message(fileContainers);
493 m_sender->unsavedFilesRemoved(message);
494 }
495
requestCompletions(ClangCompletionAssistProcessor * assistProcessor,const QString & filePath,quint32 line,quint32 column,qint32 funcNameStartLine,qint32 funcNameStartColumn)496 void BackendCommunicator::requestCompletions(ClangCompletionAssistProcessor *assistProcessor,
497 const QString &filePath,
498 quint32 line,
499 quint32 column,
500 qint32 funcNameStartLine,
501 qint32 funcNameStartColumn)
502 {
503 const RequestCompletionsMessage message(filePath,
504 line,
505 column,
506 funcNameStartLine,
507 funcNameStartColumn);
508 m_sender->requestCompletions(message);
509 m_receiver.addExpectedCompletionsMessage(message.ticketNumber, assistProcessor);
510 }
511
cancelCompletions(TextEditor::IAssistProcessor * processor)512 void BackendCommunicator::cancelCompletions(TextEditor::IAssistProcessor *processor)
513 {
514 m_receiver.cancelProcessor(processor);
515 }
516
517 } // namespace Internal
518 } // namespace ClangCodeModel
519