diff --git a/CMakeLists.txt b/CMakeLists.txt
index 21873f3..d9d2592 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,59 +1,60 @@
cmake_minimum_required(VERSION 2.8.12)
project(kdevgoplugin)
set(VERSION_MAJOR 1)
set(VERSION_MINOR 90)
set(VERSION_PATCH 90)
# KDevplatform dependency version
set(KDEVPLATFORM_VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
find_package(ECM 0.0.9 REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH})
include(ECMAddTests)
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings)
include(GenerateExportHeader)
find_package(Qt5 REQUIRED Core Widgets Test)
find_package(KF5 REQUIRED COMPONENTS ItemModels ThreadWeaver TextEditor I18n)
find_package(KDevPlatform ${KDEVPLATFORM_VERSION} REQUIRED)
find_package(KDevelop-PG-Qt REQUIRED)
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_SOURCE_DIR}
)
enable_testing()
add_subdirectory(app_templates)
add_subdirectory(buildsystem)
add_subdirectory(parser)
add_subdirectory(duchain)
add_subdirectory(codecompletion)
+add_subdirectory(gometalinter)
include_directories(
${KDEVPGQT_INCLUDE_DIR}
)
kdevplatform_add_plugin(kdevgoplugin JSON kdevgo.json SOURCES
kdevgoplugin.cpp
golangparsejob.cpp
gohighlighting.cpp
godebug.cpp
)
target_link_libraries(kdevgoplugin
KDev::Interfaces
KDev::Language
KF5::ThreadWeaver
KF5::TextEditor
kdevgoparser
kdevgoduchain
kdevgocompletion
)
install(FILES builtins.go DESTINATION ${DATA_INSTALL_DIR}/kdev-go)
diff --git a/gometalinter/CMakeLists.txt b/gometalinter/CMakeLists.txt
new file mode 100644
index 0000000..2ebc6a4
--- /dev/null
+++ b/gometalinter/CMakeLists.txt
@@ -0,0 +1,18 @@
+add_definitions(-DTRANSLATION_DOMAIN=\"gometalinter\")
+
+set(gometalinter_SRCS plugin.cpp job.cpp problemmodel.cpp)
+
+qt5_add_resources(gometalinter_SRCS
+ gometalinter.qrc
+)
+kdevplatform_add_plugin(gometalinter
+ JSON gometalinter.json
+ SOURCES ${gometalinter_SRCS}
+)
+
+target_link_libraries(gometalinter
+ KDev::Language
+ KDev::Project
+ KDev::Shell
+ KF5::ItemViews
+)
diff --git a/gometalinter/gometalinter.json b/gometalinter/gometalinter.json
new file mode 100644
index 0000000..08d3f03
--- /dev/null
+++ b/gometalinter/gometalinter.json
@@ -0,0 +1,27 @@
+{
+ "GenericName": "Gometalinter Support",
+ "GenericName[x-test]": "xxGometalinter Supportxx",
+ "KPlugin": {
+ "Authors": [
+ {
+ "Name": "Mikhail Ivchenko",
+ "Name[x-test]": "xxMikhail Ivchenkoxx"
+ }
+ ],
+ "Category": "Analyzers",
+ "Description": "This plugin integrates gometalinter (static analysis tool) to KDevelop",
+ "Description[x-test]": "xxThis plugin integrates gometalinter (static analysis tool) to KDevelopxx",
+ "Id": "gometalinter",
+ "License": "GPL",
+ "Name": "Gometalinter Support",
+ "Name[x-test]": "xxGometalinter Supportxx",
+ "ServiceTypes": [
+ "KDevelop/Plugin"
+ ]
+ },
+ "X-KDevelop-Category": "Global",
+ "X-KDevelop-IRequired": [
+ "org.kdevelop.IExecutePlugin"
+ ],
+ "X-KDevelop-Mode": "GUI"
+}
diff --git a/gometalinter/gometalinter.qrc b/gometalinter/gometalinter.qrc
new file mode 100644
index 0000000..43e6ea7
--- /dev/null
+++ b/gometalinter/gometalinter.qrc
@@ -0,0 +1,6 @@
+
+
+
+ gometalinter.rc
+
+
diff --git a/gometalinter/gometalinter.rc b/gometalinter/gometalinter.rc
new file mode 100644
index 0000000..ce9990b
--- /dev/null
+++ b/gometalinter/gometalinter.rc
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/gometalinter/job.cpp b/gometalinter/job.cpp
new file mode 100644
index 0000000..12cfb71
--- /dev/null
+++ b/gometalinter/job.cpp
@@ -0,0 +1,135 @@
+/* KDevelop gometalinter support
+ *
+ * Copyright 2017 Mikhail Ivchenko
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ */
+
+#include "job.h"
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+namespace GoMetaLinter
+{
+
+
+Job::Job(const QUrl &workingDirectory, const QString &path, QObject *parent)
+ : KDevelop::OutputExecuteJob(parent)
+ , m_timer(new QElapsedTimer)
+{
+ setJobName(i18n("Go Meta Linter Analysis"));
+
+ setCapabilities(KJob::Killable);
+ setStandardToolView(KDevelop::IOutputView::TestView);
+ setBehaviours(KDevelop::IOutputView::AutoScroll);
+
+ setProperties(KDevelop::OutputExecuteJob::JobProperty::DisplayStdout);
+ setProperties(KDevelop::OutputExecuteJob::JobProperty::DisplayStderr);
+ setProperties(KDevelop::OutputExecuteJob::JobProperty::PostProcessOutput);
+
+ setWorkingDirectory(workingDirectory);
+ QStringList commandLine = {"gometalinter", "--aggregate", "--sort=path", path};
+ *this << commandLine;
+ m_projectRootPath = KDevelop::Path(workingDirectory);
+}
+
+Job::~Job()
+{
+ doKill();
+}
+
+void Job::postProcessStdout(const QStringList& lines)
+{
+ static const auto problemRegex = QRegularExpression(QStringLiteral("^([^:]*):([^:]*):([^:]*):([^:]*): ([^:]*)$"));
+
+ QRegularExpressionMatch match;
+
+ QVector problems;
+
+ foreach (const QString & line, lines) {
+ match = problemRegex.match(line);
+ if (match.hasMatch()) {
+ KDevelop::IProblem::Ptr problem(new KDevelop::DetectedProblem(i18n("Go Meta Linter")));
+ if(match.captured(4) == QStringLiteral("warning"))
+ {
+ problem->setSeverity(KDevelop::IProblem::Warning);
+ }
+ if(match.captured(4) == QStringLiteral("error"))
+ {
+ problem->setSeverity(KDevelop::IProblem::Error);
+ }
+ problem->setDescription(match.captured(5));
+ KDevelop::DocumentRange range;
+ range.document = KDevelop::IndexedString(KDevelop::Path(m_projectRootPath, match.captured(1)).toLocalFile());
+ range.setBothLines(match.captured(2).toInt() - 1);
+ if(!match.captured(3).isEmpty())
+ {
+ range.setBothColumns(match.captured(3).toInt() - 1);
+ }
+ problem->setFinalLocation(range);
+ problems.append(problem);
+ }
+ }
+
+ emit problemsDetected(problems);
+
+ if (status() == KDevelop::OutputExecuteJob::JobStatus::JobRunning) {
+ KDevelop::OutputExecuteJob::postProcessStdout(lines);
+ }
+}
+
+void Job::start()
+{
+ m_timer->restart();
+ KDevelop::OutputExecuteJob::start();
+}
+
+void Job::childProcessError(QProcess::ProcessError e)
+{
+ QString message;
+
+ switch (e) {
+ case QProcess::FailedToStart:
+ message = i18n("Failed to start Go Meta Linter from \"%1\".", commandLine()[0]);
+ break;
+
+ case QProcess::Crashed:
+ if (status() != KDevelop::OutputExecuteJob::JobStatus::JobCanceled) {
+ message = i18n("Go Meta Linter crashed.");
+ }
+ break;
+
+ case QProcess::Timedout:
+ message = i18n("Go Meta Linter process timed out.");
+ break;
+
+ case QProcess::WriteError:
+ message = i18n("Write to Go Meta Linter process failed.");
+ break;
+
+ case QProcess::ReadError:
+ message = i18n("Read from Go Meta Linter process failed.");
+ break;
+
+ case QProcess::UnknownError:
+ break;
+ }
+
+ if (!message.isEmpty()) {
+ KMessageBox::error(qApp->activeWindow(), message, i18n("Go Meta Linter Error"));
+ }
+
+ KDevelop::OutputExecuteJob::childProcessError(e);
+}
+
+}
diff --git a/gometalinter/job.h b/gometalinter/job.h
new file mode 100644
index 0000000..08795b2
--- /dev/null
+++ b/gometalinter/job.h
@@ -0,0 +1,49 @@
+/* KDevelop gometalinter support
+ *
+ * Copyright 2017 Mikhail Ivchenko
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ */
+
+#ifndef GOMETALINTER_JOB_H
+#define GOMETALINTER_JOB_H
+
+#include
+#include
+#include
+
+class QElapsedTimer;
+
+namespace GoMetaLinter
+{
+
+class Job : public KDevelop::OutputExecuteJob
+{
+ Q_OBJECT
+
+public:
+ explicit Job(const QUrl &workingDirectory, const QString &path, QObject *parent = nullptr);
+ ~Job() override;
+
+ void start() override;
+
+Q_SIGNALS:
+ void problemsDetected(const QVector& problems);
+
+protected slots:
+ void postProcessStdout(const QStringList& lines) override;
+
+ void childProcessError(QProcess::ProcessError processError) override;
+
+protected:
+ QScopedPointer m_timer;
+
+ KDevelop::Path m_projectRootPath;
+};
+
+}
+
+#endif
diff --git a/gometalinter/plugin.cpp b/gometalinter/plugin.cpp
new file mode 100644
index 0000000..5ccdbc0
--- /dev/null
+++ b/gometalinter/plugin.cpp
@@ -0,0 +1,200 @@
+/* KDevelop gometalinter support
+ *
+ * Copyright 2017 Mikhail Ivchenko
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ */
+
+#include "plugin.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+K_PLUGIN_FACTORY_WITH_JSON(GoMetaLinterFactory, "gometalinter.json", registerPlugin();)
+
+namespace GoMetaLinter
+{
+
+Plugin::Plugin(QObject *parent, const QVariantList&)
+ : IPlugin(QStringLiteral("gometalinter"), parent),
+ m_currentProject(nullptr),
+ m_model(new ProblemModel(this)),
+ m_job(nullptr)
+{
+ setXMLFile(QStringLiteral("gometalinter.rc"));
+
+ auto analyzeFile = [this](){ run(false); };
+ auto analyzeProject = [this](){ run(true); };
+
+ m_menuActionFile = new QAction(i18n("Analyze Current File with Go Meta Linter"), this);
+ connect(m_menuActionFile, &QAction::triggered, this, analyzeFile);
+ actionCollection()->addAction(QStringLiteral("gometalinter_file"), m_menuActionFile);
+
+ m_menuActionProject = new QAction(i18n("Analyze Current Project with Go Meta Linter"), this);
+ connect(m_menuActionProject, &QAction::triggered, this, analyzeProject);
+ actionCollection()->addAction(QStringLiteral("gometalinter_project"), m_menuActionProject);
+
+ auto documentController = core()->documentController();
+ connect(documentController, &KDevelop::IDocumentController::documentClosed, this, &Plugin::updateActions);
+ connect(documentController, &KDevelop::IDocumentController::documentActivated, this, &Plugin::updateActions);
+
+ auto projectController = core()->projectController();
+ connect(projectController, &KDevelop::IProjectController::projectOpened, this, &Plugin::updateActions);
+ connect(projectController, &KDevelop::IProjectController::projectClosed, this, &Plugin::projectClosed);
+
+ m_contextActionFile = new QAction(i18n("Go Meta Linter"), this);
+ connect(m_contextActionFile, &QAction::triggered, this, analyzeFile);
+
+ m_contextActionProject = new QAction(i18n("Go Meta Linter"), this);
+ connect(m_contextActionProject, &QAction::triggered, this, analyzeProject);
+
+ m_contextActionProjectItem = new QAction(i18n("Go Meta Linter"), this);
+
+ updateActions();
+}
+
+Plugin::~Plugin()
+{
+ killJob();
+}
+
+void Plugin::run(bool checkProject)
+{
+ auto path = checkProject ? m_currentProject->path().toUrl() : core()->documentController()->activeDocument()->url();
+ run(m_currentProject, path.toLocalFile());
+}
+
+void Plugin::run(KDevelop::IProject* project, const QString &path)
+{
+ auto workingDirectory = project->path().toUrl();
+ m_model->reset(project, path);
+
+ m_job = new Job(workingDirectory, path);
+ connect(m_job, &Job::problemsDetected, m_model, &ProblemModel::addProblems);
+ connect(m_job, &Job::finished, this, &Plugin::result);
+
+ core()->uiController()->registerStatus(new KDevelop::JobStatus(m_job, QStringLiteral("Go Meta Linter")));
+ core()->runController()->registerJob(m_job);
+
+ m_model->show();
+
+ updateActions();
+}
+
+void Plugin::updateActions()
+{
+ m_currentProject = nullptr;
+ m_menuActionFile->setEnabled(false);
+ m_menuActionProject->setEnabled(false);
+
+ KDevelop::IDocument* activeDocument = core()->documentController()->activeDocument();
+
+ if (!activeDocument) {
+ return;
+ }
+
+ m_currentProject = core()->projectController()->findProjectForUrl(activeDocument->url());
+
+ m_menuActionFile->setEnabled(m_currentProject);
+ m_menuActionProject->setEnabled(m_currentProject);
+}
+
+void Plugin::projectClosed(KDevelop::IProject *project)
+{
+ if (project != m_model->project()) {
+ return;
+ }
+
+ killJob();
+ m_model->reset();
+}
+
+void Plugin::killJob()
+{
+ if(isRunning())
+ {
+ m_job->kill(KJob::EmitResult);
+ }
+}
+
+void Plugin::result(KJob*)
+{
+ if (!core()->projectController()->projects().contains(m_model->project())) {
+ m_model->reset();
+ } else {
+ m_model->show();
+ }
+
+ m_job = nullptr;
+
+ updateActions();
+}
+
+KDevelop::ContextMenuExtension Plugin::contextMenuExtension(KDevelop::Context* context)
+{
+ KDevelop::ContextMenuExtension extension = KDevelop::IPlugin::contextMenuExtension(context);
+
+ if (context->hasType(KDevelop::Context::EditorContext) && m_currentProject && !isRunning()) {
+ auto eContext = dynamic_cast(context);
+ QMimeDatabase db;
+ const auto mime = db.mimeTypeForUrl(eContext->url());
+
+ if (mime.name() == QLatin1String("text/x-go")) {
+ extension.addAction(KDevelop::ContextMenuExtension::AnalyzeFileGroup, m_contextActionFile);
+ extension.addAction(KDevelop::ContextMenuExtension::AnalyzeProjectGroup, m_contextActionProject);
+ }
+ }
+
+ if (context->hasType(KDevelop::Context::ProjectItemContext) && !isRunning()) {
+ auto pContext = dynamic_cast(context);
+ if (pContext->items().size() != 1) {
+ return extension;
+ }
+
+ auto item = pContext->items().first();
+
+ switch (item->type()) {
+ case KDevelop::ProjectBaseItem::File:
+ case KDevelop::ProjectBaseItem::Folder:
+ case KDevelop::ProjectBaseItem::BuildFolder:
+ break;
+
+ default:
+ return extension;
+ }
+
+ m_contextActionProjectItem->disconnect();
+ connect(m_contextActionProjectItem, &QAction::triggered, this, [this, item](){
+ run(item->project(), item->path().toLocalFile());
+ });
+
+ extension.addAction(KDevelop::ContextMenuExtension::AnalyzeProjectGroup, m_contextActionProjectItem);
+ }
+
+ return extension;
+}
+
+bool Plugin::isRunning()
+{
+ return m_job;
+}
+
+}
+
+#include "plugin.moc"
\ No newline at end of file
diff --git a/gometalinter/plugin.h b/gometalinter/plugin.h
new file mode 100644
index 0000000..f30729d
--- /dev/null
+++ b/gometalinter/plugin.h
@@ -0,0 +1,55 @@
+/* KDevelop gometalinter support
+ *
+ * Copyright 2017 Mikhail Ivchenko
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ */
+
+#ifndef GOMETALINTER_PLUGIN_H
+#define GOMETALINTER_PLUGIN_H
+
+#include "problemmodel.h"
+#include "job.h"
+
+#include
+#include
+#include
+
+namespace GoMetaLinter
+{
+
+class Plugin : public KDevelop::IPlugin
+{
+ Q_OBJECT
+public:
+ explicit Plugin(QObject *parent, const QVariantList&);
+ ~Plugin() override;
+ KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context) override;
+ void run(KDevelop::IProject* project, const QString &path);
+ bool isRunning();
+private:
+ void updateActions();
+ void projectClosed(KDevelop::IProject* project);
+ void killJob();
+ void result(KJob* job);
+ void run(bool checkProject);
+
+ KDevelop::IProject* m_currentProject;
+
+ QAction* m_menuActionFile;
+ QAction* m_menuActionProject;
+ QAction* m_contextActionFile;
+ QAction* m_contextActionProject;
+ QAction* m_contextActionProjectItem;
+
+ ProblemModel* m_model;
+ Job* m_job;
+};
+
+}
+
+
+#endif //KDEVGOPLUGIN_PLUGIN_H
diff --git a/gometalinter/problemmodel.cpp b/gometalinter/problemmodel.cpp
new file mode 100644
index 0000000..4db1d5a
--- /dev/null
+++ b/gometalinter/problemmodel.cpp
@@ -0,0 +1,120 @@
+/* KDevelop gometalinter support
+ *
+ * Copyright 2017 Mikhail Ivchenko
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ */
+
+#include "problemmodel.h"
+
+#include "plugin.h"
+
+#include
+#include
+#include
+#include
+
+#include
+
+namespace GoMetaLinter
+{
+
+inline KDevelop::ProblemModelSet* problemModelSet()
+{
+ return KDevelop::ICore::self()->languageController()->problemModelSet();
+}
+
+static const QString problemModelId = QStringLiteral("Go Meta Linter");
+
+ProblemModel::ProblemModel(Plugin* plugin)
+ : KDevelop::ProblemModel(plugin),
+ m_plugin(plugin),
+ m_project(nullptr)
+{
+ setFeatures(CanDoFullUpdate | ScopeFilter | SeverityFilter | Grouping | CanByPassScopeFilter);
+ reset();
+ problemModelSet()->addModel(problemModelId, i18n("Go Meta Linter"), this);
+}
+
+ProblemModel::~ProblemModel()
+{
+ problemModelSet()->removeModel(problemModelId);
+}
+
+KDevelop::IProject* ProblemModel::project() const
+{
+ return m_project;
+}
+
+bool ProblemModel::problemExists(KDevelop::IProblem::Ptr newProblem)
+{
+ for (auto problem : m_problems) {
+ if (newProblem->source() == problem->source() &&
+ newProblem->severity() == problem->severity() &&
+ newProblem->finalLocation() == problem->finalLocation() &&
+ newProblem->description() == problem->description() &&
+ newProblem->explanation() == problem->explanation())
+ return true;
+ }
+
+ return false;
+}
+
+void ProblemModel::addProblems(const QVector& problems)
+{
+ static int maxLength = 0;
+
+ if (m_problems.isEmpty()) {
+ maxLength = 0;
+ }
+
+ for (auto problem : problems) {
+
+ if (problemExists(problem)) {
+ continue;
+ }
+
+ m_problems.append(problem);
+ addProblem(problem);
+
+ // This performs adjusting of columns width in the ProblemsView
+ if (maxLength < problem->description().length()) {
+ maxLength = problem->description().length();
+ setProblems(m_problems);
+ }
+ }
+}
+
+void ProblemModel::reset()
+{
+ reset(nullptr, QString());
+}
+
+void ProblemModel::reset(KDevelop::IProject* project, const QString& path)
+{
+ m_project = project;
+ m_path = path;
+
+ clearProblems();
+ m_problems.clear();
+
+ QString tooltip = i18nc("@info:tooltip", "Re-Run Last Go Meta Linter Analysis");
+ setFullUpdateTooltip(tooltip);
+}
+
+void ProblemModel::show()
+{
+ problemModelSet()->showModel(problemModelId);
+}
+
+void ProblemModel::forceFullUpdate()
+{
+ if (m_project && !m_plugin->isRunning()) {
+ m_plugin->run(m_project, m_path);
+ }
+}
+
+}
diff --git a/gometalinter/problemmodel.h b/gometalinter/problemmodel.h
new file mode 100644
index 0000000..5ec9999
--- /dev/null
+++ b/gometalinter/problemmodel.h
@@ -0,0 +1,58 @@
+/* KDevelop gometalinter support
+ *
+ * Copyright 2017 Mikhail Ivchenko
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ */
+
+#ifndef GOMETALINTER_PROBLEMMODEL_H
+#define GOMETALINTER_PROBLEMMODEL_H
+
+#include
+
+namespace KDevelop
+{
+ class IProject;
+}
+
+namespace GoMetaLinter
+{
+
+class Plugin;
+
+class ProblemModel : public KDevelop::ProblemModel
+{
+public:
+ explicit ProblemModel(Plugin* plugin);
+ ~ProblemModel() override;
+
+ KDevelop::IProject* project() const;
+
+ void addProblems(const QVector& problems);
+
+ void reset();
+ void reset(KDevelop::IProject* project, const QString& path);
+
+ void show();
+
+ void forceFullUpdate() override;
+
+private:
+ bool problemExists(KDevelop::IProblem::Ptr newProblem);
+
+ using KDevelop::ProblemModel::setProblems;
+
+ Plugin* m_plugin;
+
+ KDevelop::IProject* m_project;
+ QString m_path;
+
+ QVector m_problems;
+};
+
+}
+
+#endif
\ No newline at end of file