aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/axivion/axivionplugin.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/axivion/axivionplugin.cpp')
-rw-r--r--src/plugins/axivion/axivionplugin.cpp348
1 files changed, 348 insertions, 0 deletions
diff --git a/src/plugins/axivion/axivionplugin.cpp b/src/plugins/axivion/axivionplugin.cpp
new file mode 100644
index 00000000000..7648448132a
--- /dev/null
+++ b/src/plugins/axivion/axivionplugin.cpp
@@ -0,0 +1,348 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "axivionplugin.h"
+
+#include "axivionoutputpane.h"
+#include "axivionprojectsettings.h"
+#include "axivionquery.h"
+#include "axivionresultparser.h"
+#include "axivionsettings.h"
+#include "axivionsettingspage.h"
+#include "axiviontr.h"
+
+#include <coreplugin/editormanager/documentmodel.h>
+#include <coreplugin/editormanager/editormanager.h>
+#include <coreplugin/icore.h>
+#include <coreplugin/messagemanager.h>
+
+#include <extensionsystem/pluginmanager.h>
+
+#include <projectexplorer/buildsystem.h>
+#include <projectexplorer/project.h>
+#include <projectexplorer/projectmanager.h>
+#include <projectexplorer/projectpanelfactory.h>
+
+#include <texteditor/textdocument.h>
+#include <texteditor/texteditor.h>
+#include <texteditor/textmark.h>
+
+#include <utils/qtcassert.h>
+#include <utils/utilsicons.h>
+
+#include <QAction>
+#include <QMessageBox>
+#include <QTimer>
+
+constexpr char AxivionTextMarkId[] = "AxivionTextMark";
+
+namespace Axivion::Internal {
+
+class AxivionPluginPrivate : public QObject
+{
+public:
+ AxivionProjectSettings *projectSettings(ProjectExplorer::Project *project);
+ void onStartupProjectChanged();
+ void fetchProjectInfo(const QString &projectName);
+ void handleProjectInfo(const ProjectInfo &info);
+ void handleOpenedDocs(ProjectExplorer::Project *project);
+ void onDocumentOpened(Core::IDocument *doc);
+ void onDocumentClosed(Core::IDocument * doc);
+ void clearAllMarks();
+ void handleIssuesForFile(const IssuesList &issues);
+ void fetchRuleInfo(const QString &id);
+
+ AxivionSettings m_axivionSettings;
+ AxivionSettingsPage m_axivionSettingsPage{&m_axivionSettings};
+ AxivionOutputPane m_axivionOutputPane;
+ QHash<ProjectExplorer::Project *, AxivionProjectSettings *> m_axivionProjectSettings;
+ ProjectInfo m_currentProjectInfo;
+ bool m_runningQuery = false;
+};
+
+static AxivionPlugin *s_instance = nullptr;
+static AxivionPluginPrivate *dd = nullptr;
+
+class AxivionTextMark : public TextEditor::TextMark
+{
+public:
+ AxivionTextMark(const Utils::FilePath &filePath, const ShortIssue &issue);
+
+private:
+ QString m_id;
+};
+
+AxivionTextMark::AxivionTextMark(const Utils::FilePath &filePath, const ShortIssue &issue)
+ : TextEditor::TextMark(filePath, issue.lineNumber, {Tr::tr("Axivion"), AxivionTextMarkId})
+ , m_id(issue.id)
+{
+ const QString markText = issue.entity.isEmpty() ? issue.message
+ : issue.entity + ": " + issue.message;
+ setToolTip(issue.errorNumber + " " + markText);
+ setPriority(TextEditor::TextMark::NormalPriority);
+ setLineAnnotation(markText);
+ setActionsProvider([this]{
+ auto action = new QAction;
+ action->setIcon(Utils::Icons::INFO.icon());
+ action->setToolTip(Tr::tr("Show rule details"));
+ QObject::connect(action, &QAction::triggered,
+ dd, [this]{ dd->fetchRuleInfo(m_id); });
+ return QList{action};
+ });
+}
+
+AxivionPlugin::AxivionPlugin()
+{
+ s_instance = this;
+}
+
+AxivionPlugin::~AxivionPlugin()
+{
+ if (dd && !dd->m_axivionProjectSettings.isEmpty()) {
+ qDeleteAll(dd->m_axivionProjectSettings);
+ dd->m_axivionProjectSettings.clear();
+ }
+ delete dd;
+ dd = nullptr;
+}
+
+AxivionPlugin *AxivionPlugin::instance()
+{
+ return s_instance;
+}
+
+bool AxivionPlugin::initialize(const QStringList &arguments, QString *errorMessage)
+{
+ Q_UNUSED(arguments)
+ Q_UNUSED(errorMessage)
+
+ dd = new AxivionPluginPrivate;
+ dd->m_axivionSettings.fromSettings(Core::ICore::settings());
+
+ auto panelFactory = new ProjectExplorer::ProjectPanelFactory;
+ panelFactory->setPriority(250);
+ panelFactory->setDisplayName(Tr::tr("Axivion"));
+ panelFactory->setCreateWidgetFunction([](ProjectExplorer::Project *project){
+ return new AxivionProjectSettingsWidget(project);
+ });
+ ProjectExplorer::ProjectPanelFactory::registerFactory(panelFactory);
+ connect(ProjectExplorer::ProjectManager::instance(),
+ &ProjectExplorer::ProjectManager::startupProjectChanged,
+ dd, &AxivionPluginPrivate::onStartupProjectChanged);
+ connect(Core::EditorManager::instance(), &Core::EditorManager::documentOpened,
+ dd, &AxivionPluginPrivate::onDocumentOpened);
+ connect(Core::EditorManager::instance(), &Core::EditorManager::documentClosed,
+ dd, &AxivionPluginPrivate::onDocumentClosed);
+ return true;
+}
+
+AxivionSettings *AxivionPlugin::settings()
+{
+ QTC_ASSERT(dd, return nullptr);
+ return &dd->m_axivionSettings;
+}
+
+AxivionProjectSettings *AxivionPlugin::projectSettings(ProjectExplorer::Project *project)
+{
+ QTC_ASSERT(project, return nullptr);
+ QTC_ASSERT(dd, return nullptr);
+
+ return dd->projectSettings(project);
+}
+
+bool AxivionPlugin::handleCertificateIssue()
+{
+ QTC_ASSERT(dd, return false);
+
+ const QString serverHost = QUrl(dd->m_axivionSettings.server.dashboard).host();
+ if (QMessageBox::question(Core::ICore::dialogParent(), Tr::tr("Certificate Error"),
+ Tr::tr("Server certificate for %1 cannot be authenticated.\n"
+ "Do you want to disable SSL verification for this server?\n"
+ "Note: This can expose you to man-in-the-middle attack.")
+ .arg(serverHost))
+ != QMessageBox::Yes) {
+ return false;
+ }
+ dd->m_axivionSettings.server.validateCert = false;
+ emit s_instance->settingsChanged();
+ return true;
+}
+
+void AxivionPlugin::fetchProjectInfo(const QString &projectName)
+{
+ QTC_ASSERT(dd, return);
+ dd->fetchProjectInfo(projectName);
+}
+
+ProjectInfo AxivionPlugin::projectInfo()
+{
+ QTC_ASSERT(dd, return {});
+ return dd->m_currentProjectInfo;
+}
+
+AxivionProjectSettings *AxivionPluginPrivate::projectSettings(ProjectExplorer::Project *project)
+{
+ auto &settings = m_axivionProjectSettings[project];
+ if (!settings)
+ settings = new AxivionProjectSettings(project);
+ return settings;
+}
+
+void AxivionPluginPrivate::onStartupProjectChanged()
+{
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
+ if (!project) {
+ clearAllMarks();
+ m_currentProjectInfo = ProjectInfo();
+ m_axivionOutputPane.updateDashboard();
+ return;
+ }
+
+ const AxivionProjectSettings *projSettings = projectSettings(project);
+ fetchProjectInfo(projSettings->dashboardProjectName());
+}
+
+void AxivionPluginPrivate::fetchProjectInfo(const QString &projectName)
+{
+ if (m_runningQuery) { // re-schedule
+ QTimer::singleShot(3000, [this, projectName]{ fetchProjectInfo(projectName); });
+ return;
+ }
+ clearAllMarks();
+ if (projectName.isEmpty()) {
+ m_currentProjectInfo = ProjectInfo();
+ m_axivionOutputPane.updateDashboard();
+ return;
+ }
+ m_runningQuery = true;
+
+ AxivionQuery query(AxivionQuery::ProjectInfo, {projectName});
+ AxivionQueryRunner *runner = new AxivionQueryRunner(query, this);
+ connect(runner, &AxivionQueryRunner::resultRetrieved, this, [this](const QByteArray &result){
+ handleProjectInfo(ResultParser::parseProjectInfo(result));
+ });
+ connect(runner, &AxivionQueryRunner::finished, [runner]{ runner->deleteLater(); });
+ runner->start();
+}
+
+void AxivionPluginPrivate::fetchRuleInfo(const QString &id)
+{
+ if (m_runningQuery) {
+ QTimer::singleShot(3000, [this, id]{ fetchRuleInfo(id); });
+ return;
+ }
+
+ const QStringList args = id.split(':');
+ QTC_ASSERT(args.size() == 2, return);
+ m_runningQuery = true;
+ AxivionQuery query(AxivionQuery::RuleInfo, args);
+ AxivionQueryRunner *runner = new AxivionQueryRunner(query, this);
+ connect(runner, &AxivionQueryRunner::resultRetrieved, this, [this](const QByteArray &result){
+ m_runningQuery = false;
+ m_axivionOutputPane.updateAndShowRule(ResultParser::parseRuleInfo(result));
+ });
+ connect(runner, &AxivionQueryRunner::finished, [runner]{ runner->deleteLater(); });
+ runner->start();
+}
+
+void AxivionPluginPrivate::handleOpenedDocs(ProjectExplorer::Project *project)
+{
+ if (project && ProjectExplorer::ProjectManager::startupProject() != project)
+ return;
+ const QList<Core::IDocument *> openDocuments = Core::DocumentModel::openedDocuments();
+ for (Core::IDocument *doc : openDocuments)
+ onDocumentOpened(doc);
+ if (project)
+ disconnect(ProjectExplorer::ProjectManager::instance(),
+ &ProjectExplorer::ProjectManager::projectFinishedParsing,
+ this, &AxivionPluginPrivate::handleOpenedDocs);
+}
+
+void AxivionPluginPrivate::clearAllMarks()
+{
+ const QList<Core::IDocument *> openDocuments = Core::DocumentModel::openedDocuments();
+ for (Core::IDocument *doc : openDocuments)
+ onDocumentClosed(doc);
+}
+
+void AxivionPluginPrivate::handleProjectInfo(const ProjectInfo &info)
+{
+ m_runningQuery = false;
+ if (!info.error.isEmpty()) {
+ Core::MessageManager::writeFlashing("Axivion: " + info.error);
+ return;
+ }
+
+ m_currentProjectInfo = info;
+ m_axivionOutputPane.updateDashboard();
+
+ if (m_currentProjectInfo.name.isEmpty())
+ return;
+
+ // handle already opened documents
+ if (auto buildSystem = ProjectExplorer::ProjectManager::startupBuildSystem();
+ !buildSystem || !buildSystem->isParsing()) {
+ handleOpenedDocs(nullptr);
+ } else {
+ connect(ProjectExplorer::ProjectManager::instance(),
+ &ProjectExplorer::ProjectManager::projectFinishedParsing,
+ this, &AxivionPluginPrivate::handleOpenedDocs);
+ }
+}
+
+void AxivionPluginPrivate::onDocumentOpened(Core::IDocument *doc)
+{
+ if (m_currentProjectInfo.name.isEmpty()) // we do not have a project info (yet)
+ return;
+
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
+ if (!doc || !project->isKnownFile(doc->filePath()))
+ return;
+
+ Utils::FilePath relative = doc->filePath().relativeChildPath(project->projectDirectory());
+ // for now only style violations
+ AxivionQuery query(AxivionQuery::IssuesForFileList, {m_currentProjectInfo.name, "SV",
+ relative.path() } );
+ AxivionQueryRunner *runner = new AxivionQueryRunner(query, this);
+ connect(runner, &AxivionQueryRunner::resultRetrieved, this, [this](const QByteArray &result){
+ handleIssuesForFile(ResultParser::parseIssuesList(result));
+ });
+ connect(runner, &AxivionQueryRunner::finished, [runner]{ runner->deleteLater(); });
+ runner->start();
+}
+
+void AxivionPluginPrivate::onDocumentClosed(Core::IDocument *doc)
+{
+ const auto document = qobject_cast<TextEditor::TextDocument *>(doc);
+ if (!document)
+ return;
+
+ const TextEditor::TextMarks marks = document->marks();
+ for (auto m : marks) {
+ if (m->category().id == AxivionTextMarkId)
+ delete m;
+ }
+}
+
+void AxivionPluginPrivate::handleIssuesForFile(const IssuesList &issues)
+{
+ if (issues.issues.isEmpty())
+ return;
+
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
+ if (!project)
+ return;
+
+ const Utils::FilePath filePath = project->projectDirectory()
+ .pathAppended(issues.issues.first().filePath);
+
+ const Utils::Id axivionId(AxivionTextMarkId);
+ for (const ShortIssue &issue : std::as_const(issues.issues)) {
+ // FIXME the line location can be wrong (even the whole issue could be wrong)
+ // depending on whether this line has been changed since the last axivion run and the
+ // current state of the file - some magic has to happen here
+ new AxivionTextMark(filePath, issue);
+ }
+}
+
+} // Axivion::Internal