Thank you for the very useful library! Few improvements: - Better header hygiene - Isolate `windows.h` to `.cpp` whenever possible - Use `WIN32_LEAN_AND_MEAN` - Remove unused headers Tested on Windows with ``` cmake .. -DCMAKE_BUILD_TYPE=Debug -GNinja -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_FLAGS="-ftime-trace -Wall -Wextra -Wpedantic -Wno-ignored-attributes" -DCMAKE_COLOR_DIAGNOSTICS=1 -DCPPTRACE_BUILD_TESTING=1 -DCPPTRACE_BUILD_BENCHMARKING=0 ``` There's a lot more that can be improved if you are interested. --------- Co-authored-by: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com>
144 lines
4.7 KiB
C++
144 lines
4.7 KiB
C++
#include "snippets/snippet.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <mutex>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
|
|
#include "utils/common.hpp"
|
|
#include "utils/microfmt.hpp"
|
|
#include "utils/utils.hpp"
|
|
|
|
namespace cpptrace {
|
|
namespace detail {
|
|
constexpr std::int64_t max_size = 1024 * 1024 * 10; // 10 MiB
|
|
|
|
struct line_range {
|
|
std::size_t begin;
|
|
std::size_t end; // one past the end
|
|
};
|
|
|
|
class snippet_manager {
|
|
bool loaded_contents;
|
|
std::string contents;
|
|
// 1-based indexing
|
|
std::vector<line_range> line_table;
|
|
public:
|
|
snippet_manager(const std::string& path) : loaded_contents(false) {
|
|
std::ifstream file;
|
|
try {
|
|
file.open(path, std::ios::ate);
|
|
if(file.is_open()) {
|
|
std::ifstream::pos_type size = file.tellg();
|
|
if(size == std::ifstream::pos_type(-1) || size > max_size) {
|
|
return;
|
|
}
|
|
// else load file
|
|
file.seekg(0, std::ios::beg);
|
|
contents.resize(to<std::size_t>(size));
|
|
if(!file.read(&contents[0], size)) {
|
|
// error ...
|
|
}
|
|
build_line_table();
|
|
loaded_contents = true;
|
|
}
|
|
} catch(const std::ifstream::failure&) {
|
|
// ...
|
|
}
|
|
}
|
|
|
|
// takes a 1-index line
|
|
std::string get_line(std::size_t line) const {
|
|
if(!loaded_contents || line > line_table.size()) {
|
|
return "";
|
|
} else {
|
|
return contents.substr(line_table[line].begin, line_table[line].end - line_table[line].begin);
|
|
}
|
|
}
|
|
|
|
std::size_t num_lines() const {
|
|
return line_table.size();
|
|
}
|
|
|
|
bool ok() const {
|
|
return loaded_contents;
|
|
}
|
|
private:
|
|
void build_line_table() {
|
|
line_table.push_back({0, 0});
|
|
std::size_t pos = 0; // stores the start of the current line
|
|
while(true) {
|
|
// find the end of the current line
|
|
std::size_t terminator_pos = contents.find('\n', pos);
|
|
if(terminator_pos == std::string::npos) {
|
|
line_table.push_back({pos, contents.size()});
|
|
break;
|
|
} else {
|
|
std::size_t end_pos = terminator_pos; // one past the end of the current line
|
|
if(end_pos > 0 && contents[end_pos - 1] == '\r') {
|
|
end_pos--;
|
|
}
|
|
line_table.push_back({pos, end_pos});
|
|
pos = terminator_pos + 1;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
std::mutex snippet_manager_mutex;
|
|
std::unordered_map<std::string, const snippet_manager> snippet_managers;
|
|
|
|
const snippet_manager& get_manager(const std::string& path) {
|
|
std::unique_lock<std::mutex> lock(snippet_manager_mutex);
|
|
auto it = snippet_managers.find(path);
|
|
if(it == snippet_managers.end()) {
|
|
return snippet_managers.insert({path, snippet_manager(path)}).first->second;
|
|
} else {
|
|
return it->second;
|
|
}
|
|
}
|
|
|
|
// how wide the margin for the line number should be
|
|
constexpr std::size_t margin_width = 8;
|
|
|
|
// 1-indexed line
|
|
std::string get_snippet(const std::string& path, std::size_t target_line, std::size_t context_size, bool color) {
|
|
const auto& manager = get_manager(path);
|
|
if(!manager.ok()) {
|
|
return "";
|
|
}
|
|
auto begin = target_line <= context_size + 1 ? 1 : target_line - context_size;
|
|
auto original_begin = begin;
|
|
auto end = std::min(target_line + context_size, manager.num_lines() - 1);
|
|
std::vector<std::string> lines;
|
|
for(auto line = begin; line <= end; line++) {
|
|
lines.push_back(manager.get_line(line));
|
|
}
|
|
// trim blank lines
|
|
while(begin < target_line && lines[begin - original_begin].empty()) {
|
|
begin++;
|
|
}
|
|
while(end > target_line && lines[end - original_begin].empty()) {
|
|
end--;
|
|
}
|
|
// make the snippet
|
|
std::string snippet;
|
|
for(auto line = begin; line <= end; line++) {
|
|
if(color && line == target_line) {
|
|
snippet += YELLOW;
|
|
}
|
|
auto line_str = std::to_string(line);
|
|
snippet += microfmt::format("{>{}}: ", margin_width, line_str);
|
|
if(color && line == target_line) {
|
|
snippet += RESET;
|
|
}
|
|
snippet += lines[line - original_begin] + "\n";
|
|
}
|
|
return snippet;
|
|
}
|
|
}
|
|
}
|