/**************************************************************************** ** ** Copyright (C) 2016 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 "clangdiagnostictooltipwidget.h" #include "clangfixitoperation.h" #include #include #include #include #include #include #include #include using namespace ClangCodeModel; using Internal::ClangDiagnosticWidget; namespace { const char LINK_ACTION_GOTO_LOCATION[] = "#gotoLocation"; const char LINK_ACTION_APPLY_FIX[] = "#applyFix"; QString fileNamePrefix(const QString &mainFilePath, const ClangBackEnd::SourceLocationContainer &location) { const QString filePath = location.filePath().toString(); if (filePath != mainFilePath) return QFileInfo(filePath).fileName() + QLatin1Char(':'); return QString(); } QString locationToString(const ClangBackEnd::SourceLocationContainer &location) { return QString::number(location.line()) + QStringLiteral(":") + QString::number(location.column()); } void openEditorAt(const ClangBackEnd::DiagnosticContainer &diagnostic) { const ClangBackEnd::SourceLocationContainer location = diagnostic.location(); Core::EditorManager::openEditorAt(location.filePath().toString(), int(location.line()), int(location.column() - 1)); } void applyFixit(const ClangBackEnd::DiagnosticContainer &diagnostic) { ClangCodeModel::ClangFixItOperation operation(Utf8String(), diagnostic.fixIts()); operation.perform(); } class WidgetFromDiagnostics { public: struct DisplayHints { bool showCategoryAndEnableOption; bool showFileNameInMainDiagnostic; bool enableClickableFixits; bool limitWidth; bool hideTooltipAfterLinkActivation; }; static QWidget *create(const QVector &diagnostics, const DisplayHints &displayHints) { WidgetFromDiagnostics converter(displayHints); return converter.createWidget(diagnostics); } private: enum class IndentMode { Indent, DoNotIndent }; WidgetFromDiagnostics(const DisplayHints &displayHints) : m_displayHints(displayHints) { } QWidget *createWidget(const QVector &diagnostics) { const QString text = htmlText(diagnostics); auto *label = new QLabel; label->setTextFormat(Qt::RichText); label->setText(text); label->setTextInteractionFlags(Qt::TextBrowserInteraction); if (m_displayHints.limitWidth) { const int limit = widthLimit(); // Using "setWordWrap(true)" alone will wrap the text already for small // widths, so do not require word wrapping until we hit limits. if (label->sizeHint().width() > limit) { label->setMaximumWidth(limit); label->setWordWrap(true); } } else { label->setWordWrap(true); } const TargetIdToDiagnosticTable table = m_targetIdsToDiagnostics; const bool hideToolTipAfterLinkActivation = m_displayHints.hideTooltipAfterLinkActivation; QObject::connect(label, &QLabel::linkActivated, [table, hideToolTipAfterLinkActivation] (const QString &action) { const ClangBackEnd::DiagnosticContainer diagnostic = table.value(action); QTC_ASSERT(diagnostic != ClangBackEnd::DiagnosticContainer(), return); if (action.startsWith(LINK_ACTION_APPLY_FIX)) applyFixit(diagnostic); else openEditorAt(diagnostic); if (hideToolTipAfterLinkActivation) Utils::ToolTip::hideImmediately(); }); return label; } QString htmlText(const QVector &diagnostics) { // For debugging, add: style='border-width:1px;border-color:black' QString text = ""; foreach (const ClangBackEnd::DiagnosticContainer &diagnostic, diagnostics) text.append(tableRows(diagnostic)); text.append("
"); return text; } QString tableRows(const ClangBackEnd::DiagnosticContainer &diagnostic) { m_mainFilePath = m_displayHints.showFileNameInMainDiagnostic ? Utf8String() : diagnostic.location().filePath(); QString text; if (m_displayHints.showCategoryAndEnableOption) text.append(diagnosticCategoryAndEnableOptionRow(diagnostic)); text.append(diagnosticRow(diagnostic, IndentMode::DoNotIndent)); text.append(diagnosticRowsForChildren(diagnostic)); return text; } static QString diagnosticCategoryAndEnableOptionRow( const ClangBackEnd::DiagnosticContainer &diagnostic) { const QString text = QString::fromLatin1( " " " %1" "  %2" " ") .arg(diagnostic.category(), diagnostic.enableOption()); return text; } QString diagnosticText(const ClangBackEnd::DiagnosticContainer &diagnostic) { const bool hasFixit = m_displayHints.enableClickableFixits && !diagnostic.fixIts().isEmpty(); const QString diagnosticText = diagnostic.text().toString().toHtmlEscaped(); const QString text = QString::fromLatin1("%1: %2") .arg(clickableLocation(diagnostic, m_mainFilePath), clickableFixIt(diagnostic, diagnosticText, hasFixit)); return text; } QString diagnosticRow(const ClangBackEnd::DiagnosticContainer &diagnostic, IndentMode indentMode) { const QString text = QString::fromLatin1( " " " %2" " ") .arg(indentModeToHtmlStyle(indentMode), diagnosticText(diagnostic)); return text; } QString diagnosticRowsForChildren(const ClangBackEnd::DiagnosticContainer &diagnostic) { const QVector children = diagnostic.children(); QString text; if (children.size() <= 10) { text += diagnosticRowsForChildren(children.begin(), children.end()); } else { text += diagnosticRowsForChildren(children.begin(), children.begin() + 7); text += ellipsisRow(); text += diagnosticRowsForChildren(children.end() - 3, children.end()); } return text; } QString diagnosticRowsForChildren( const QVector::const_iterator first, const QVector::const_iterator last) { QString text; for (auto it = first; it != last; ++it) text.append(diagnosticRow(*it, IndentMode::Indent)); return text; } QString clickableLocation(const ClangBackEnd::DiagnosticContainer &diagnostic, const QString &mainFilePath) { const ClangBackEnd::SourceLocationContainer location = diagnostic.location(); const QString filePrefix = fileNamePrefix(mainFilePath, location); const QString lineColumn = locationToString(location); const QString linkText = filePrefix + lineColumn; const QString targetId = generateTargetId(LINK_ACTION_GOTO_LOCATION, diagnostic); return wrapInLink(linkText, targetId); } QString clickableFixIt(const ClangBackEnd::DiagnosticContainer &diagnostic, const QString &text, bool hasFixIt) { if (!hasFixIt) return text; QString clickableText = text; QString nonClickableCategory; const int colonPosition = text.indexOf(QStringLiteral(": ")); if (colonPosition != -1) { nonClickableCategory = text.mid(0, colonPosition + 2); clickableText = text.mid(colonPosition + 2); } const QString targetId = generateTargetId(LINK_ACTION_APPLY_FIX, diagnostic); return nonClickableCategory + wrapInLink(clickableText, targetId); } QString generateTargetId(const QString &targetPrefix, const ClangBackEnd::DiagnosticContainer &diagnostic) { const QString idAsString = QString::number(++m_targetIdCounter); const QString targetId = targetPrefix + idAsString; m_targetIdsToDiagnostics.insert(targetId, diagnostic); return targetId; } static QString wrapInLink(const QString &text, const QString &target) { return QStringLiteral("%2").arg(target, text); } static QString ellipsisRow() { return QString::fromLatin1( " " " ..." " ") .arg(indentModeToHtmlStyle(IndentMode::Indent)); } static QString indentModeToHtmlStyle(IndentMode indentMode) { return indentMode == IndentMode::Indent ? QString("padding-left:10px") : QString(); } static int widthLimit() { return QApplication::desktop()->availableGeometry(QCursor::pos()).width() / 2; } private: const DisplayHints m_displayHints; using TargetIdToDiagnosticTable = QHash; TargetIdToDiagnosticTable m_targetIdsToDiagnostics; unsigned m_targetIdCounter = 0; QString m_mainFilePath; }; } // anonymous namespace namespace ClangCodeModel { namespace Internal { QWidget *ClangDiagnosticWidget::create( const QVector &diagnostics, const Destination &destination) { WidgetFromDiagnostics::DisplayHints hints; if (destination == ToolTip) { hints.showCategoryAndEnableOption = true; hints.showFileNameInMainDiagnostic = false; hints.enableClickableFixits = true; hints.limitWidth = true; hints.hideTooltipAfterLinkActivation = true; } else { // Info Bar hints.showCategoryAndEnableOption = false; hints.showFileNameInMainDiagnostic = true; // Clickable fixits might change toolchain headers, so disable. hints.enableClickableFixits = false; hints.limitWidth = false; hints.hideTooltipAfterLinkActivation = false; } return WidgetFromDiagnostics::create(diagnostics, hints); } } // namespace Internal } // namespace ClangCodeModel