Browse Source

Add CoverageView to display line/branch coverage in the editor side-bar (gutter)

merge-requests/413/head
doe300 4 years ago
parent
commit
a46e900379
  1. 1
      src/CMakeLists.txt
  2. 1
      src/config.cpp
  3. 6
      src/menu.cpp
  4. 3
      src/source.cpp
  5. 3
      src/source.hpp
  6. 30
      src/source_clang.cpp
  7. 141
      src/source_coverage.cpp
  8. 34
      src/source_coverage.hpp
  9. 7
      src/window.cpp

1
src/CMakeLists.txt

@ -20,6 +20,7 @@ set(JUCI_SHARED_FILES
source.cpp source.cpp
source_base.cpp source_base.cpp
source_clang.cpp source_clang.cpp
source_coverage.cpp
source_diff.cpp source_diff.cpp
source_generic.cpp source_generic.cpp
source_language_protocol.cpp source_language_protocol.cpp

1
src/config.cpp

@ -408,6 +408,7 @@ std::string Config::default_config() {
"source_implement_method": "<primary><shift>m", "source_implement_method": "<primary><shift>m",
"source_goto_next_diagnostic": "<primary>e", "source_goto_next_diagnostic": "<primary>e",
"source_apply_fix_its": "<control>space", "source_apply_fix_its": "<control>space",
"source_toggle_coverage": "<primary><shift>c",
"project_set_run_arguments": "", "project_set_run_arguments": "",
"project_compile_and_run": "<primary>Return", "project_compile_and_run": "<primary>Return",
"project_compile": "<primary><shift>Return", "project_compile": "<primary><shift>Return",

6
src/menu.cpp

@ -399,6 +399,12 @@ const Glib::ustring menu_xml = R"RAW(<interface>
<attribute name='action'>app.source_apply_fix_its</attribute> <attribute name='action'>app.source_apply_fix_its</attribute>
</item> </item>
</section> </section>
<section>
<item>
<attribute name='label' translatable='yes'>_Toggle _Coverage</attribute>
<attribute name='action'>app.source_toggle_coverage</attribute>
</item>
</section>
</submenu> </submenu>
<submenu> <submenu>

3
src/source.cpp

@ -142,7 +142,7 @@ std::string Source::FixIt::string(BaseView &view) {
std::set<Source::View *> Source::View::non_deleted_views; std::set<Source::View *> Source::View::non_deleted_views;
std::set<Source::View *> Source::View::views; std::set<Source::View *> Source::View::views;
Source::View::View(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language, bool is_generic_view) : BaseView(file_path, language), SpellCheckView(file_path, language), DiffView(file_path, language) { Source::View::View(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language, bool is_generic_view) : BaseView(file_path, language), SpellCheckView(file_path, language), DiffView(file_path, language), CoverageView(file_path, language) {
non_deleted_views.emplace(this); non_deleted_views.emplace(this);
views.emplace(this); views.emplace(this);
@ -463,6 +463,7 @@ bool Source::View::save() {
void Source::View::configure() { void Source::View::configure() {
SpellCheckView::configure(); SpellCheckView::configure();
DiffView::configure(); DiffView::configure();
CoverageView::configure();
if(Config::get().source.style.size() > 0) { if(Config::get().source.style.size() > 0) {
auto scheme = StyleSchemeManager::get_default()->get_scheme(Config::get().source.style); auto scheme = StyleSchemeManager::get_default()->get_scheme(Config::get().source.style);

3
src/source.hpp

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "process.hpp" #include "process.hpp"
#include "source_coverage.hpp"
#include "source_diff.hpp" #include "source_diff.hpp"
#include "source_spellcheck.hpp" #include "source_spellcheck.hpp"
#include "tooltips.hpp" #include "tooltips.hpp"
@ -72,7 +73,7 @@ namespace Source {
} }
}; };
class View : public SpellCheckView, public DiffView { class View : public SpellCheckView, public DiffView, public CoverageView {
public: public:
static std::set<View *> non_deleted_views; static std::set<View *> non_deleted_views;
static std::set<View *> views; static std::set<View *> views;

30
src/source_clang.cpp

@ -2003,6 +2003,36 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file
return std::tuple<Source::Offset, std::string, size_t>(Source::Offset(), "", 0); return std::tuple<Source::Offset, std::string, size_t>(Source::Offset(), "", 0);
} }
}; };
get_coverage = [this]() {
auto build = Project::Build::create(this->file_path);
if(build->project_path.empty()) {
Info::get().print(this->file_path.filename().string() + ": could not find a supported build system");
return std::vector<Coverage::LineCoverage>{};
}
build->update_default();
CompileCommands commands(build->get_default_path());
boost::filesystem::path object_file;
for(const auto &command : commands.commands) {
if(command.file == this->file_path) {
auto values = command.parameter_values("-o");
if(!values.empty()) {
object_file = command.directory / values.front();
break;
}
}
}
if(object_file.empty()) {
Info::get().print(this->file_path.filename().string() + ": could not find the C/C++ object file");
return std::vector<Coverage::LineCoverage>{};
}
auto result = Coverage::analyze(Coverage::FileInfo{this->file_path, object_file, build->get_default_path(), this->language_id});
if(result.empty()) {
Info::get().print(this->file_path.filename().string() + ": no supported coverage information found");
}
return result;
};
} }
Source::ClangViewRefactor::Identifier Source::ClangViewRefactor::get_identifier() { Source::ClangViewRefactor::Identifier Source::ClangViewRefactor::get_identifier() {

141
src/source_coverage.cpp

@ -0,0 +1,141 @@
#include "source_coverage.hpp"
#include <algorithm>
#include <sstream>
Source::CoverageView::Renderer::Renderer() : Gsv::GutterRendererText() {
set_padding(4, 0);
}
Source::CoverageView::CoverageView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language) : BaseView(file_path, language), renderer(new Renderer()) {}
Source::CoverageView::~CoverageView() {
get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT)->remove(renderer.get());
}
void Source::CoverageView::configure() {
// Set colors
auto &yellow = renderer->yellow;
auto &red = renderer->red;
auto &green = renderer->green;
auto &transparent = renderer->transparent;
auto normal_color = get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_NORMAL);
auto light_theme = (normal_color.get_red() + normal_color.get_green() + normal_color.get_blue()) / 3 < 0.5;
yellow.set_rgba(1.0, 1.0, 0.2);
double factor = light_theme ? 0.85 : 0.5;
yellow.set_red(normal_color.get_red() + factor * (yellow.get_red() - normal_color.get_red()));
yellow.set_green(normal_color.get_green() + factor * (yellow.get_green() - normal_color.get_green()));
yellow.set_blue(normal_color.get_blue() + factor * (yellow.get_blue() - normal_color.get_blue()));
red.set_rgba(1.0, 0.0, 0.0);
factor = light_theme ? 0.8 : 0.35;
red.set_red(normal_color.get_red() + factor * (red.get_red() - normal_color.get_red()));
red.set_green(normal_color.get_green() + factor * (red.get_green() - normal_color.get_green()));
red.set_blue(normal_color.get_blue() + factor * (red.get_blue() - normal_color.get_blue()));
green.set_rgba(0.0, 1.0, 0.0);
factor = light_theme ? 0.7 : 0.4;
green.set_red(normal_color.get_red() + factor * (green.get_red() - normal_color.get_red()));
green.set_green(normal_color.get_green() + factor * (green.get_green() - normal_color.get_green()));
green.set_blue(normal_color.get_blue() + factor * (green.get_blue() - normal_color.get_blue()));
transparent.set_rgba(1.0, 1.0, 1.0, 1.0);
// Style gutter
renderer->set_alignment_mode(Gsv::GutterRendererAlignmentMode::GUTTER_RENDERER_ALIGNMENT_MODE_FIRST);
renderer->set_alignment(1.0, -1);
renderer->set_padding(3, -1);
// Connect gutter renderer signals
renderer->signal_query_data().connect([this](const Gtk::TextIter &start, const Gtk::TextIter &end, Gsv::GutterRendererState state) {
update_gutter(find_coverage(start.get_line()));
});
renderer->signal_query_activatable().connect([this](const Gtk::TextIter &, const Gdk::Rectangle &, GdkEvent *) {
return !line_coverage.empty();
});
renderer->signal_query_tooltip().connect([this](const Gtk::TextIter &iter, const Gdk::Rectangle &area, int x, int y, const Glib::RefPtr<Gtk::Tooltip> &tooltip) {
if(const auto *entry = find_coverage(iter.get_line())) {
tooltip->set_text(update_tooltip(*entry));
return true;
}
return false;
});
get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT)->insert(renderer.get(), -50);
renderer->set_visible(false);
}
void Source::CoverageView::toggle_coverage() {
if(!line_coverage.empty()) {
update_coverage({});
}
else if(get_coverage) {
update_coverage(get_coverage());
}
}
void Source::CoverageView::update_coverage(std::vector<Coverage::LineCoverage> &&coverage) {
line_coverage = std::move(coverage);
unsigned long max_line_count = 0;
for(const auto &line : line_coverage) {
max_line_count = std::max(max_line_count, line.count);
}
if(line_coverage.empty()) {
renderer->set_visible(false);
}
else {
int width, height;
renderer->measure(std::to_string(max_line_count), width, height);
renderer->set_visible(true);
renderer->set_size(width);
}
renderer->queue_draw();
}
const Coverage::LineCoverage *Source::CoverageView::find_coverage(int line) {
if(line_coverage.empty()) {
return nullptr;
}
auto it = std::lower_bound(line_coverage.begin(), line_coverage.end(), line, [](const Coverage::LineCoverage &entry, int line) {
return entry.line < static_cast<unsigned long>(line);
});
if(it != line_coverage.end() && it->line == static_cast<unsigned long>(line)) {
return &(*it);
}
return nullptr;
}
void Source::CoverageView::update_gutter(const Coverage::LineCoverage *line_coverage) {
if(!line_coverage) {
renderer->set_text("");
renderer->set_background(renderer->transparent);
return;
}
renderer->set_text(std::to_string(line_coverage->count));
if(line_coverage->fully_covered()) {
renderer->set_background(renderer->green);
}
else if(line_coverage->partially_covered()) {
renderer->set_background(renderer->yellow);
}
else {
renderer->set_background(renderer->red);
}
}
std::string Source::CoverageView::update_tooltip(const Coverage::LineCoverage &line_coverage) {
std::stringstream ss;
ss << "Line executed " << line_coverage.count << " times";
if(line_coverage.count > 0 && line_coverage.has_unexecuted_statements) {
ss << " (with unexecuted statements)";
}
if(!line_coverage.branches.empty()) {
ss << ", with branches:";
for(const auto &branch : line_coverage.branches) {
ss << "\n- " << (branch.is_fallthrough ? "Fall-through " : "") << (branch.is_throw ? "Exceptional " : "") << "Branch executed " << branch.count << " times";
}
}
return ss.str();
}

34
src/source_coverage.hpp

@ -0,0 +1,34 @@
#pragma once
#include "coverage.hpp"
#include "source_base.hpp"
namespace Source {
class CoverageView : virtual public Source::BaseView {
class Renderer : public Gsv::GutterRendererText {
public:
Renderer();
Gdk::RGBA yellow, red, green, transparent;
};
public:
CoverageView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language);
~CoverageView() override;
void configure() override;
std::function<std::vector<Coverage::LineCoverage>()> get_coverage;
void toggle_coverage();
private:
std::unique_ptr<Renderer> renderer;
std::vector<Coverage::LineCoverage> line_coverage;
void update_coverage(std::vector<Coverage::LineCoverage> &&coverage);
void update_gutter(const Coverage::LineCoverage *line_coverage);
std::string update_tooltip(const Coverage::LineCoverage &line_coverage);
const Coverage::LineCoverage *find_coverage(int line);
};
} // namespace Source

7
src/window.cpp

@ -1376,6 +1376,12 @@ void Window::set_menu_actions() {
} }
}); });
menu.add_action("source_toggle_coverage", []() {
if(auto view = Notebook::get().get_current_view()) {
view->toggle_coverage();
}
});
menu.add_action("project_set_run_arguments", []() { menu.add_action("project_set_run_arguments", []() {
auto project = Project::create(); auto project = Project::create();
auto run_arguments = project->get_run_arguments(); auto run_arguments = project->get_run_arguments();
@ -1804,6 +1810,7 @@ void Window::set_menu_actions() {
menu.actions["source_implement_method"]->set_enabled(view && view->get_method); menu.actions["source_implement_method"]->set_enabled(view && view->get_method);
menu.actions["source_goto_next_diagnostic"]->set_enabled(view && view->goto_next_diagnostic); menu.actions["source_goto_next_diagnostic"]->set_enabled(view && view->goto_next_diagnostic);
menu.actions["source_apply_fix_its"]->set_enabled(view && view->get_fix_its); menu.actions["source_apply_fix_its"]->set_enabled(view && view->get_fix_its);
menu.actions["source_toggle_coverage"]->set_enabled(view && view->get_coverage);
#ifdef JUCI_ENABLE_DEBUG #ifdef JUCI_ENABLE_DEBUG
Project::debug_activate_menu_items(); Project::debug_activate_menu_items();
#endif #endif

Loading…
Cancel
Save