1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 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 "clangeditordocumentprocessor.h"
27
28 #include "clangbackendcommunicator.h"
29 #include "clangdiagnostictooltipwidget.h"
30 #include "clangfixitoperation.h"
31 #include "clangfixitoperationsextractor.h"
32 #include "clangmodelmanagersupport.h"
33 #include "clanghighlightingresultreporter.h"
34 #include "clangutils.h"
35
36 #include <diagnosticcontainer.h>
37 #include <sourcelocationcontainer.h>
38
39 #include <cpptools/builtincursorinfo.h>
40 #include <cpptools/clangdiagnosticconfigsmodel.h>
41 #include <cpptools/compileroptionsbuilder.h>
42 #include <cpptools/cppcodemodelsettings.h>
43 #include <cpptools/cppmodelmanager.h>
44 #include <cpptools/cpptoolsbridge.h>
45 #include <cpptools/cpptoolsreuse.h>
46 #include <cpptools/cppworkingcopy.h>
47 #include <cpptools/editordocumenthandle.h>
48
49 #include <texteditor/fontsettings.h>
50 #include <texteditor/texteditor.h>
51 #include <texteditor/texteditorconstants.h>
52 #include <texteditor/texteditorsettings.h>
53
54 #include <cplusplus/CppDocument.h>
55
56 #include <utils/algorithm.h>
57 #include <utils/textutils.h>
58 #include <utils/qtcassert.h>
59 #include <utils/runextensions.h>
60
61 #include <QTextBlock>
62 #include <QVBoxLayout>
63 #include <QWidget>
64
65 namespace ClangCodeModel {
66 namespace Internal {
67
ClangEditorDocumentProcessor(BackendCommunicator & communicator,TextEditor::TextDocument * document)68 ClangEditorDocumentProcessor::ClangEditorDocumentProcessor(
69 BackendCommunicator &communicator,
70 TextEditor::TextDocument *document)
71 : BaseEditorDocumentProcessor(document->document(), document->filePath().toString())
72 , m_document(*document)
73 , m_diagnosticManager(document)
74 , m_communicator(communicator)
75 , m_parser(new ClangEditorDocumentParser(document->filePath().toString()))
76 , m_parserRevision(0)
77 , m_semanticHighlighter(document)
78 , m_builtinProcessor(document, /*enableSemanticHighlighter=*/ false)
79 {
80 m_updateBackendDocumentTimer.setSingleShot(true);
81 m_updateBackendDocumentTimer.setInterval(350);
82 connect(&m_updateBackendDocumentTimer, &QTimer::timeout,
83 this, &ClangEditorDocumentProcessor::updateBackendDocumentIfProjectPartExists);
84
85 connect(m_parser.data(), &ClangEditorDocumentParser::projectPartInfoUpdated,
86 this, &BaseEditorDocumentProcessor::projectPartInfoUpdated);
87
88 // Forwarding the semantic info from the builtin processor enables us to provide all
89 // editor (widget) related features that are not yet implemented by the clang plugin.
90 connect(&m_builtinProcessor, &CppTools::BuiltinEditorDocumentProcessor::cppDocumentUpdated,
91 this, &ClangEditorDocumentProcessor::cppDocumentUpdated);
92 connect(&m_builtinProcessor, &CppTools::BuiltinEditorDocumentProcessor::semanticInfoUpdated,
93 this, &ClangEditorDocumentProcessor::semanticInfoUpdated);
94
95 m_parserSynchronizer.setCancelOnWait(true);
96 }
97
~ClangEditorDocumentProcessor()98 ClangEditorDocumentProcessor::~ClangEditorDocumentProcessor()
99 {
100 m_updateBackendDocumentTimer.stop();
101
102 if (m_projectPart)
103 closeBackendDocument();
104 }
105
runImpl(const CppTools::BaseEditorDocumentParser::UpdateParams & updateParams)106 void ClangEditorDocumentProcessor::runImpl(
107 const CppTools::BaseEditorDocumentParser::UpdateParams &updateParams)
108 {
109 m_updateBackendDocumentTimer.start();
110
111 // Run clang parser
112 disconnect(&m_parserWatcher, &QFutureWatcher<void>::finished,
113 this, &ClangEditorDocumentProcessor::onParserFinished);
114 m_parserWatcher.cancel();
115 m_parserWatcher.setFuture(QFuture<void>());
116
117 m_parserRevision = revision();
118 connect(&m_parserWatcher, &QFutureWatcher<void>::finished,
119 this, &ClangEditorDocumentProcessor::onParserFinished);
120 const QFuture<void> future = ::Utils::runAsync(&runParser, parser(), updateParams);
121 m_parserWatcher.setFuture(future);
122 m_parserSynchronizer.addFuture(future);
123
124 // Run builtin processor
125 m_builtinProcessor.runImpl(updateParams);
126 }
127
recalculateSemanticInfoDetached(bool force)128 void ClangEditorDocumentProcessor::recalculateSemanticInfoDetached(bool force)
129 {
130 m_builtinProcessor.recalculateSemanticInfoDetached(force);
131 }
132
semanticRehighlight()133 void ClangEditorDocumentProcessor::semanticRehighlight()
134 {
135 const auto matchesEditor = [this](const Core::IEditor *editor) {
136 return editor->document()->filePath() == m_document.filePath();
137 };
138 if (!Utils::contains(Core::EditorManager::visibleEditors(), matchesEditor))
139 return;
140 if (ClangModelManagerSupport::instance()->clientForFile(m_document.filePath()))
141 return;
142
143 m_semanticHighlighter.updateFormatMapFromFontSettings();
144 if (m_projectPart)
145 requestAnnotationsFromBackend();
146 }
147
recalculateSemanticInfo()148 CppTools::SemanticInfo ClangEditorDocumentProcessor::recalculateSemanticInfo()
149 {
150 return m_builtinProcessor.recalculateSemanticInfo();
151 }
152
parser()153 CppTools::BaseEditorDocumentParser::Ptr ClangEditorDocumentProcessor::parser()
154 {
155 return m_parser;
156 }
157
snapshot()158 CPlusPlus::Snapshot ClangEditorDocumentProcessor::snapshot()
159 {
160 return m_builtinProcessor.snapshot();
161 }
162
isParserRunning() const163 bool ClangEditorDocumentProcessor::isParserRunning() const
164 {
165 return m_parserWatcher.isRunning();
166 }
167
hasProjectPart() const168 bool ClangEditorDocumentProcessor::hasProjectPart() const
169 {
170 return !m_projectPart.isNull();
171 }
172
projectPart() const173 CppTools::ProjectPart::Ptr ClangEditorDocumentProcessor::projectPart() const
174 {
175 return m_projectPart;
176 }
177
clearProjectPart()178 void ClangEditorDocumentProcessor::clearProjectPart()
179 {
180 m_projectPart.clear();
181 }
182
diagnosticConfigId() const183 ::Utils::Id ClangEditorDocumentProcessor::diagnosticConfigId() const
184 {
185 return m_diagnosticConfigId;
186 }
187
updateCodeWarnings(const QVector<ClangBackEnd::DiagnosticContainer> & diagnostics,const ClangBackEnd::DiagnosticContainer & firstHeaderErrorDiagnostic,uint documentRevision)188 void ClangEditorDocumentProcessor::updateCodeWarnings(
189 const QVector<ClangBackEnd::DiagnosticContainer> &diagnostics,
190 const ClangBackEnd::DiagnosticContainer &firstHeaderErrorDiagnostic,
191 uint documentRevision)
192 {
193 if (ClangModelManagerSupport::instance()->clientForFile(m_document.filePath()))
194 return;
195
196 if (documentRevision == revision()) {
197 if (m_invalidationState == InvalidationState::Scheduled)
198 m_invalidationState = InvalidationState::Canceled;
199 m_diagnosticManager.processNewDiagnostics(diagnostics, m_isProjectFile);
200 const auto codeWarnings = m_diagnosticManager.takeExtraSelections();
201 const auto fixitAvailableMarkers = m_diagnosticManager.takeFixItAvailableMarkers();
202 const auto creator = creatorForHeaderErrorDiagnosticWidget(firstHeaderErrorDiagnostic);
203
204 emit codeWarningsUpdated(revision(),
205 codeWarnings,
206 creator,
207 fixitAvailableMarkers);
208 }
209 }
210 namespace {
211
212 TextEditor::BlockRange
toTextEditorBlock(QTextDocument * textDocument,const ClangBackEnd::SourceRangeContainer & sourceRangeContainer)213 toTextEditorBlock(QTextDocument *textDocument,
214 const ClangBackEnd::SourceRangeContainer &sourceRangeContainer)
215 {
216 return {::Utils::Text::positionInText(textDocument,
217 sourceRangeContainer.start.line,
218 sourceRangeContainer.start.column),
219 ::Utils::Text::positionInText(textDocument,
220 sourceRangeContainer.end.line,
221 sourceRangeContainer.end.column)};
222 }
223
224 QList<TextEditor::BlockRange>
toTextEditorBlocks(QTextDocument * textDocument,const QVector<ClangBackEnd::SourceRangeContainer> & ifdefedOutRanges)225 toTextEditorBlocks(QTextDocument *textDocument,
226 const QVector<ClangBackEnd::SourceRangeContainer> &ifdefedOutRanges)
227 {
228 QList<TextEditor::BlockRange> blockRanges;
229 blockRanges.reserve(ifdefedOutRanges.size());
230
231 for (const auto &range : ifdefedOutRanges)
232 blockRanges.append(toTextEditorBlock(textDocument, range));
233
234 return blockRanges;
235 }
236 }
237
238 const QVector<ClangBackEnd::TokenInfoContainer>
tokenInfos() const239 &ClangEditorDocumentProcessor::tokenInfos() const
240 {
241 return m_tokenInfos;
242 }
243
clearTaskHubIssues()244 void ClangEditorDocumentProcessor::clearTaskHubIssues()
245 {
246 ClangDiagnosticManager::clearTaskHubIssues();
247 }
248
generateTaskHubIssues()249 void ClangEditorDocumentProcessor::generateTaskHubIssues()
250 {
251 m_diagnosticManager.generateTaskHubIssues();
252 }
253
clearTextMarks(const Utils::FilePath & filePath)254 void ClangEditorDocumentProcessor::clearTextMarks(const Utils::FilePath &filePath)
255 {
256 if (ClangEditorDocumentProcessor * const proc = get(filePath.toString())) {
257 proc->m_diagnosticManager.cleanMarks();
258 emit proc->codeWarningsUpdated(proc->revision(), {}, {}, {});
259 }
260 }
261
updateHighlighting(const QVector<ClangBackEnd::TokenInfoContainer> & tokenInfos,const QVector<ClangBackEnd::SourceRangeContainer> & skippedPreprocessorRanges,uint documentRevision)262 void ClangEditorDocumentProcessor::updateHighlighting(
263 const QVector<ClangBackEnd::TokenInfoContainer> &tokenInfos,
264 const QVector<ClangBackEnd::SourceRangeContainer> &skippedPreprocessorRanges,
265 uint documentRevision)
266 {
267 if (ClangModelManagerSupport::instance()->clientForFile(m_document.filePath()))
268 return;
269 if (documentRevision == revision()) {
270 const auto skippedPreprocessorBlocks = toTextEditorBlocks(textDocument(), skippedPreprocessorRanges);
271 emit ifdefedOutBlocksUpdated(documentRevision, skippedPreprocessorBlocks);
272
273 m_semanticHighlighter.setHighlightingRunner(
274 [tokenInfos]() {
275 auto *reporter = new HighlightingResultReporter(tokenInfos);
276 return reporter->start();
277 });
278 m_semanticHighlighter.run();
279 }
280 }
281
updateTokenInfos(const QVector<ClangBackEnd::TokenInfoContainer> & tokenInfos,uint documentRevision)282 void ClangEditorDocumentProcessor::updateTokenInfos(
283 const QVector<ClangBackEnd::TokenInfoContainer> &tokenInfos,
284 uint documentRevision)
285 {
286 if (documentRevision != revision())
287 return;
288 m_tokenInfos = tokenInfos;
289 emit tokenInfosUpdated();
290 }
291
currentLine(const TextEditor::AssistInterface & assistInterface)292 static int currentLine(const TextEditor::AssistInterface &assistInterface)
293 {
294 int line, column;
295 ::Utils::Text::convertPosition(assistInterface.textDocument(), assistInterface.position(),
296 &line, &column);
297 return line;
298 }
299
extraRefactoringOperations(const TextEditor::AssistInterface & assistInterface)300 TextEditor::QuickFixOperations ClangEditorDocumentProcessor::extraRefactoringOperations(
301 const TextEditor::AssistInterface &assistInterface)
302 {
303 ClangFixItOperationsExtractor extractor(m_diagnosticManager.diagnosticsWithFixIts());
304
305 return extractor.extract(assistInterface.filePath().toString(), currentLine(assistInterface));
306 }
307
editorDocumentTimerRestarted()308 void ClangEditorDocumentProcessor::editorDocumentTimerRestarted()
309 {
310 m_updateBackendDocumentTimer.stop(); // Wait for the next call to run().
311 m_invalidationState = InvalidationState::Scheduled;
312 }
313
invalidateDiagnostics()314 void ClangEditorDocumentProcessor::invalidateDiagnostics()
315 {
316 if (m_invalidationState != InvalidationState::Canceled)
317 m_diagnosticManager.invalidateDiagnostics();
318 m_invalidationState = InvalidationState::Off;
319 }
320
diagnosticTextMarksAt(uint line,uint column) const321 TextEditor::TextMarks ClangEditorDocumentProcessor::diagnosticTextMarksAt(uint line,
322 uint column) const
323 {
324 return m_diagnosticManager.diagnosticTextMarksAt(line, column);
325 }
326
setParserConfig(const CppTools::BaseEditorDocumentParser::Configuration & config)327 void ClangEditorDocumentProcessor::setParserConfig(
328 const CppTools::BaseEditorDocumentParser::Configuration &config)
329 {
330 m_parser->setConfiguration(config);
331 m_builtinProcessor.parser()->setConfiguration(config);
332 }
333
isCursorOnIdentifier(const QTextCursor & textCursor)334 static bool isCursorOnIdentifier(const QTextCursor &textCursor)
335 {
336 QTextDocument *document = textCursor.document();
337 return CppTools::isValidIdentifierChar(document->characterAt(textCursor.position()));
338 }
339
defaultCursorInfoFuture()340 static QFuture<CppTools::CursorInfo> defaultCursorInfoFuture()
341 {
342 QFutureInterface<CppTools::CursorInfo> futureInterface;
343 futureInterface.reportResult(CppTools::CursorInfo());
344 futureInterface.reportFinished();
345
346 return futureInterface.future();
347 }
348
convertPosition(const QTextCursor & textCursor,int * line,int * column)349 static bool convertPosition(const QTextCursor &textCursor, int *line, int *column)
350 {
351 const bool converted = ::Utils::Text::convertPosition(textCursor.document(),
352 textCursor.position(),
353 line,
354 column);
355 QTC_CHECK(converted);
356 return converted;
357 }
358
359 QFuture<CppTools::CursorInfo>
cursorInfo(const CppTools::CursorInfoParams & params)360 ClangEditorDocumentProcessor::cursorInfo(const CppTools::CursorInfoParams ¶ms)
361 {
362 int line, column;
363 convertPosition(params.textCursor, &line, &column);
364
365 if (!isCursorOnIdentifier(params.textCursor))
366 return defaultCursorInfoFuture();
367
368 column = clangColumn(params.textCursor.document()->findBlockByNumber(line - 1), column);
369 const CppTools::SemanticInfo::LocalUseMap localUses
370 = CppTools::BuiltinCursorInfo::findLocalUses(params.semanticInfo.doc, line, column);
371
372 return m_communicator.requestReferences(simpleFileContainer(),
373 static_cast<quint32>(line),
374 static_cast<quint32>(column),
375 localUses);
376 }
377
requestLocalReferences(const QTextCursor & cursor)378 QFuture<CppTools::CursorInfo> ClangEditorDocumentProcessor::requestLocalReferences(
379 const QTextCursor &cursor)
380 {
381 int line, column;
382 convertPosition(cursor, &line, &column);
383 ++column; // for 1-based columns
384
385 // TODO: check that by highlighting items
386 if (!isCursorOnIdentifier(cursor))
387 return defaultCursorInfoFuture();
388
389 return m_communicator.requestLocalReferences(simpleFileContainer(),
390 static_cast<quint32>(line),
391 static_cast<quint32>(column));
392 }
393
394 QFuture<CppTools::SymbolInfo>
requestFollowSymbol(int line,int column)395 ClangEditorDocumentProcessor::requestFollowSymbol(int line, int column)
396 {
397 return m_communicator.requestFollowSymbol(simpleFileContainer(),
398 static_cast<quint32>(line),
399 static_cast<quint32>(column));
400 }
401
toolTipInfo(const QByteArray & codecName,int line,int column)402 QFuture<CppTools::ToolTipInfo> ClangEditorDocumentProcessor::toolTipInfo(const QByteArray &codecName,
403 int line,
404 int column)
405 {
406 return m_communicator.requestToolTip(simpleFileContainer(codecName),
407 static_cast<quint32>(line),
408 static_cast<quint32>(column));
409 }
410
clearDiagnosticsWithFixIts()411 void ClangEditorDocumentProcessor::clearDiagnosticsWithFixIts()
412 {
413 m_diagnosticManager.clearDiagnosticsWithFixIts();
414 }
415
get(const QString & filePath)416 ClangEditorDocumentProcessor *ClangEditorDocumentProcessor::get(const QString &filePath)
417 {
418 auto *processor = CppTools::CppToolsBridge::baseEditorDocumentProcessor(filePath);
419
420 return qobject_cast<ClangEditorDocumentProcessor*>(processor);
421 }
422
isProjectPartLoadedOrIsFallback(CppTools::ProjectPart::Ptr projectPart)423 static bool isProjectPartLoadedOrIsFallback(CppTools::ProjectPart::Ptr projectPart)
424 {
425 return projectPart
426 && (projectPart->id().isEmpty() || isProjectPartLoaded(projectPart));
427 }
428
updateBackendProjectPartAndDocument()429 void ClangEditorDocumentProcessor::updateBackendProjectPartAndDocument()
430 {
431 const CppTools::ProjectPart::Ptr projectPart = m_parser->projectPartInfo().projectPart;
432
433 if (isProjectPartLoadedOrIsFallback(projectPart)) {
434 updateBackendDocument(*projectPart.data());
435
436 m_projectPart = projectPart;
437 m_isProjectFile = m_parser->projectPartInfo().hints
438 & CppTools::ProjectPartInfo::IsFromProjectMatch;
439 }
440 }
441
onParserFinished()442 void ClangEditorDocumentProcessor::onParserFinished()
443 {
444 if (revision() != m_parserRevision)
445 return;
446
447 updateBackendProjectPartAndDocument();
448 }
449
updateBackendDocument(CppTools::ProjectPart & projectPart)450 void ClangEditorDocumentProcessor::updateBackendDocument(CppTools::ProjectPart &projectPart)
451 {
452 // On registration we send the document content immediately as an unsaved
453 // file, because
454 // (1) a refactoring action might have opened and already modified
455 // this document.
456 // (2) it prevents an extra preamble generation on first user
457 // modification of the document in case the line endings on disk
458 // differ from the ones returned by textDocument()->toPlainText(),
459 // like on Windows.
460
461 if (m_projectPart) {
462 if (projectPart.id() == m_projectPart->id())
463 return;
464 }
465
466 const auto clangOptions = createClangOptions(projectPart, filePath());
467 m_diagnosticConfigId = clangOptions.first;
468
469 m_communicator.documentsOpened(
470 {fileContainerWithOptionsAndDocumentContent(clangOptions.second, projectPart.headerPaths)});
471 setLastSentDocumentRevision(filePath(), revision());
472 }
473
closeBackendDocument()474 void ClangEditorDocumentProcessor::closeBackendDocument()
475 {
476 QTC_ASSERT(m_projectPart, return);
477 m_communicator.documentsClosed({ClangBackEnd::FileContainer(filePath(), m_projectPart->id())});
478 }
479
updateBackendDocumentIfProjectPartExists()480 void ClangEditorDocumentProcessor::updateBackendDocumentIfProjectPartExists()
481 {
482 if (m_projectPart) {
483 const ClangBackEnd::FileContainer fileContainer = fileContainerWithDocumentContent();
484 m_communicator.documentsChangedWithRevisionCheck(fileContainer);
485 }
486 }
487
requestAnnotationsFromBackend()488 void ClangEditorDocumentProcessor::requestAnnotationsFromBackend()
489 {
490 const auto fileContainer = fileContainerWithDocumentContent();
491 m_communicator.requestAnnotations(fileContainer);
492 }
493
494 CppTools::BaseEditorDocumentProcessor::HeaderErrorDiagnosticWidgetCreator
creatorForHeaderErrorDiagnosticWidget(const ClangBackEnd::DiagnosticContainer & firstHeaderErrorDiagnostic)495 ClangEditorDocumentProcessor::creatorForHeaderErrorDiagnosticWidget(
496 const ClangBackEnd::DiagnosticContainer &firstHeaderErrorDiagnostic)
497 {
498 if (firstHeaderErrorDiagnostic.text.isEmpty())
499 return CppTools::BaseEditorDocumentProcessor::HeaderErrorDiagnosticWidgetCreator();
500
501 return [firstHeaderErrorDiagnostic]() {
502 auto vbox = new QVBoxLayout;
503 vbox->setContentsMargins(10, 0, 0, 2);
504 vbox->setSpacing(2);
505
506 vbox->addWidget(ClangDiagnosticWidget::createWidget({firstHeaderErrorDiagnostic},
507 ClangDiagnosticWidget::InfoBar, {}));
508
509 auto widget = new QWidget;
510 widget->setLayout(vbox);
511
512 return widget;
513 };
514 }
515
simpleFileContainer(const QByteArray & codecName) const516 ClangBackEnd::FileContainer ClangEditorDocumentProcessor::simpleFileContainer(
517 const QByteArray &codecName) const
518 {
519 return ClangBackEnd::FileContainer(filePath(),
520 Utf8String(),
521 false,
522 revision(),
523 Utf8String::fromByteArray(codecName));
524 }
525
fileContainerWithOptionsAndDocumentContent(const QStringList & compilationArguments,const ProjectExplorer::HeaderPaths headerPaths) const526 ClangBackEnd::FileContainer ClangEditorDocumentProcessor::fileContainerWithOptionsAndDocumentContent(
527 const QStringList &compilationArguments, const ProjectExplorer::HeaderPaths headerPaths) const
528 {
529 auto theHeaderPaths
530 = ::Utils::transform<QVector>(headerPaths, [](const ProjectExplorer::HeaderPath path) {
531 return Utf8String(QDir::toNativeSeparators(path.path));
532 });
533 theHeaderPaths << QDir::toNativeSeparators(
534 ClangModelManagerSupport::instance()->dummyUiHeaderOnDiskDirPath());
535
536 return ClangBackEnd::FileContainer(filePath(),
537 Utf8StringVector(compilationArguments),
538 theHeaderPaths,
539 textDocument()->toPlainText(),
540 true,
541 revision());
542 }
543
544 ClangBackEnd::FileContainer
fileContainerWithDocumentContent() const545 ClangEditorDocumentProcessor::fileContainerWithDocumentContent() const
546 {
547 return ClangBackEnd::FileContainer(filePath(),
548 textDocument()->toPlainText(),
549 true,
550 revision());
551 }
552
553 } // namespace Internal
554 } // namespace ClangCodeModel
555