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