Porytiles
Loading...
Searching...
No Matches
file_highlight_printer.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cstddef>
5#include <filesystem>
6#include <fstream>
7#include <set>
8#include <string>
9#include <vector>
10
11#include "gsl/pointers"
12
16
17namespace porytiles {
18
19namespace {
20
21constexpr std::size_t base_prefix_width = 3;
22const Style highlight_style = Style::bold | Style::italic | Style::yellow;
23
24struct WindowBounds {
25 std::size_t start;
26 std::size_t end;
27 std::size_t max_digits;
28};
29
30WindowBounds
31compute_window_bounds(std::size_t min_line, std::size_t max_line, std::size_t window_size, std::size_t total_lines)
32{
33 const std::size_t half_window = (window_size - 1) / 2;
34 const std::size_t start = (min_line >= half_window) ? min_line - half_window : 0;
35 const std::size_t end = std::min(max_line + half_window + 1, total_lines);
36 const std::size_t max_digits = std::to_string(end).length();
37 return {start, end, max_digits};
38}
39
40std::size_t compute_prefix_width(std::size_t line_num_display, std::size_t max_digits)
41{
42 const std::size_t current_digits = std::to_string(line_num_display).length();
43 return base_prefix_width + (max_digits - current_digits);
44}
45
46std::string format_prefix(bool is_highlighted, std::size_t prefix_width, const TextFormatter *format)
47{
48 if (is_highlighted) {
49 const std::string arrow_with_spaces = "➞" + std::string(prefix_width - 1, ' ');
50 return format->format("{}", FormatParam{arrow_with_spaces, highlight_style});
51 }
52 return std::string(prefix_width, ' ');
53}
54
55std::string format_plain_line(const std::string &prefix, std::size_t line_num, const std::string &content)
56{
57 return prefix + std::to_string(line_num) + ": " + content;
58}
59
60std::string format_highlighted_line(
61 const std::string &prefix, std::size_t line_num, const std::string &content, const TextFormatter *format)
62{
63 const auto styled_content = format->format("{}", FormatParam{content, highlight_style});
64 return prefix + std::to_string(line_num) + ": " + styled_content;
65}
66
67std::vector<std::string> read_file_lines(const std::filesystem::path &file)
68{
69 if (!exists(file)) {
70 panic("file does not exist: " + file.string());
71 }
72
73 std::vector<std::string> lines{};
74 std::ifstream stream{file};
75 std::string line_buf{};
76 while (std::getline(stream, line_buf)) {
77 trim_line_ending(line_buf);
78 lines.push_back(line_buf);
79 }
80 return lines;
81}
82
83} // namespace
84
85FileHighlightPrinter::FileHighlightPrinter(gsl::not_null<const TextFormatter *> format) : format_{format} {}
86
87std::vector<std::string> FileHighlightPrinter::print(
88 const std::vector<std::string> &lines,
89 const std::vector<std::size_t> &line_indices_to_highlight,
90 std::size_t window_size) const
91{
92 std::vector<std::string> result;
93
94 if (lines.empty()) {
95 return result;
96 }
97
98 // Build a set for O(1) lookup and find min/max
99 std::set<std::size_t> highlight_set;
100 std::size_t min_line = std::numeric_limits<std::size_t>::max();
101 std::size_t max_line = 0;
102
103 for (const auto line_index : line_indices_to_highlight) {
104 if (line_index >= lines.size()) {
105 panic("invalid line index " + std::to_string(line_index) + ": index out of bounds");
106 }
107 highlight_set.insert(line_index);
108 min_line = std::min(min_line, line_index);
109 max_line = std::max(max_line, line_index);
110 }
111
112 if (highlight_set.empty()) {
113 return result;
114 }
115
116 const auto bounds = compute_window_bounds(min_line, max_line, window_size, lines.size());
117
118 for (std::size_t i = bounds.start; i < bounds.end; ++i) {
119 const bool is_highlighted = highlight_set.contains(i);
120 const std::size_t line_num = i + 1;
121 const std::size_t prefix_width = compute_prefix_width(line_num, bounds.max_digits);
122 const std::string prefix = format_prefix(is_highlighted, prefix_width, format_);
123
124 if (is_highlighted) {
125 result.push_back(format_highlighted_line(prefix, line_num, lines[i], format_));
126 }
127 else {
128 result.push_back(format_plain_line(prefix, line_num, lines[i]));
129 }
130 }
131
132 return result;
133}
134
135std::vector<std::string> FileHighlightPrinter::print(
136 const std::vector<std::string> &lines,
137 std::size_t line_index_to_highlight,
138 std::size_t col_to_highlight,
139 std::size_t window_size) const
140{
141 std::vector<std::string> result;
142
143 if (lines.empty()) {
144 return result;
145 }
146
147 if (line_index_to_highlight >= lines.size()) {
148 panic("invalid line index " + std::to_string(line_index_to_highlight) + ": index out of bounds");
149 }
150
151 const std::string &target_line = lines[line_index_to_highlight];
152 if (col_to_highlight >= target_line.size()) {
153 panic(
154 "invalid column index " + std::to_string(col_to_highlight) + ": index out of bounds for line " +
155 std::to_string(line_index_to_highlight));
156 }
157
158 const auto bounds =
159 compute_window_bounds(line_index_to_highlight, line_index_to_highlight, window_size, lines.size());
160
161 for (std::size_t i = bounds.start; i < bounds.end; ++i) {
162 const bool is_highlighted = (i == line_index_to_highlight);
163 const std::size_t line_num = i + 1;
164 const std::size_t prefix_width = compute_prefix_width(line_num, bounds.max_digits);
165 const std::string prefix = format_prefix(is_highlighted, prefix_width, format_);
166
167 if (is_highlighted) {
168 // Split line into three parts for column-specific styling
169 const std::string before = target_line.substr(0, col_to_highlight);
170 const std::string at_col = target_line.substr(col_to_highlight, 1);
171 const std::string after =
172 (col_to_highlight + 1 < target_line.size()) ? target_line.substr(col_to_highlight + 1) : "";
173
174 const Style col_style = highlight_style | Style::underline;
175
176 const auto formatted_before = format_->format("{}", FormatParam{before, highlight_style});
177 const auto formatted_at = format_->format("{}", FormatParam{at_col, col_style});
178 const auto formatted_after = format_->format("{}", FormatParam{after, highlight_style});
179
180 result.push_back(
181 prefix + std::to_string(line_num) + ": " + formatted_before + formatted_at + formatted_after);
182
183 // Add caret indicator line
184 const std::size_t indent = prefix_width + std::to_string(line_num).length() + 4 + col_to_highlight;
185 const std::string caret =
186 std::string(indent, ' ') + format_->format("{}", FormatParam{"^", Style::bold | Style::green});
187 result.push_back(caret);
188 }
189 else {
190 result.push_back(format_plain_line(prefix, line_num, lines[i]));
191 }
192 }
193
194 return result;
195}
196
197std::vector<std::string> FileHighlightPrinter::print(
198 const std::filesystem::path &file,
199 const std::vector<std::size_t> &line_indices_to_highlight,
200 std::size_t window_size) const
201{
202 const auto lines = read_file_lines(file);
203 return print(lines, line_indices_to_highlight, window_size);
204}
205
206std::vector<std::string> FileHighlightPrinter::print(
207 const std::filesystem::path &file,
208 std::size_t line_index_to_highlight,
209 std::size_t col_to_highlight,
210 std::size_t window_size) const
211{
212 const auto lines = read_file_lines(file);
213 return print(lines, line_index_to_highlight, col_to_highlight, window_size);
214}
215
216} // namespace porytiles
std::vector< std::string > print(const std::vector< std::string > &lines, const std::vector< std::size_t > &line_indices_to_highlight, std::size_t window_size=9) const
Prints lines with specified lines highlighted and line numbers shown.
FileHighlightPrinter(gsl::not_null< const TextFormatter * > format)
Constructs a FileHighlightPrinter with a text formatter for styling.
A text parameter with associated styling for formatted output.
Text styling class supporting formatting, foreground colors, and background colors.
static const Style underline
Underline text formatting.
static const Style italic
Italic text formatting.
static const Style yellow
Yellow foreground color.
static const Style bold
Bold text formatting.
static const Style green
Green foreground color.
virtual std::string format(const std::string &format_str, const std::vector< FormatParam > &params) const
Formats a string with styled parameters using fmtlib syntax.
std::size_t end
std::size_t start
std::size_t max_digits
void panic(const StringViewSourceLoc &s)
Unconditionally terminates the program with a panic message.
Definition panic.cpp:43
std::string & trim_line_ending(std::string &line)
Removes line ending characters from a string in-place.
Utility functions for string manipulation and formatting.