/**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** 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 ** 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. ** ** 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. ** ****************************************************************************/ #include "clanghoverhandler.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(hoverLog, "qtc.clangcodemodel.hover", QtWarningMsg); using namespace TextEditor; namespace ClangCodeModel { namespace Internal { static CppTools::BaseEditorDocumentProcessor *editorDocumentProcessor(TextEditorWidget *editorWidget) { const QString filePath = editorWidget->textDocument()->filePath().toString(); auto cppModelManager = CppTools::CppModelManager::instance(); CppTools::CppEditorDocumentHandle *editorHandle = cppModelManager->cppEditorDocument(filePath); if (editorHandle) return editorHandle->processor(); return nullptr; } static bool editorDocumentProcessorHasDiagnosticAt(TextEditorWidget *editorWidget, int pos) { if (CppTools::BaseEditorDocumentProcessor *processor = editorDocumentProcessor(editorWidget)) { int line, column; if (Utils::Text::convertPosition(editorWidget->document(), pos, &line, &column)) return processor->hasDiagnosticsAt(line, column); } return false; } static void processWithEditorDocumentProcessor(TextEditorWidget *editorWidget, const QPoint &point, int position, const Core::HelpItem &helpItem) { if (CppTools::BaseEditorDocumentProcessor *processor = editorDocumentProcessor(editorWidget)) { int line, column; if (Utils::Text::convertPosition(editorWidget->document(), position, &line, &column)) { auto layout = new QVBoxLayout; layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(2); processor->addDiagnosticToolTipToLayout(line, column, layout); Utils::ToolTip::show(point, layout, editorWidget, qVariantFromValue(helpItem)); } } } static QFuture editorDocumentHandlesToolTipInfo( TextEditorWidget *editorWidget, int pos) { const QByteArray textCodecName = editorWidget->textDocument()->codec()->name(); if (CppTools::BaseEditorDocumentProcessor *processor = editorDocumentProcessor(editorWidget)) { int line, column; if (Utils::Text::convertPosition(editorWidget->document(), pos, &line, &column)) return processor->toolTipInfo(textCodecName, line, column + 1); } return QFuture(); } ClangHoverHandler::~ClangHoverHandler() { abort(); } static int skipChars(QTextCursor *tc, QTextCursor::MoveOperation op, int offset, std::function skip) { const QTextDocument *doc = tc->document(); QChar ch = doc->characterAt(tc->position() + offset); if (ch.isNull()) return 0; int count = 0; while (skip(ch)) { if (tc->movePosition(op)) ++count; else break; ch = doc->characterAt(tc->position() + offset); } return count; } static int skipCharsForward(QTextCursor *tc, std::function skip) { return skipChars(tc, QTextCursor::NextCharacter, 0, skip); } static int skipCharsBackward(QTextCursor *tc, std::function skip) { return skipChars(tc, QTextCursor::PreviousCharacter, -1, skip); } static QStringList fallbackWords(QTextDocument *document, int pos) { const auto isSpace = [](const QChar &c) { return c.isSpace(); }; const auto isColon = [](const QChar &c) { return c == ':'; }; const auto isValidIdentifierChar = [document](const QTextCursor &tc) { return CppTools::isValidIdentifierChar(document->characterAt(tc.position())); }; // move to the end QTextCursor endCursor(document); endCursor.setPosition(pos); do { CppTools::moveCursorToEndOfIdentifier(&endCursor); // possibly skip :: QTextCursor temp(endCursor); skipCharsForward(&temp, isSpace); const int colons = skipCharsForward(&temp, isColon); skipCharsForward(&temp, isSpace); if (colons == 2 && isValidIdentifierChar(temp)) endCursor = temp; } while (isValidIdentifierChar(endCursor)); QStringList results; QTextCursor startCursor(endCursor); do { CppTools::moveCursorToStartOfIdentifier(&startCursor); if (startCursor.position() == endCursor.position()) break; QTextCursor temp(endCursor); temp.setPosition(startCursor.position(), QTextCursor::KeepAnchor); results.append(temp.selectedText().remove(QRegularExpression("\\s"))); // possibly skip :: temp = startCursor; skipCharsBackward(&temp, isSpace); const int colons = skipCharsBackward(&temp, isColon); skipCharsBackward(&temp, isSpace); if (colons == 2 && CppTools::isValidIdentifierChar(document->characterAt(temp.position() - 1))) { startCursor = temp; } } while (!isValidIdentifierChar(startCursor)); return results; } void ClangHoverHandler::identifyMatch(TextEditorWidget *editorWidget, int pos, BaseHoverHandler::ReportPriority report) { // Reset m_futureWatcher.reset(); m_cursorPosition = -1; // Check for diagnostics (sync) if (!isContextHelpRequest() && editorDocumentProcessorHasDiagnosticAt(editorWidget, pos)) { qCDebug(hoverLog) << "Checking for diagnostic at" << pos; setPriority(Priority_Diagnostic); m_cursorPosition = pos; } // Check for tooltips (async) QFuture future = editorDocumentHandlesToolTipInfo(editorWidget, pos); if (QTC_GUARD(future.isRunning())) { qCDebug(hoverLog) << "Requesting tooltip info at" << pos; m_reportPriority = report; m_futureWatcher.reset(new QFutureWatcher()); const QStringList fallback = fallbackWords(editorWidget->document(), pos); QObject::connect(m_futureWatcher.data(), &QFutureWatcherBase::finished, [this, fallback]() { if (m_futureWatcher->isCanceled()) { m_reportPriority(Priority_None); } else { CppTools::ToolTipInfo info = m_futureWatcher->result(); qCDebug(hoverLog) << "Appending word-based fallback lookup" << fallback; info.qDocIdCandidates += fallback; processToolTipInfo(info); } }); m_futureWatcher->setFuture(future); return; } report(priority()); // Ops, something went wrong. } void ClangHoverHandler::abort() { if (m_futureWatcher) { m_futureWatcher->cancel(); m_futureWatcher.reset(); } } #define RETURN_TEXT_FOR_CASE(enumValue) case Core::HelpItem::enumValue: return #enumValue static const char *helpItemCategoryAsString(Core::HelpItem::Category category) { switch (category) { RETURN_TEXT_FOR_CASE(Unknown); RETURN_TEXT_FOR_CASE(ClassOrNamespace); RETURN_TEXT_FOR_CASE(Enum); RETURN_TEXT_FOR_CASE(Typedef); RETURN_TEXT_FOR_CASE(Macro); RETURN_TEXT_FOR_CASE(Brief); RETURN_TEXT_FOR_CASE(Function); RETURN_TEXT_FOR_CASE(QmlComponent); RETURN_TEXT_FOR_CASE(QmlProperty); RETURN_TEXT_FOR_CASE(QMakeVariableOfFunction); } return "UnhandledHelpItemCategory"; } #undef RETURN_TEXT_FOR_CASE void ClangHoverHandler::processToolTipInfo(const CppTools::ToolTipInfo &info) { qCDebug(hoverLog) << "Processing tooltip info" << info.text; QString text = info.text; if (!info.briefComment.isEmpty()) text.append("\n\n" + info.briefComment); qCDebug(hoverLog) << "Querying help manager with" << info.qDocIdCandidates << info.qDocMark << helpItemCategoryAsString(info.qDocCategory); setLastHelpItemIdentified({info.qDocIdCandidates, info.qDocMark, info.qDocCategory}); if (!info.sizeInBytes.isEmpty()) text.append("\n\n" + tr("%1 bytes").arg(info.sizeInBytes)); setToolTip(text); m_reportPriority(priority()); } void ClangHoverHandler::operateTooltip(TextEditor::TextEditorWidget *editorWidget, const QPoint &point) { if (priority() == Priority_Diagnostic) { processWithEditorDocumentProcessor(editorWidget, point, m_cursorPosition, lastHelpItemIdentified()); return; } // Priority_Tooltip / Priority_Help BaseHoverHandler::operateTooltip(editorWidget, point); } } // namespace Internal } // namespace ClangCodeModel