#include "dirbrowserformaction.h" #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "fmtstrformatter.h" #include "keycombination.h" #include "logger.h" #include "strprintf.h" #include "utils.h" #include "view.h" namespace newsboat { DirBrowserFormAction::DirBrowserFormAction(View& vv, std::string formstr, ConfigContainer* cfg) : FormAction(vv, formstr, cfg) , file_prompt_line(f, "fileprompt") , files_list("files", FormAction::f, cfg->get_configvalue_as_int("scrolloff")) , view(vv) { } bool DirBrowserFormAction::process_operation(Operation op, const std::vector& /* args */, BindingType /*bindingType*/) { switch (op) { case OP_OPEN: { /* * whenever "ENTER" is hit, we need to distinguish two different * cases: * - the focus is in the list of directories, then we need to * set the dirname field to the currently selected entry * - the focus is in the dirname field, then the dirname * needs to be returned. */ LOG(Level::DEBUG, "DirBrowserFormAction: 'opening' item"); const std::string focus = f.get_focus(); if (focus.length() > 0) { if (focus == "files") { const auto selected_position = files_list.get_position(); const auto selection = id_at_position[selected_position]; switch (selection.filetype) { case file_system::FileType::Directory: { const int status = ::chdir(selection.name.to_locale_string().c_str()); LOG(Level::DEBUG, "DirBrowserFormAction:OP_OPEN: chdir(%s) = %i", selection.name, status); files_list.set_position(0); const auto fn = utils::getcwd(); update_title(fn); const auto fn_with_trailing_slash = fn.join(Filepath{}); set_value("filenametext", fn_with_trailing_slash.to_locale_string()); do_redraw = true; } break; case file_system::FileType::RegularFile: { const auto filename = utils::getcwd().join(selection.name); set_value("filenametext", filename.to_locale_string()); f.set_focus("filename"); } break; default: // TODO: show error message break; } } else { curs_set(0); v.pop_current_formaction(); } } } break; case OP_SWITCH_FOCUS: { LOG(Level::DEBUG, "view::dirbrowser: focusing different widget"); const std::string focus = f.get_focus(); if (focus == "files") { f.set_focus("filename"); } else { f.set_focus("files"); } break; } case OP_PREV: case OP_SK_UP: if (f.get_focus() == "files") { files_list.move_up(cfg->get_configvalue_as_bool("wrap-scroll")); } else { f.set_focus("files"); } break; case OP_NEXT: case OP_SK_DOWN: if (f.get_focus() == "files") { if (!files_list.move_down( cfg->get_configvalue_as_bool("wrap-scroll"))) { f.set_focus("filename"); } } break; case OP_SK_HOME: if (f.get_focus() == "files") { files_list.move_to_first(); } else { set_value("filenametext_pos", "0"); } break; case OP_SK_END: if (f.get_focus() == "files") { files_list.move_to_last(); } else { const std::size_t text_length = f.get("filenametext").length(); set_value("filenametext_pos", std::to_string(text_length)); } break; case OP_SK_PGUP: if (f.get_focus() == "files") { files_list.move_page_up( cfg->get_configvalue_as_bool("wrap-scroll")); } break; case OP_SK_PGDOWN: if (f.get_focus() == "files") { files_list.move_page_down( cfg->get_configvalue_as_bool("wrap-scroll")); } break; case OP_SK_HALF_PAGE_UP: if (f.get_focus() == "files") { files_list.scroll_halfpage_up(cfg->get_configvalue_as_bool("wrap-scroll")); } break; case OP_SK_HALF_PAGE_DOWN: if (f.get_focus() == "files") { files_list.scroll_halfpage_down(cfg->get_configvalue_as_bool("wrap-scroll")); } break; case OP_QUIT: LOG(Level::DEBUG, "view::dirbrowser: quitting"); curs_set(0); v.pop_current_formaction(); set_value("filenametext", ""); break; case OP_HARDQUIT: LOG(Level::DEBUG, "view::dirbrowser: hard quitting"); while (v.formaction_stack_size() > 0) { v.pop_current_formaction(); } set_value("filenametext", ""); break; default: break; } return true; } void DirBrowserFormAction::update_title(const Filepath& working_directory) { const unsigned int width = files_list.get_width(); FmtStrFormatter fmt; fmt.register_fmt('N', PROGRAM_NAME); fmt.register_fmt('V', utils::program_version()); fmt.register_fmt('f', working_directory.display()); set_title(fmt.do_format( cfg->get_configvalue("dirbrowser-title-format"), width)); } std::vector get_sorted_dirlist() { std::vector ret; const auto cwdtmp = utils::getcwd(); DIR* dirp = ::opendir(cwdtmp.to_locale_string().c_str()); if (dirp) { struct dirent* de = ::readdir(dirp); while (de) { if (strcmp(de->d_name, ".") != 0 && strcmp(de->d_name, "..") != 0) { struct stat sb; auto entry = Filepath::from_locale_string(de->d_name); const auto dpath = cwdtmp.join(entry); if (::lstat(dpath.to_locale_string().c_str(), &sb) == 0) { const auto ftype = file_system::mode_to_filetype(sb.st_mode); if (ftype == file_system::FileType::Directory) { ret.emplace_back(std::move(entry)); } } } de = ::readdir(dirp); } ::closedir(dirp); } std::sort(ret.begin(), ret.end()); if (cwdtmp != Filepath::from_locale_string("/")) { ret.emplace(ret.begin(), Filepath::from_locale_string("..")); } return ret; } void DirBrowserFormAction::prepare() { /* * prepare is always called before an operation is processed, * and if a redraw is necessary, it updates the list of files * in the current directory. */ if (do_redraw) { update_title(utils::getcwd()); id_at_position.clear(); lines.clear(); for (auto directory : get_sorted_dirlist()) { add_directory(id_at_position, directory); } auto render_line = [this](std::uint32_t line, std::uint32_t width) -> StflRichText { (void)width; return lines[line]; }; files_list.invalidate_list_content(lines.size(), render_line); do_redraw = false; } std::string focus = f.get_focus(); if (focus == "files") { curs_set(0); } else { curs_set(1); } } void DirBrowserFormAction::init() { // In dirbrowser, keyboard focus is at the input field, so user will be // unable to use alphanumeric keys to confirm or quit the dialog (e.g. they // can't quit with the default `q` bind). KeyMap* keys = view.get_keymap(); keys->set_key(OP_OPEN, KeyCombination("ENTER"), id()); keys->set_key(OP_QUIT, KeyCombination("ESC"), id()); view.set_keymap(keys); set_keymap_hints(); file_prompt_line.set_text(_("Directory: ")); const std::string save_path = cfg->get_configvalue("save-path"); LOG(Level::DEBUG, "view::dirbrowser: save-path is '%s'", save_path); const int status = ::chdir(save_path.c_str()); LOG(Level::DEBUG, "view::dirbrowser: chdir(%s) = %i", save_path, status); set_value("filenametext", save_path); // Set position to 0 and back to ensure that the text is visible draw_form(); set_value("filenametext_pos", std::to_string(save_path.length())); } std::vector DirBrowserFormAction::get_keymap_hint() const { static const std::vector hints = {{OP_QUIT, _("Cancel")}, {OP_OPEN, _("Save")} }; return hints; } void DirBrowserFormAction::add_directory( std::vector& id_at_position, Filepath dirname) { struct stat sb; if (::lstat(dirname.to_locale_string().c_str(), &sb) == 0) { const auto ftype = file_system::mode_to_filetype(sb.st_mode); const auto rwxbits = file_system::permissions_string(sb.st_mode); const auto owner = file_system::get_user_padded(sb.st_uid); const auto group = file_system::get_group_padded(sb.st_gid); std::string formatteddirname = get_formatted_dirname(dirname, sb.st_mode); std::string sizestr = strprintf::fmt( "%12" PRIi64, // `st_size` is `off_t`, which is a signed integer type of // unspecified size. We'll have to bet it's no larger than 64 // bits. static_cast(sb.st_size)); std::string line = strprintf::fmt("%c%s %s %s %s %s", file_system::filetype_to_char(ftype), rwxbits, owner, group, sizestr, formatteddirname); lines.push_back(StflRichText::from_plaintext(line)); id_at_position.push_back(file_system::FileSystemEntry{ftype, dirname}); } } std::string DirBrowserFormAction::get_formatted_dirname(const Filepath& dirname, mode_t mode) { const auto dirname_str = dirname.display(); const auto suffix = file_system::mode_suffix(mode); if (suffix.has_value()) { return strprintf::fmt("%s%c", dirname_str, suffix.value()); } else { return dirname_str; } } std::string DirBrowserFormAction::title() { return strprintf::fmt(_("Save Files - %s"), utils::getcwd()); } } // namespace newsboat