1 /**
2 * Copyright (c) 2020, Timothy Stack
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * * Redistributions of source code must retain the above copyright notice, this
10 * list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 * * Neither the name of Timothy Stack nor the names of its contributors
15 * may be used to endorse or promote products derived from this software
16 * without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include "config.h"
31
32 #include "base/humanize.hh"
33 #include "base/humanize.network.hh"
34 #include "base/string_util.hh"
35 #include "mapbox/variant.hpp"
36
37 #include "lnav.hh"
38 #include "files_sub_source.hh"
39
40 namespace files_model {
from_selection(vis_line_t sel_vis)41 files_list_selection from_selection(vis_line_t sel_vis)
42 {
43 auto &fc = lnav_data.ld_active_files;
44 int sel = (int) sel_vis;
45
46 if (sel < fc.fc_name_to_errors.size()) {
47 auto iter = fc.fc_name_to_errors.begin();
48
49 std::advance(iter, sel);
50 return error_selection::build(sel, iter);
51 }
52
53 sel -= fc.fc_name_to_errors.size();
54
55 if (sel < fc.fc_other_files.size()) {
56 auto iter = fc.fc_other_files.begin();
57
58 std::advance(iter, sel);
59 return other_selection::build(sel, iter);
60 }
61
62 sel -= fc.fc_other_files.size();
63
64 if (sel < fc.fc_files.size()) {
65 auto iter = fc.fc_files.begin();
66
67 std::advance(iter, sel);
68 return file_selection::build(sel, iter);
69 }
70
71 return no_selection{};
72 }
73 }
74
files_sub_source()75 files_sub_source::files_sub_source()
76 {
77
78 }
79
list_input_handle_key(listview_curses & lv,int ch)80 bool files_sub_source::list_input_handle_key(listview_curses &lv, int ch)
81 {
82 switch (ch) {
83 case KEY_ENTER:
84 case '\r': {
85 auto sel = files_model::from_selection(lv.get_selection());
86
87 sel.match(
88 [](files_model::no_selection) {},
89 [](files_model::error_selection) {},
90 [](files_model::other_selection) {},
91 [&](files_model::file_selection& fs) {
92 auto& lss = lnav_data.ld_log_source;
93 auto lf = *fs.sb_iter;
94
95 lss.find_data(lf) | [](auto ld) {
96 ld->set_visibility(true);
97 lnav_data.ld_log_source.text_filters_changed();
98 };
99
100 if (lf->get_format() != nullptr) {
101 auto& log_view = lnav_data.ld_views[LNV_LOG];
102 lss.row_for_time(lf->front().get_timeval()) | [](auto row) {
103 lnav_data.ld_views[LNV_LOG].set_top(row);
104 };
105 ensure_view(&log_view);
106 } else {
107 auto& tv = lnav_data.ld_views[LNV_TEXT];
108 auto& tss = lnav_data.ld_text_source;
109
110 tss.to_front(lf);
111 tv.reload_data();
112 ensure_view(&tv);
113 }
114
115 lv.reload_data();
116 lnav_data.ld_mode = LNM_PAGING;
117 }
118 );
119
120 return true;
121 }
122
123 case ' ': {
124 auto sel = files_model::from_selection(lv.get_selection());
125
126 sel.match(
127 [](files_model::no_selection) {},
128 [](files_model::error_selection) {},
129 [](files_model::other_selection) {},
130 [&](files_model::file_selection& fs) {
131 auto& lss = lnav_data.ld_log_source;
132 auto lf = *fs.sb_iter;
133
134 lss.find_data(lf) | [](auto ld) {
135 ld->set_visibility(!ld->ld_visible);
136 };
137
138 auto top_view = *lnav_data.ld_view_stack.top();
139 auto tss = top_view->get_sub_source();
140
141 if (tss != nullptr) {
142 tss->text_filters_changed();
143 top_view->reload_data();
144 }
145
146 lv.reload_data();
147 }
148 );
149 return true;
150 }
151 case 'n': {
152 execute_command(lnav_data.ld_exec_context, "next-mark search");
153 return true;
154 }
155 case 'N': {
156 execute_command(lnav_data.ld_exec_context, "prev-mark search");
157 return true;
158 }
159 case '/': {
160 execute_command(lnav_data.ld_exec_context,
161 "prompt search-files");
162 return true;
163 }
164 case 'X': {
165 auto sel = files_model::from_selection(lv.get_selection());
166
167 sel.match(
168 [](files_model::no_selection) {},
169 [&](files_model::error_selection& es) {
170 auto &fc = lnav_data.ld_active_files;
171
172 fc.fc_file_names.erase(es.sb_iter->first);
173
174 auto name_iter = fc.fc_file_names.begin();
175 while (name_iter != fc.fc_file_names.end()) {
176 if (name_iter->first == es.sb_iter->first) {
177 name_iter = fc.fc_file_names.erase(name_iter);
178 continue;
179 }
180
181 auto rp_opt = humanize::network::path::from_str(name_iter->first);
182
183 if (rp_opt) {
184 auto rp = *rp_opt;
185
186 if (fmt::format("{}", rp.home()) == es.sb_iter->first) {
187 fc.fc_other_files.erase(name_iter->first);
188 name_iter = fc.fc_file_names.erase(name_iter);
189 continue;
190 }
191 }
192 ++name_iter;
193 }
194
195 fc.fc_name_to_errors.erase(es.sb_iter);
196 fc.fc_invalidate_merge = true;
197 lv.reload_data();
198 },
199 [](files_model::other_selection) {},
200 [](files_model::file_selection) {}
201 );
202 return true;
203 }
204 }
205 return false;
206 }
207
list_input_handle_scroll_out(listview_curses & lv)208 void files_sub_source::list_input_handle_scroll_out(listview_curses &lv)
209 {
210 lnav_data.ld_mode = LNM_PAGING;
211 lnav_data.ld_filter_view.reload_data();
212 }
213
text_line_count()214 size_t files_sub_source::text_line_count()
215 {
216 const auto &fc = lnav_data.ld_active_files;
217
218 return fc.fc_name_to_errors.size() +
219 fc.fc_other_files.size() +
220 fc.fc_files.size();
221 }
222
text_line_width(textview_curses & curses)223 size_t files_sub_source::text_line_width(textview_curses &curses)
224 {
225 return 512;
226 }
227
text_value_for_line(textview_curses & tc,int line,std::string & value_out,text_sub_source::line_flags_t flags)228 void files_sub_source::text_value_for_line(textview_curses &tc, int line,
229 std::string &value_out,
230 text_sub_source::line_flags_t flags)
231 {
232 const auto dim = tc.get_dimensions();
233 const auto &fc = lnav_data.ld_active_files;
234 auto filename_width =
235 std::min(fc.fc_largest_path_length,
236 std::max((size_t) 40, (size_t) dim.second - 30));
237
238 if (line < fc.fc_name_to_errors.size()) {
239 auto iter = fc.fc_name_to_errors.begin();
240 std::advance(iter, line);
241 auto path = ghc::filesystem::path(iter->first);
242 auto fn = path.filename().string();
243
244 truncate_to(fn, filename_width);
245 value_out = fmt::format(
246 FMT_STRING(" {:<{}} {}"),
247 fn, filename_width, iter->second.fei_description);
248 return;
249 }
250
251 line -= fc.fc_name_to_errors.size();
252
253 if (line < fc.fc_other_files.size()) {
254 auto iter = fc.fc_other_files.begin();
255 std::advance(iter, line);
256 auto path = ghc::filesystem::path(iter->first);
257 auto fn = path.string();
258
259 truncate_to(fn, filename_width);
260 value_out = fmt::format(
261 FMT_STRING(" {:<{}} {:14} {}"),
262 fn, filename_width, iter->second.ofd_format,
263 iter->second.ofd_description);
264 return;
265 }
266
267 line -= fc.fc_other_files.size();
268
269 const auto &lf = fc.fc_files[line];
270 auto fn = lf->get_unique_path();
271 char start_time[64] = "", end_time[64] = "";
272 std::vector<std::string> file_notes;
273
274 if (lf->get_format() != nullptr) {
275 sql_strftime(start_time, sizeof(start_time), lf->front().get_timeval());
276 sql_strftime(end_time, sizeof(end_time), lf->back().get_timeval());
277 }
278 truncate_to(fn, filename_width);
279 for (const auto& pair : lf->get_notes()) {
280 file_notes.push_back(pair.second);
281 }
282 value_out = fmt::format(
283 FMT_STRING(" {:<{}} {:>8} {} \u2014 {} {}"),
284 fn,
285 filename_width,
286 humanize::file_size(lf->get_index_size()),
287 start_time,
288 end_time,
289 fmt::join(file_notes, "; "));
290 this->fss_last_line_len =
291 filename_width + 23 + strlen(start_time) + strlen(end_time);
292 }
293
text_attrs_for_line(textview_curses & tc,int line,string_attrs_t & value_out)294 void files_sub_source::text_attrs_for_line(textview_curses &tc, int line,
295 string_attrs_t &value_out)
296 {
297 bool selected = lnav_data.ld_mode == LNM_FILES && line == tc.get_selection();
298 const auto &fc = lnav_data.ld_active_files;
299 auto &vcolors = view_colors::singleton();
300 const auto dim = tc.get_dimensions();
301 auto filename_width =
302 std::min(fc.fc_largest_path_length,
303 std::max((size_t) 40, (size_t) dim.second - 30));
304
305 if (selected) {
306 value_out.emplace_back(line_range{0, 1}, &view_curses::VC_GRAPHIC, ACS_RARROW);
307 }
308
309 if (line < fc.fc_name_to_errors.size()) {
310 if (selected) {
311 value_out.emplace_back(line_range{0, -1},
312 &view_curses::VC_ROLE,
313 view_colors::VCR_DISABLED_FOCUSED);
314 }
315
316 value_out.emplace_back(line_range{4 + (int) filename_width, -1},
317 &view_curses::VC_ROLE_FG,
318 view_colors::VCR_ERROR);
319 return;
320 }
321 line -= fc.fc_name_to_errors.size();
322
323 if (line < fc.fc_other_files.size()) {
324 if (selected) {
325 value_out.emplace_back(line_range{0, -1},
326 &view_curses::VC_ROLE,
327 view_colors::VCR_DISABLED_FOCUSED);
328 }
329 if (line == fc.fc_other_files.size() - 1) {
330 value_out.emplace_back(line_range{0, -1},
331 &view_curses::VC_STYLE,
332 A_UNDERLINE);
333 }
334 return;
335 }
336
337 line -= fc.fc_other_files.size();
338
339 if (selected) {
340 value_out.emplace_back(line_range{0, -1},
341 &view_curses::VC_ROLE,
342 view_colors::VCR_FOCUSED);
343 }
344
345 auto& lss = lnav_data.ld_log_source;
346 auto &lf = fc.fc_files[line];
347 auto ld_opt = lss.find_data(lf);
348
349 chtype visible = ACS_DIAMOND;
350 if (ld_opt && !ld_opt.value()->ld_visible) {
351 visible = ' ';
352 }
353 value_out.emplace_back(line_range{2, 3}, &view_curses::VC_GRAPHIC, visible);
354 if (visible == ACS_DIAMOND) {
355 value_out.emplace_back(line_range{2, 3}, &view_curses::VC_FOREGROUND,
356 vcolors.ansi_to_theme_color(COLOR_GREEN));
357 }
358
359 auto lr = line_range{
360 (int) filename_width + 3 + 4,
361 (int) filename_width + 3 + 10,
362 };
363 value_out.emplace_back(lr, &view_curses::VC_STYLE, A_BOLD);
364
365 lr.lr_start = this->fss_last_line_len;
366 lr.lr_end = -1;
367 value_out.emplace_back(lr, &view_curses::VC_FOREGROUND,
368 vcolors.ansi_to_theme_color(COLOR_YELLOW));
369 }
370
text_size_for_line(textview_curses & tc,int line,text_sub_source::line_flags_t raw)371 size_t files_sub_source::text_size_for_line(textview_curses &tc, int line,
372 text_sub_source::line_flags_t raw)
373 {
374 return 0;
375 }
376
377 static
spinner_index()378 auto spinner_index()
379 {
380 auto now = ui_clock::now();
381
382 return std::chrono::duration_cast<std::chrono::milliseconds>(
383 now.time_since_epoch()).count() / 100;
384 }
385
386 bool
list_value_for_overlay(const listview_curses & lv,int y,int bottom,vis_line_t line,attr_line_t & value_out)387 files_overlay_source::list_value_for_overlay(const listview_curses &lv, int y,
388 int bottom, vis_line_t line,
389 attr_line_t &value_out)
390 {
391 if (y == 0) {
392 static const char PROG[] = "-\\|/";
393 constexpr size_t PROG_SIZE = sizeof(PROG) - 1;
394
395 auto &fc = lnav_data.ld_active_files;
396 auto &fc_prog = fc.fc_progress;
397 safe::WriteAccess<safe_scan_progress> sp(*fc_prog);
398
399 if (!sp->sp_extractions.empty()) {
400 const auto& prog = sp->sp_extractions.front();
401
402 value_out.with_ansi_string(fmt::format(
403 "{} Extracting "
404 ANSI_COLOR(COLOR_CYAN) "{}" ANSI_NORM
405 "... {:>8}/{}",
406 PROG[spinner_index() % PROG_SIZE],
407 prog.ep_path.filename().string(),
408 humanize::file_size(prog.ep_out_size),
409 humanize::file_size(prog.ep_total_size)));
410 return true;
411 }
412 if (!sp->sp_tailers.empty()) {
413 auto first_iter = sp->sp_tailers.begin();
414
415 value_out.with_ansi_string(fmt::format(
416 "{} Connecting to "
417 ANSI_COLOR(COLOR_CYAN) "{}" ANSI_NORM
418 ": {}",
419 PROG[spinner_index() % PROG_SIZE],
420 first_iter->first,
421 first_iter->second.tp_message));
422 return true;
423 }
424 }
425 return false;
426 }
427