/** * Copyright (c) 2020, Timothy Stack * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Timothy Stack nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "files_sub_source.hh" #include "base/ansi_scrubber.hh" #include "base/humanize.hh" #include "base/humanize.network.hh" #include "base/opt_util.hh" #include "base/string_util.hh" #include "config.h" #include "lnav.hh" #include "mapbox/variant.hpp" namespace files_model { files_list_selection from_selection(vis_line_t sel_vis) { auto& fc = lnav_data.ld_active_files; int sel = (int) sel_vis; if (sel < fc.fc_name_to_errors.size()) { auto iter = fc.fc_name_to_errors.begin(); std::advance(iter, sel); return error_selection::build(sel, iter); } sel -= fc.fc_name_to_errors.size(); if (sel < fc.fc_other_files.size()) { auto iter = fc.fc_other_files.begin(); std::advance(iter, sel); return other_selection::build(sel, iter); } sel -= fc.fc_other_files.size(); if (sel < fc.fc_files.size()) { auto iter = fc.fc_files.begin(); std::advance(iter, sel); return file_selection::build(sel, iter); } return no_selection{}; } } // namespace files_model files_sub_source::files_sub_source() {} bool files_sub_source::list_input_handle_key(listview_curses& lv, int ch) { switch (ch) { case KEY_ENTER: case '\r': { auto sel = files_model::from_selection(lv.get_selection()); sel.match([](files_model::no_selection) {}, [](files_model::error_selection) {}, [](files_model::other_selection) {}, [&](files_model::file_selection& fs) { auto& lss = lnav_data.ld_log_source; auto lf = *fs.sb_iter; lss.find_data(lf) | [](auto ld) { ld->set_visibility(true); lnav_data.ld_log_source.text_filters_changed(); }; if (lf->get_format() != nullptr) { auto& log_view = lnav_data.ld_views[LNV_LOG]; lss.row_for_time(lf->front().get_timeval()) | [](auto row) { lnav_data.ld_views[LNV_LOG].set_top(row); }; ensure_view(&log_view); } else { auto& tv = lnav_data.ld_views[LNV_TEXT]; auto& tss = lnav_data.ld_text_source; tss.to_front(lf); tv.reload_data(); ensure_view(&tv); } lv.reload_data(); lnav_data.ld_mode = ln_mode_t::PAGING; }); return true; } case ' ': { auto sel = files_model::from_selection(lv.get_selection()); sel.match([](files_model::no_selection) {}, [](files_model::error_selection) {}, [](files_model::other_selection) {}, [&](files_model::file_selection& fs) { auto& lss = lnav_data.ld_log_source; auto lf = *fs.sb_iter; lss.find_data(lf) | [](auto ld) { ld->set_visibility(!ld->ld_visible); }; auto top_view = *lnav_data.ld_view_stack.top(); auto tss = top_view->get_sub_source(); if (tss != nullptr) { tss->text_filters_changed(); top_view->reload_data(); } lv.reload_data(); }); return true; } case 'n': { execute_command(lnav_data.ld_exec_context, "next-mark search"); return true; } case 'N': { execute_command(lnav_data.ld_exec_context, "prev-mark search"); return true; } case '/': { execute_command(lnav_data.ld_exec_context, "prompt search-files"); return true; } case 'X': { auto sel = files_model::from_selection(lv.get_selection()); sel.match( [](files_model::no_selection) {}, [&](files_model::error_selection& es) { auto& fc = lnav_data.ld_active_files; fc.fc_file_names.erase(es.sb_iter->first); auto name_iter = fc.fc_file_names.begin(); while (name_iter != fc.fc_file_names.end()) { if (name_iter->first == es.sb_iter->first) { name_iter = fc.fc_file_names.erase(name_iter); continue; } auto rp_opt = humanize::network::path::from_str( name_iter->first); if (rp_opt) { auto rp = *rp_opt; if (fmt::to_string(rp.home()) == es.sb_iter->first) { fc.fc_other_files.erase(name_iter->first); name_iter = fc.fc_file_names.erase(name_iter); continue; } } ++name_iter; } fc.fc_name_to_errors.erase(es.sb_iter); fc.fc_invalidate_merge = true; lv.reload_data(); }, [](files_model::other_selection) {}, [](files_model::file_selection) {}); return true; } } return false; } void files_sub_source::list_input_handle_scroll_out(listview_curses& lv) { lnav_data.ld_mode = ln_mode_t::PAGING; lnav_data.ld_filter_view.reload_data(); } size_t files_sub_source::text_line_count() { const auto& fc = lnav_data.ld_active_files; return fc.fc_name_to_errors.size() + fc.fc_other_files.size() + fc.fc_files.size(); } size_t files_sub_source::text_line_width(textview_curses& curses) { return 512; } void files_sub_source::text_value_for_line(textview_curses& tc, int line, std::string& value_out, text_sub_source::line_flags_t flags) { const auto dim = tc.get_dimensions(); const auto& fc = lnav_data.ld_active_files; auto filename_width = std::min(fc.fc_largest_path_length, std::max((size_t) 40, (size_t) dim.second - 30)); if (line < fc.fc_name_to_errors.size()) { auto iter = fc.fc_name_to_errors.begin(); std::advance(iter, line); auto path = ghc::filesystem::path(iter->first); auto fn = path.filename().string(); truncate_to(fn, filename_width); value_out = fmt::format(FMT_STRING(" {:<{}} {}"), fn, filename_width, iter->second.fei_description); return; } line -= fc.fc_name_to_errors.size(); if (line < fc.fc_other_files.size()) { auto iter = fc.fc_other_files.begin(); std::advance(iter, line); auto path = ghc::filesystem::path(iter->first); auto fn = path.string(); truncate_to(fn, filename_width); value_out = fmt::format(FMT_STRING(" {:<{}} {:14} {}"), fn, filename_width, iter->second.ofd_format, iter->second.ofd_description); return; } line -= fc.fc_other_files.size(); const auto& lf = fc.fc_files[line]; auto fn = lf->get_unique_path(); char start_time[64] = "", end_time[64] = ""; std::vector file_notes; if (lf->get_format() != nullptr) { sql_strftime(start_time, sizeof(start_time), lf->front().get_timeval()); sql_strftime(end_time, sizeof(end_time), lf->back().get_timeval()); } truncate_to(fn, filename_width); for (const auto& pair : lf->get_notes()) { file_notes.push_back(pair.second); } value_out = fmt::format(FMT_STRING(" {:<{}} {:>8} {} \u2014 {} {}"), fn, filename_width, humanize::file_size(lf->get_index_size(), humanize::alignment::columnar), start_time, end_time, fmt::join(file_notes, "; ")); this->fss_last_line_len = filename_width + 23 + strlen(start_time) + strlen(end_time); } void files_sub_source::text_attrs_for_line(textview_curses& tc, int line, string_attrs_t& value_out) { bool selected = lnav_data.ld_mode == ln_mode_t::FILES && line == tc.get_selection(); const auto& fc = lnav_data.ld_active_files; auto& vcolors = view_colors::singleton(); const auto dim = tc.get_dimensions(); auto filename_width = std::min(fc.fc_largest_path_length, std::max((size_t) 40, (size_t) dim.second - 30)); if (selected) { value_out.emplace_back(line_range{0, 1}, VC_GRAPHIC.value(ACS_RARROW)); } if (line < fc.fc_name_to_errors.size()) { if (selected) { value_out.emplace_back( line_range{0, -1}, VC_ROLE.value(role_t::VCR_DISABLED_FOCUSED)); } value_out.emplace_back( line_range{4 + (int) filename_width, -1}, VC_ROLE_FG.value(role_t::VCR_ERROR)); return; } line -= fc.fc_name_to_errors.size(); if (line < fc.fc_other_files.size()) { if (selected) { value_out.emplace_back( line_range{0, -1}, VC_ROLE.value(role_t::VCR_DISABLED_FOCUSED)); } if (line == fc.fc_other_files.size() - 1) { value_out.emplace_back(line_range{0, -1}, VC_STYLE.value(A_UNDERLINE)); } return; } line -= fc.fc_other_files.size(); if (selected) { value_out.emplace_back( line_range{0, -1}, VC_ROLE.value(role_t::VCR_FOCUSED)); } auto& lss = lnav_data.ld_log_source; auto& lf = fc.fc_files[line]; auto ld_opt = lss.find_data(lf); chtype visible = ACS_DIAMOND; if (ld_opt && !ld_opt.value()->ld_visible) { visible = ' '; } value_out.emplace_back(line_range{2, 3}, VC_GRAPHIC.value(visible)); if (visible == ACS_DIAMOND) { value_out.emplace_back(line_range{2, 3}, VC_FOREGROUND.value( vcolors.ansi_to_theme_color(COLOR_GREEN))); } auto lr = line_range{ (int) filename_width + 3 + 4, (int) filename_width + 3 + 10, }; value_out.emplace_back(lr, VC_STYLE.value(A_BOLD)); lr.lr_start = this->fss_last_line_len; lr.lr_end = -1; value_out.emplace_back(lr, VC_FOREGROUND.value( vcolors.ansi_to_theme_color(COLOR_YELLOW))); } size_t files_sub_source::text_size_for_line(textview_curses& tc, int line, text_sub_source::line_flags_t raw) { return 0; } static auto spinner_index() { auto now = ui_clock::now(); return std::chrono::duration_cast( now.time_since_epoch()) .count() / 100; } bool files_overlay_source::list_value_for_overlay(const listview_curses& lv, int y, int bottom, vis_line_t line, attr_line_t& value_out) { if (y == 0) { static const char PROG[] = "-\\|/"; constexpr size_t PROG_SIZE = sizeof(PROG) - 1; auto& fc = lnav_data.ld_active_files; auto& fc_prog = fc.fc_progress; safe::WriteAccess sp(*fc_prog); if (!sp->sp_extractions.empty()) { const auto& prog = sp->sp_extractions.front(); value_out.with_ansi_string(fmt::format( "{} Extracting " ANSI_COLOR(COLOR_CYAN) "{}" ANSI_NORM "... {:>8}/{}", PROG[spinner_index() % PROG_SIZE], prog.ep_path.filename().string(), humanize::file_size(prog.ep_out_size, humanize::alignment::none), humanize::file_size(prog.ep_total_size, humanize::alignment::none))); return true; } if (!sp->sp_tailers.empty()) { auto first_iter = sp->sp_tailers.begin(); value_out.with_ansi_string(fmt::format( "{} Connecting to " ANSI_COLOR(COLOR_CYAN) "{}" ANSI_NORM ": {}", PROG[spinner_index() % PROG_SIZE], first_iter->first, first_iter->second.tp_message)); return true; } } return false; }