├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── tests.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── bacon.toml ├── benches ├── composite.rs ├── fuzzy.rs ├── path_normalization.rs ├── shared │ ├── mod.rs │ └── names.rs └── toks.rs ├── build-all-targets.sh ├── build.rs ├── build.sh ├── features.md ├── man └── page ├── release-for-binstall.sh ├── release.sh ├── resources ├── default-conf │ ├── conf.hjson │ ├── skins │ │ ├── catppuccin-macchiato.hjson │ │ ├── catppuccin-mocha.hjson │ │ ├── dark-blue.hjson │ │ ├── dark-gruvbox.hjson │ │ ├── dark-orange.hjson │ │ ├── native-16.hjson │ │ ├── solarized-dark.hjson │ │ ├── solarized-light.hjson │ │ └── white.hjson │ └── verbs.hjson ├── icons │ ├── nerdfont │ │ ├── README.md │ │ └── data │ │ │ ├── double_extension_to_icon_name_map.rs │ │ │ ├── extension_to_icon_name_map.rs │ │ │ ├── file_name_to_icon_name_map.rs │ │ │ └── icon_name_to_icon_code_point_map.rs │ └── vscode │ │ ├── data │ │ ├── README │ │ ├── double_extension_to_icon_name_map.rs │ │ ├── extension_to_icon_name_map.rs │ │ ├── file_name_to_icon_name_map.rs │ │ └── icon_name_to_icon_code_point_map.rs │ │ └── vscode.ttf └── syntect │ ├── README.md │ └── syntaxes.bin ├── rustfmt.toml ├── src ├── app │ ├── app.rs │ ├── app_context.rs │ ├── app_state.rs │ ├── cmd_context.rs │ ├── cmd_result.rs │ ├── display_context.rs │ ├── mod.rs │ ├── mode.rs │ ├── panel.rs │ ├── panel_id.rs │ ├── panel_purpose.rs │ ├── panel_state.rs │ ├── selection.rs │ ├── standard_status.rs │ ├── state_type.rs │ └── status.rs ├── browser │ ├── browser_state.rs │ └── mod.rs ├── cli │ ├── args.rs │ ├── install_launch_args.rs │ └── mod.rs ├── command │ ├── command.rs │ ├── completion.rs │ ├── mod.rs │ ├── panel_input.rs │ ├── parts.rs │ ├── scroll.rs │ ├── sel.rs │ ├── sequence.rs │ └── trigger_type.rs ├── conf │ ├── conf.rs │ ├── default.rs │ ├── default_flags.rs │ ├── file_size.rs │ ├── format.rs │ ├── import.rs │ ├── mod.rs │ ├── special_handling_conf.rs │ └── verb_conf.rs ├── content_search │ ├── content_match.rs │ ├── content_search_result.rs │ ├── mod.rs │ └── needle.rs ├── content_type │ ├── extensions.rs │ ├── magic_numbers.rs │ └── mod.rs ├── display │ ├── areas.rs │ ├── cell_size.rs │ ├── col.rs │ ├── displayable_tree.rs │ ├── flags_display.rs │ ├── git_status_display.rs │ ├── layout_instructions.rs │ ├── luma.rs │ ├── matched_string.rs │ ├── mod.rs │ ├── num_format.rs │ ├── permissions.rs │ ├── screen.rs │ └── status_line.rs ├── errors.rs ├── file_sum │ ├── mod.rs │ └── sum_computation.rs ├── filesystems │ ├── filesystems_state.rs │ ├── mod.rs │ ├── mount_list.rs │ └── mount_space_display.rs ├── flag │ └── mod.rs ├── git │ ├── ignore.rs │ ├── mod.rs │ ├── status.rs │ └── status_computer.rs ├── help │ ├── help_content.rs │ ├── help_features.rs │ ├── help_search_modes.rs │ ├── help_state.rs │ ├── help_verbs.rs │ └── mod.rs ├── hex │ ├── byte.rs │ ├── hex_view.rs │ └── mod.rs ├── icon │ ├── font.rs │ ├── icon_plugin.rs │ └── mod.rs ├── image │ ├── double_line.rs │ ├── image_view.rs │ ├── mod.rs │ ├── source_image.rs │ └── svg.rs ├── keys.rs ├── kitty │ ├── detect_support.rs │ ├── image_renderer.rs │ └── mod.rs ├── launchable.rs ├── lib.rs ├── main.rs ├── net │ ├── client.rs │ ├── message.rs │ ├── mod.rs │ └── server.rs ├── path │ ├── anchor.rs │ ├── closest.rs │ ├── common.rs │ ├── from.rs │ ├── mod.rs │ ├── normalize.rs │ └── special_path.rs ├── pattern │ ├── candidate.rs │ ├── composite_pattern.rs │ ├── content_pattern.rs │ ├── content_regex_pattern.rs │ ├── exact_pattern.rs │ ├── fuzzy_pattern.rs │ ├── input_pattern.rs │ ├── mod.rs │ ├── name_match.rs │ ├── operator.rs │ ├── pattern.rs │ ├── pattern_object.rs │ ├── pattern_parts.rs │ ├── pos.rs │ ├── regex_pattern.rs │ ├── search_mode.rs │ └── tok_pattern.rs ├── permissions │ ├── mod.rs │ └── permissions_unix.rs ├── preview │ ├── dir_view.rs │ ├── mod.rs │ ├── preview.rs │ ├── preview_state.rs │ ├── preview_transformer.rs │ └── zero_len_file_view.rs ├── print.rs ├── shell_install │ ├── bash.rs │ ├── fish.rs │ ├── mod.rs │ ├── nushell.rs │ ├── powershell.rs │ ├── state.rs │ └── util.rs ├── skin │ ├── app_skin.rs │ ├── cli_mad_skin.rs │ ├── ext_colors.rs │ ├── help_mad_skin.rs │ ├── mod.rs │ ├── panel_skin.rs │ ├── purpose_mad_skin.rs │ ├── skin_entry.rs │ ├── status_mad_skin.rs │ └── style_map.rs ├── stage │ ├── filtered_stage.rs │ ├── mod.rs │ ├── stage.rs │ ├── stage_state.rs │ └── stage_sum.rs ├── syntactic │ ├── mod.rs │ ├── syntactic_view.rs │ ├── syntax_theme.rs │ └── syntaxer.rs ├── task_sync.rs ├── terminal.rs ├── trash │ ├── mod.rs │ ├── trash_sort.rs │ ├── trash_state.rs │ └── trash_state_cols.rs ├── tree │ ├── mod.rs │ ├── sort.rs │ ├── tree.rs │ ├── tree_line.rs │ ├── tree_line_type.rs │ └── tree_options.rs ├── tree_build │ ├── bid.rs │ ├── bline.rs │ ├── build_report.rs │ ├── builder.rs │ └── mod.rs └── verb │ ├── arg_def.rs │ ├── exec_pattern.rs │ ├── execution_builder.rs │ ├── external_execution.rs │ ├── external_execution_mode.rs │ ├── file_type_condition.rs │ ├── internal.rs │ ├── internal_execution.rs │ ├── internal_focus.rs │ ├── internal_path.rs │ ├── internal_select.rs │ ├── invocation_parser.rs │ ├── mod.rs │ ├── sequence_execution.rs │ ├── verb.rs │ ├── verb_description.rs │ ├── verb_execution.rs │ ├── verb_invocation.rs │ ├── verb_store.rs │ └── write.rs ├── tests └── search_strings.rs ├── version.sh └── website ├── README.md ├── broot_theme ├── 404.html ├── __init__.py ├── base.html ├── broot_theme.yml ├── content.html ├── css │ ├── base.css │ ├── bootstrap-custom.min.css │ ├── font-awesome.min.css │ └── github.min.css ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ ├── fontawesome-webfont.woff2 │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── img │ ├── cows.jpg │ ├── favicon.ico │ └── favicon.png ├── js │ ├── base.js │ ├── bootstrap-3.0.3.min.js │ └── jquery-1.10.2.min.js ├── keyboard-modal.html ├── main.html ├── nav-sub.html ├── search-modal.html └── toc.html ├── deploy-master.sh ├── docs ├── README.md ├── common-problems.md ├── community.md ├── conf_file.md ├── conf_verbs.md ├── css │ ├── details.css │ ├── extra.css │ ├── link-to-dystroy.css │ └── tab-langs.css ├── export.md ├── file-operations.md ├── help.md ├── icons.md ├── img │ ├── 20181215-edit.png │ ├── 20181215-only-folders-with-size.png │ ├── 20181215-overview.png │ ├── 20181215-search.png │ ├── 20181218-cd.png │ ├── 20190101-flags.png │ ├── 20190110-flags.png │ ├── 20190110-pattern_verb.png │ ├── 20190122-dcd_rulset.png │ ├── 20190128-cd.png │ ├── 20190128-edit.png │ ├── 20190128-only-folders-with-size.png │ ├── 20190128-overview.png │ ├── 20190128-search.png │ ├── 20190205-mycnf.png │ ├── 20190212-mycnf.png │ ├── 20190217-custom-colors-help.png │ ├── 20190217-custom-colors-tree.png │ ├── 20190305-dev-sizes.png │ ├── 20190305-rm.png │ ├── 20190305-search-hel.png │ ├── 20190306-blop.png │ ├── 20190306-md-c-client.png │ ├── 20190306-md.png │ ├── 20190306-mv.png │ ├── 20190321-cmd-pt-styled.png │ ├── 20190321-cmd-pt-unstyled.png │ ├── 20190607-custom-colors-sizes.png │ ├── 20190607-custom-colors-tree.png │ ├── 20190802-sizes.png │ ├── 20191030-dev-sizes.png │ ├── 20191112-cd.png │ ├── 20191112-custom-colors-tree.png │ ├── 20191112-edit.png │ ├── 20191112-md-list.png │ ├── 20191112-md-missing-subpath.png │ ├── 20191112-mv.png │ ├── 20191112-mycnf.png │ ├── 20191112-overview.png │ ├── 20191112-sizes.png │ ├── 20191114-light-skin.png │ ├── 20191214-replace-ls.png │ ├── 20200203-git.png │ ├── 20200520-ctrlp-1.png │ ├── 20200520-ctrlp-2.png │ ├── 20200520-ctrlp-3.png │ ├── 20200520-ctrlp-4.png │ ├── 20200525-colored-panels.png │ ├── 20200525-cpp.png │ ├── 20200525-custom-colors-panels.png │ ├── 20200526-3-panels.png │ ├── 20200526-input-fuzzy-mv.png │ ├── 20200526-input-fuzzy-rm.png │ ├── 20200526-input-fuzzy.png │ ├── 20200526-input-regex.png │ ├── 20200526-light-skin.png │ ├── 20200529-transparent-broot.png │ ├── 20200604-fuzzy-path.png │ ├── 20200604-input-regex.png │ ├── 20200608-content-search.png │ ├── 20200620-complex-composite.png │ ├── 20200620-composite-notrs.png │ ├── 20200620-content-memm.png │ ├── 20200620-content-search.png │ ├── 20200628-sdp.png │ ├── 20200628-whale-spotting.png │ ├── 20200629-overview.png │ ├── 20200704-sdp.png │ ├── 20200704-whale-spotting.png │ ├── 20200709-combneg-1.png │ ├── 20200709-combneg-2.png │ ├── 20200709-combneg-3.png │ ├── 20200710-alias-tree.png │ ├── 20200716-binary.png │ ├── 20200716-preview.png │ ├── 20200716-search-log.png │ ├── 20200727-search-preview.png │ ├── 2020081609-preview-binary.png │ ├── 2020081609-preview-image.png │ ├── 20201002-cr-search.png │ ├── 20201020-chmod.png │ ├── 20201020-fs.png │ ├── 20201020-whale-spotting.png │ ├── 20201127-kitty-preview.png │ ├── 20201130-sdp.png │ ├── 20201219-tree-with-args.png │ ├── 20210204-gruvbox-sdp.png │ ├── 20210204-mycnf.png │ ├── 20210222-cmd-pt-unstyled.png │ ├── 20210424-gruvbox-sdp.png │ ├── 20210424-mycnf.png │ ├── 20210424-staging-mv.png │ ├── 20210425-alias-tree.png │ ├── 20210425-help-filter-stage.png │ ├── 20210425-sdp.png │ ├── 20210425-staging-filter.png │ ├── 20210511-fuzzy-re.png │ ├── 20210511-regex.png │ ├── 20210511-twop.png │ ├── 20210603-br-w-stage-rm.png │ ├── 20210603-br-w-stage.png │ ├── 20210603-br-w.png │ ├── 20210603-chmod-perm.png │ ├── 20210603-chmod.png │ ├── 20210603-cp.png │ ├── 20210603-cpp.png │ ├── 20210603-cr.png │ ├── 20210603-e-line.png │ ├── 20210603-e.png │ ├── 20210603-md.png │ ├── 20210603-mv.png │ ├── 20210603-mvp.png │ ├── 20210603-rename.png │ ├── 20210603-rm.png │ ├── 20230129-overview.png │ ├── 20230210-extension-content.png │ ├── 20230517-fs.png │ ├── 20230526-chmod.png │ ├── 20230930-cd.png │ ├── 20230930-chmod.png │ ├── 20230930-colored-panels.png │ ├── 20230930-content-memm.png │ ├── 20230930-cpp.png │ ├── 20230930-edit.png │ ├── 20230930-fs.png │ ├── 20230930-gccran.png │ ├── 20230930-gg.png │ ├── 20230930-git.png │ ├── 20230930-mv.png │ ├── 20230930-overview.png │ ├── 20230930-preview-image.png │ ├── 20230930-preview.png │ ├── 20230930-psql.png │ ├── 20230930-sdp.png │ ├── 20230930-staging-mv.png │ ├── 20230930-whale-spotting.png │ ├── 20240225-icon-comparison.png │ ├── 20240225-nerdfont-cheatsheet.png │ ├── 20240501-sdp.png │ ├── 20240706-match-surrounding-0-0.png │ ├── 20240706-match-surrounding-1-1.png │ ├── 20240706-preview-json.png │ ├── 20240706-preview-pdf.png │ ├── 20240715-preview-odt.png │ ├── 20241011-alias-tree.png │ ├── 20241011-tree-with-args.png │ ├── 20241027-cows.png │ ├── 20241027-preview-favicon.png │ ├── br-w.png │ ├── dystroy-rust-white.svg │ ├── dystroy-rust.svg │ ├── gf.png │ ├── help-filtered.png │ ├── help-search-modes.png │ ├── help-unfiltered.png │ ├── icons.png │ ├── regex-antislash-d.png │ ├── skins │ │ ├── solarized_dark │ │ │ ├── default.png │ │ │ ├── panels.png │ │ │ ├── perms.png │ │ │ ├── search.png │ │ │ └── sizes.png │ │ └── solarized_light │ │ │ ├── fs.png │ │ │ └── search.png │ ├── sort_by_date.png │ ├── sorts.png │ ├── subpath-match-not-shown.png │ ├── subpath-match-shown.png │ ├── tree-dates.png │ ├── tree-perm.png │ ├── tree-sizes-and-counts.png │ ├── tree-sizes.png │ ├── tree_view-basic.png │ ├── vache-blanche.svg │ ├── vache.png │ └── vache.svg ├── index.md ├── input.md ├── install-br.md ├── install.md ├── js │ ├── details.js │ ├── link-to-dystroy.js │ └── tab-langs.js ├── launch.md ├── modal.md ├── navigation.md ├── panels.md ├── remote.md ├── skins.md ├── staging-area.md ├── trash.md ├── tree_view.md ├── tricks.md └── verbs.md └── mkdocs.yml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [Canop] 2 | 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bench.sh 2 | /compile.sh 3 | /termux-deploy.sh 4 | dev.log 5 | deploy.sh 6 | /*-deploy.sh 7 | /fix-win-toolchain.sh 8 | /releases 9 | /target 10 | /broot_*.zip 11 | /screens 12 | /website/site 13 | /build 14 | /trav 15 | /press 16 | /glassbench_*.db 17 | .bacon-locations 18 | .ignore 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Canop 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bacon.toml: -------------------------------------------------------------------------------- 1 | # This is a configuration file for the bacon tool 2 | # More info at https://github.com/Canop/bacon 3 | 4 | default_job = "check" 5 | env.CARGO_TERM_COLOR = "always" 6 | 7 | [jobs] 8 | 9 | [jobs.check-all] 10 | command = ["cargo", "check", "--all-targets"] 11 | need_stdout = false 12 | watch = ["tests", "benches", "examples"] 13 | 14 | [jobs.bacon-ls] 15 | command = [ "cargo", "check", "--message-format", "json-diagnostic-rendered-ansi" ] 16 | analyzer = "cargo_json" 17 | need_stdout = true 18 | 19 | [exports.cargo-json-spans] 20 | auto = true 21 | exporter = "analyzer" 22 | line_format = "{diagnostic.level}:{span.file_name}:{span.line_start}:{span.line_end}:{diagnostic.message}" 23 | path = "bacon-analyzzzer.json" 24 | 25 | [jobs.check] 26 | command = [ 27 | "cargo", "check", 28 | "--features", "clipboard kitty-csi-check trash", 29 | ] 30 | need_stdout = false 31 | watch = ["benches"] 32 | 33 | [jobs.miri] 34 | command = ["cargo", "+nightly", "miri", "run"] 35 | need_stdout = true 36 | 37 | [jobs.win] 38 | command = ["cross", "build", "--target", "x86_64-pc-windows-gnu", "--release", "--features", "clipboard"] 39 | 40 | [jobs.light] 41 | command = ["cargo", "check"] 42 | need_stdout = false 43 | 44 | [jobs.clippy] 45 | command = [ 46 | "cargo", "clippy", 47 | "--", 48 | "-A", "clippy::bool_to_int_with_if", 49 | "-A", "clippy::collapsible_else_if", 50 | "-A", "clippy::collapsible_if", 51 | "-A", "clippy::derive_partial_eq_without_eq", 52 | "-A", "clippy::if_same_then_else", 53 | "-A", "clippy::len_without_is_empty", 54 | "-A", "clippy::manual_clamp", 55 | "-A", "clippy::manual_range_contains", 56 | "-A", "clippy::manual_unwrap_or", 57 | "-A", "clippy::match_like_matches_macro", 58 | "-A", "clippy::module_inception", 59 | "-A", "clippy::needless_bool", 60 | "-A", "clippy::needless_range_loop", 61 | "-A", "clippy::neg_multiply", 62 | "-A", "clippy::vec_init_then_push", 63 | "-W", "clippy::explicit_iter_loop", 64 | "-A", "clippy::unnecessary_map_or", 65 | ] 66 | need_stdout = false 67 | 68 | [jobs.test] 69 | command = ["cargo", "test"] 70 | need_stdout = true 71 | 72 | [keybindings] 73 | a = "job:check-all" 74 | i = "job:initial" 75 | c = "job:clippy" 76 | d = "job:doc-open" 77 | t = "job:test" 78 | r = "job:run" 79 | -------------------------------------------------------------------------------- /benches/composite.rs: -------------------------------------------------------------------------------- 1 | mod shared; 2 | 3 | use { 4 | broot::{ 5 | command::CommandParts, 6 | pattern::*, 7 | }, 8 | glassbench::*, 9 | }; 10 | 11 | // this file benches composite patterns on file names so don't 12 | // use file content sub patterns here 13 | static PATTERNS: &[&str] = &[ 14 | "réveil", 15 | "r&!e", 16 | "(!e&!b)|c", 17 | ]; 18 | 19 | fn bench_score_of_composite(gb: &mut Bench) { 20 | let search_modes = SearchModeMap::default(); 21 | for pattern in PATTERNS { 22 | let name = format!("Composite({:?})::score_of", &pattern); 23 | gb.task(name, |b| { 24 | let parts = CommandParts::from(pattern.to_string()); 25 | let cp = Pattern::new(&parts.pattern, &search_modes, 10*1024*1024).unwrap(); 26 | b.iter(|| { 27 | for name in shared::NAMES { 28 | pretend_used(cp.score_of_string(name)); 29 | } 30 | }); 31 | }); 32 | } 33 | } 34 | 35 | glassbench!( 36 | "Composite Patterns", 37 | bench_score_of_composite, 38 | ); 39 | -------------------------------------------------------------------------------- /benches/fuzzy.rs: -------------------------------------------------------------------------------- 1 | mod shared; 2 | 3 | use { 4 | broot::pattern::FuzzyPattern, 5 | glassbench::*, 6 | }; 7 | 8 | static PATTERNS: &[&str] = &["réveil", "AB", "e", "brt", "brootz"]; 9 | 10 | fn bench_score_of_fuzzy(gb: &mut Bench) { 11 | for pattern in PATTERNS { 12 | let task_name = format!("Fuzzy({pattern:?})::score_of"); 13 | gb.task(task_name, |b| { 14 | let fp = FuzzyPattern::from(pattern); 15 | b.iter(|| { 16 | for name in shared::NAMES { 17 | pretend_used(fp.score_of(name)); 18 | } 19 | }); 20 | }); 21 | } 22 | } 23 | 24 | glassbench!( 25 | "Fuzzy Patterns", 26 | bench_score_of_fuzzy, 27 | ); 28 | -------------------------------------------------------------------------------- /benches/path_normalization.rs: -------------------------------------------------------------------------------- 1 | use { 2 | broot::path, 3 | glassbench::*, 4 | }; 5 | 6 | static PATHS: &[&str] = &[ 7 | "/abc/test/../thing.png", 8 | "/abc/def/../../thing.png", 9 | "/home/dys/test", 10 | "/home/dys", 11 | "/home/dys/", 12 | "/home/dys/..", 13 | "/home/dys/../", 14 | "/..", 15 | "../test", 16 | "/home/dys/../../../test", 17 | "/a/b/c/d/e/f/g/h/i/j/k/l/m/n", 18 | "/a/b/c/d/e/f/g/h/i/j/k/l/m/n/", 19 | "/", 20 | "π/2", 21 | ]; 22 | 23 | fn bench_normalization(gb: &mut Bench) { 24 | gb.task("normalize_path", |b| { 25 | b.iter(|| { 26 | for path in PATHS { 27 | pretend_used(path::normalize_path(path)); 28 | } 29 | }); 30 | }); 31 | } 32 | 33 | glassbench!( 34 | "Path Normalization", 35 | bench_normalization, 36 | ); 37 | -------------------------------------------------------------------------------- /benches/shared/mod.rs: -------------------------------------------------------------------------------- 1 | mod names; 2 | 3 | pub use names::*; 4 | -------------------------------------------------------------------------------- /benches/toks.rs: -------------------------------------------------------------------------------- 1 | mod shared; 2 | 3 | use { 4 | broot::pattern::TokPattern, 5 | glassbench::*, 6 | }; 7 | 8 | static PATTERNS: &[&str] = &["a", "réveil", "bro,c", "e,jenc,arec,ehro", "broot"]; 9 | 10 | fn bench_score_of_toks(gb: &mut Bench) { 11 | for pattern in PATTERNS { 12 | let task_name = format!("TokPattern({pattern:?})::score_of"); 13 | gb.task(task_name, |b| { 14 | let fp = TokPattern::new(pattern); 15 | b.iter(|| { 16 | for name in shared::NAMES { 17 | pretend_used(fp.score_of(name)); 18 | } 19 | }); 20 | }); 21 | } 22 | } 23 | 24 | glassbench!( 25 | "Tokens Patterns", 26 | bench_score_of_toks, 27 | ); 28 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | //! This file is executed during broot compilation. 2 | //! It builds shell completion scripts and the man page 3 | //! 4 | //! Note: to see the eprintln messages, run cargo with 5 | //! cargo -vv build --release 6 | use { 7 | clap::CommandFactory, 8 | clap_complete::{Generator, Shell}, 9 | std::{ 10 | env, 11 | ffi::OsStr, 12 | }, 13 | }; 14 | 15 | include!("src/cli/args.rs"); 16 | 17 | /// The man page built by clap-mangen is too rough to be used as is. It's only 18 | /// used as part of a manual process to update the one in /man/page 19 | /// so this generation is usually not needed 20 | pub const BUILD_MAN_PAGE: bool = false; 21 | 22 | fn write_completions_file>(generator: G, out_dir: P) { 23 | let mut args = Args::command(); 24 | for name in &["broot", "br"] { 25 | clap_complete::generate_to( 26 | generator, 27 | &mut args, 28 | (*name).to_string(), 29 | &out_dir, 30 | ).expect("clap complete generation failed"); 31 | } 32 | } 33 | 34 | /// write the shell completion scripts which will be added to 35 | /// the release archive 36 | fn build_completion_scripts() { 37 | let out_dir = env::var_os("OUT_DIR").expect("out dir not set"); 38 | write_completions_file(Shell::Bash, &out_dir); 39 | write_completions_file(Shell::Elvish, &out_dir); 40 | write_completions_file(Shell::Fish, &out_dir); 41 | write_completions_file(Shell::PowerShell, &out_dir); 42 | write_completions_file(Shell::Zsh, &out_dir); 43 | eprintln!("completion scripts generated in {out_dir:?}"); 44 | } 45 | 46 | /// generate the man page from the Clap configuration 47 | fn build_man_page() -> std::io::Result<()> { 48 | let out_dir = env::var_os("OUT_DIR").expect("out dir not set"); 49 | let out_dir = PathBuf::from(out_dir); 50 | let cmd = Args::command(); 51 | let man = clap_mangen::Man::new(cmd); 52 | let mut buffer = Vec::::default(); 53 | man.render(&mut buffer)?; 54 | let file_path = out_dir.join("broot.1"); 55 | std::fs::write(&file_path, buffer)?; 56 | eprintln!("map page generated in {file_path:?}"); 57 | Ok(()) 58 | } 59 | 60 | fn main() -> std::io::Result<()> { 61 | build_completion_scripts(); 62 | if BUILD_MAN_PAGE { 63 | build_man_page()?; 64 | } 65 | Ok(()) 66 | } 67 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | # This script compiles broot for the local system 2 | # 3 | # After compilation, broot can be found in target/release 4 | # 5 | # If you're not a developer but just want to install broot to use it, 6 | # you'll probably prefer one of the options listed at 7 | # https://dystroy.org/broot/install 8 | # 9 | # Depending on your system, it's possible one of the 'features' 10 | # won't compile for you. You may remove them (see features.md) 11 | # 12 | # The line below can be safely executed on systems which don't 13 | # support sh scripts. 14 | 15 | cargo build --release --features "trash clipboard" 16 | 17 | -------------------------------------------------------------------------------- /features.md: -------------------------------------------------------------------------------- 1 | 2 | This page defines the optional features which may be applied on compilation: 3 | 4 | * clipboard 5 | * trash 6 | * kitty-csi-check 7 | 8 | Feature gating is usually temporary: they may be removed when a technical problem is solved, when a feature becomes "mainstream", or when it's dropped because no user mentioned using it. 9 | 10 | ## The "clipboard" feature 11 | 12 | This feature allows the `:copy_path` verb which copies the currently selected path into the clipboard, as well as copy-pasting from,to,within the input. 13 | 14 | Limits: 15 | 16 | - the feature doesn't compile right now on some platforms (for example Raspberry) 17 | - on some platforms the content leaves the clipboard when you quit broot (so you must paste while broot is still running) 18 | 19 | ## The "trash" feature 20 | 21 | This feature enables commands for managing the system Trash. They are `:open_trash`, `:delete_trashed_file`, `:restore_trashed_file`, `:purge_trash`. 22 | 23 | ## The "kitty-csi-check" feature 24 | 25 | The Kitty graphics protocol allows displaying images in high resolution in broot. 26 | 27 | Most terminals don't support it, so support must be verified. 28 | 29 | Doing this with CSI escape sequences is a solution, but it involve delays and should only be enabled when this support can't be determined with [environment variables](https://dystroy.org/broot/launch/#environment-variables). 30 | 31 | Enabling this feature is thus not recommended unless you use a terminal you know support this protocol and isn't recognized by broot. If this happen, please tell me so that we can update one of the fast checks. 32 | 33 | -------------------------------------------------------------------------------- /release-for-binstall.sh: -------------------------------------------------------------------------------- 1 | version=$(./version.sh) 2 | mkdir -p releases/broot_${version} 3 | 4 | cd build 5 | # make one zip file for each architecture 6 | # cargo binstall needs that 7 | # see default format https://github.com/cargo-bins/cargo-binstall/blob/main/SUPPORT.md#defaults 8 | find . -maxdepth 1 -type d | grep -v -e "resources" -e "completion" -e "default-conf" -e '^\.$' | cut -c 3- |xargs -I {} zip -rj ../releases/broot_${version}/broot-{}-v${version}.zip {} 9 | cd - 10 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | # build a new release of broot 2 | # This isn't used for normal compilation (see https://dystroy.org/broot for instruction) 3 | # but for building the official releases 4 | version=$(./version.sh) 5 | 6 | echo "Building release $version" 7 | 8 | # make the build directory and compile for all targets 9 | ./build-all-targets.sh 10 | 11 | # add the readme and changelog in the build directory 12 | echo "This is broot. More info and installation instructions on https://dystroy.org/broot" > build/README.md 13 | cp CHANGELOG.md build 14 | 15 | # add the man page and fix its date and version 16 | cp man/page build/broot.1 17 | sed -i "s/#version/$version/g" build/broot.1 18 | sed -i "s/#date/$(date +'%Y\/%m\/%d')/g" build/broot.1 19 | 20 | # publish version number 21 | echo "$version" > build/version 22 | 23 | # prepare the release archive 24 | rm broot_*.zip 25 | cd build 26 | zip -r "../broot_$version.zip" * 27 | cd - 28 | 29 | # copy it to releases folder 30 | mkdir -p releases/broot_${version} 31 | cp "broot_$version.zip" releases/broot_${version} 32 | 33 | # create zip files for `cargo binstall broot` 34 | ./release-for-binstall.sh 35 | -------------------------------------------------------------------------------- /resources/icons/nerdfont/README.md: -------------------------------------------------------------------------------- 1 | # Nerd Fonts Icons 2 | 3 | ## Requirements 4 | 5 | [Nerd Fonts](https://github.com/ryanoasis/nerd-fonts) installed either through a patched font or available as a fallback font. 6 | 7 | ## Configuration 8 | 9 | In broot config file, set 10 | 11 | ``` 12 | icon_theme: nerdfont 13 | ``` 14 | 15 | ## Limitations 16 | 17 | These icons are limited by availability of symbols in Nerd Fonts, so this feature can only support a subset of filetypes available in `vscode` theme. 18 | 19 | ## Editing the Icon for a File: 20 | If you want to find an icon for a file: go to https://www.nerdfonts.com/cheat-sheet and search for: 21 | - a icon name like "file", which should return the multiple file icon results. Pick one you like and copy the icon code "ea7b". Copy it into the corresponding mapping prefixed with "0x" in ./data/*.rs. ( "default_file", 0xf15b ), //  22 | - a icon code like "0xf15b" without the "0x" prefix. This should return the corresponding "" icon. 23 | 24 | 25 | ## Tips on editing these files in vi 26 | 27 | 1. Open ./icon_name_to_icon_code_point_map.rs 28 | then in the same session, switch to file you want to edit 29 | use C-n and C-y in edit mode 30 | 31 | 2. This plugin currently searches for lowercase, make everything so 32 | 33 | 3. Remember to run :Tabularize over ')' and ','. The tabular Plugin 34 | 35 | 4. :'<,'>!sort 36 | 37 | 5. `cargo run` in debug mode should figure out some problems. 38 | -------------------------------------------------------------------------------- /resources/icons/vscode/data/README: -------------------------------------------------------------------------------- 1 | Tips on editing these files in vi 2 | 3 | 1. Open ./icon_name_to_icon_code_point_map.rs 4 | then in the same session, switch to file you want to edit 5 | use C-n and C-y in edit mode 6 | 7 | 2. This plugin currently searches for lowercase, make everything so 8 | 9 | 3. Remember to run :Tabularize over ')' and ',' 10 | 11 | 4. :'<,'>!sort 12 | 13 | 5. `cargo run` in debug mode should figure out if some problems. 14 | -------------------------------------------------------------------------------- /resources/icons/vscode/data/double_extension_to_icon_name_map.rs: -------------------------------------------------------------------------------- 1 | // SEE ./README on how to edit this file 2 | [ 3 | ( "gradle.kts" , "file_type_gradle" ), 4 | ( "tar.gz" , "file_type_zip" ), 5 | ( "tar.xz" , "file_type_zip" ), 6 | ( "tar.zst" , "file_type_zip" ), 7 | ] 8 | -------------------------------------------------------------------------------- /resources/icons/vscode/data/file_name_to_icon_name_map.rs: -------------------------------------------------------------------------------- 1 | [ 2 | ( ".scalafix.conf" , "file_type_config" ), 3 | ( ".scalafmt.conf" , "file_type_config" ), 4 | ( "build.properties" , "file_type_config" ), 5 | ( "eslint.config.cjs" , "file_type_eslint" ), 6 | ( "eslint.config.js" , "file_type_eslint" ), 7 | ( "eslint.config.mjs" , "file_type_eslint" ), 8 | ( "license" , "file_type_license"), 9 | ( "package-lock.json" , "file_type_npm" ), 10 | ( "package.json" , "file_type_npm" ), 11 | ( "readme" , "file_type_text" ), 12 | ( "todo" , "file_type_todo" ), 13 | ] 14 | -------------------------------------------------------------------------------- /resources/icons/vscode/vscode.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/resources/icons/vscode/vscode.ttf -------------------------------------------------------------------------------- /resources/syntect/README.md: -------------------------------------------------------------------------------- 1 | 2 | Broot uses [syntect](https://crates.io/crates/syntect) for syntax highlighting in text files. 3 | 4 | This (excellent) library needs Sublime Text syntax definitions for all languages (when a language definition isn't found, Broot displays the text monochrome). 5 | 6 | Syntect doesn't come with an extensive set of definitions. 7 | 8 | The [bat](https://github.com/sharkdp/bat) project maintains with care an important list of such definitions (most as submodules, some with patches). 9 | 10 | It's the best public list I found, so I've included the resulting syntax set here as `syntaxes.bin`. 11 | 12 | You may replace this file with your own, building it with Syntect's [`syntect::dumps::dump_to_uncompressed_file`](https://docs.rs/syntect/latest/syntect/dumps/fn.dump_to_uncompressed_file.html) function. 13 | -------------------------------------------------------------------------------- /resources/syntect/syntaxes.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/resources/syntect/syntaxes.bin -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | version = "Two" 3 | imports_layout = "Vertical" 4 | fn_params_layout = "Vertical" 5 | newline_style = "Unix" 6 | -------------------------------------------------------------------------------- /src/app/app_state.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | stage::Stage, 4 | }, 5 | std::path::PathBuf, 6 | }; 7 | 8 | 9 | /// global mutable state 10 | #[derive(Debug)] 11 | pub struct AppState { 12 | pub stage: Stage, 13 | 14 | /// the current root, updated when a panel with this concept 15 | /// becomes active or changes its root 16 | pub root: PathBuf, 17 | 18 | /// the selected path in another panel than the currently 19 | /// active one, if any 20 | pub other_panel_path: Option, 21 | } 22 | 23 | impl AppState { 24 | } 25 | -------------------------------------------------------------------------------- /src/app/cmd_context.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::*, 3 | crate::{ 4 | command::*, 5 | display::{Areas, Screen}, 6 | skin::PanelSkin, 7 | }, 8 | }; 9 | 10 | /// short lived wrapping of a few things which are needed for the handling 11 | /// of a command in a panel and won't be modified during the operation. 12 | pub struct CmdContext<'c> { 13 | pub cmd: &'c Command, 14 | pub app: &'c AppCmdContext<'c>, 15 | pub panel: PanelCmdContext<'c>, 16 | } 17 | 18 | /// the part of the immutable command execution context which comes from the app 19 | pub struct AppCmdContext<'c> { 20 | pub panel_skin: &'c PanelSkin, 21 | pub preview_panel: Option, // id of the app's preview panel 22 | pub stage_panel: Option, // id of the app's preview panel 23 | pub screen: Screen, 24 | pub con: &'c AppContext, 25 | } 26 | 27 | /// the part of the command execution context which comes from the panel 28 | pub struct PanelCmdContext<'c> { 29 | pub areas: &'c Areas, 30 | pub purpose: PanelPurpose, 31 | } 32 | -------------------------------------------------------------------------------- /src/app/display_context.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::*, 3 | crate::{ 4 | display::Screen, 5 | skin::PanelSkin, 6 | }, 7 | termimad::Area, 8 | }; 9 | 10 | /// short lived wrapping of a few things which are needed for displaying 11 | /// panels 12 | pub struct DisplayContext<'c> { 13 | pub count: usize, 14 | pub active: bool, 15 | pub screen: Screen, 16 | pub state_area: Area, 17 | pub panel_skin: &'c PanelSkin, 18 | pub app_state: &'c AppState, 19 | pub con: &'c AppContext, 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/app/mod.rs: -------------------------------------------------------------------------------- 1 | mod app; 2 | mod app_context; 3 | mod app_state; 4 | mod cmd_context; 5 | mod cmd_result; 6 | mod display_context; 7 | mod mode; 8 | mod panel; 9 | mod panel_id; 10 | mod panel_purpose; 11 | mod panel_state; 12 | mod selection; 13 | mod standard_status; 14 | mod state_type; 15 | mod status; 16 | 17 | pub use { 18 | app::App, 19 | app_context::AppContext, 20 | app_state::*, 21 | cmd_context::*, 22 | cmd_result::*, 23 | display_context::*, 24 | mode::*, 25 | panel::Panel, 26 | panel_id::PanelId, 27 | panel_purpose::PanelPurpose, 28 | panel_state::*, 29 | selection::*, 30 | standard_status::StandardStatus, 31 | state_type::PanelStateType, 32 | status::Status, 33 | }; 34 | -------------------------------------------------------------------------------- /src/app/mode.rs: -------------------------------------------------------------------------------- 1 | use { 2 | serde::Deserialize, 3 | }; 4 | 5 | /// modes are used when the application is configured to 6 | /// be "modal". If not, the only mode is the `Input` mode. 7 | #[derive(Debug, Clone, Copy, PartialEq, Deserialize)] 8 | #[serde(rename_all = "lowercase")] 9 | pub enum Mode { 10 | Input, 11 | Command, 12 | } 13 | -------------------------------------------------------------------------------- /src/app/panel_id.rs: -------------------------------------------------------------------------------- 1 | /// The unique identifiant of a panel 2 | #[derive(Debug, Clone, Copy, PartialEq)] 3 | pub struct PanelId(usize); 4 | 5 | impl From for PanelId { 6 | fn from(u: usize) -> Self { 7 | Self(u) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/app/panel_purpose.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::SelectionType, 3 | }; 4 | 5 | /// the possible special reason the panel was open 6 | #[derive(Debug, Clone, Copy)] 7 | pub enum PanelPurpose { 8 | None, 9 | ArgEdition { 10 | arg_type: SelectionType, 11 | }, 12 | Preview, 13 | } 14 | 15 | impl PanelPurpose { 16 | pub fn is_arg_edition(self) -> bool { 17 | matches!(self, PanelPurpose::ArgEdition { .. }) 18 | } 19 | pub fn is_preview(self) -> bool { 20 | matches!(self, PanelPurpose::Preview) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/state_type.rs: -------------------------------------------------------------------------------- 1 | use { 2 | serde::{Deserialize, Serialize}, 3 | }; 4 | 5 | /// one of the types of state that you could 6 | /// find in a panel today 7 | #[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)] 8 | #[serde(rename_all = "snake_case")] 9 | pub enum PanelStateType { 10 | 11 | /// filesystems 12 | Fs, 13 | 14 | /// help "screen" 15 | Help, 16 | 17 | /// preview panel, never alone on screen 18 | Preview, 19 | 20 | /// stage panel, never alone on screen 21 | Stage, 22 | 23 | /// content of the trash 24 | Trash, 25 | 26 | /// standard browsing tree 27 | Tree, 28 | } 29 | -------------------------------------------------------------------------------- /src/app/status.rs: -------------------------------------------------------------------------------- 1 | 2 | /// the status contains information written on the grey line 3 | /// near the bottom of the screen 4 | #[derive(Debug, Clone)] 5 | pub struct Status { 6 | pub message: String, // markdown 7 | pub error: bool, // is the current message an error? 8 | } 9 | 10 | impl Status { 11 | pub fn new>(message: S, error: bool) -> Status { 12 | Self { 13 | message: message.into(), 14 | error, 15 | } 16 | } 17 | 18 | pub fn from_message>(message: S) -> Status { 19 | Self { 20 | message: message.into(), 21 | error: false, 22 | } 23 | } 24 | 25 | pub fn from_error>(message: S) -> Status { 26 | Self { 27 | message: message.into(), 28 | error: true, 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/browser/mod.rs: -------------------------------------------------------------------------------- 1 | mod browser_state; 2 | 3 | pub use browser_state::BrowserState; 4 | -------------------------------------------------------------------------------- /src/cli/install_launch_args.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | errors::ProgramError, 4 | cli::{Args, CliShellInstallState}, 5 | }, 6 | std::{ 7 | env, 8 | }, 9 | }; 10 | 11 | 12 | /// launch arguments related to installation 13 | /// (not used by the application after the first step) 14 | pub struct InstallLaunchArgs { 15 | pub install: Option, // installation is required 16 | pub set_install_state: Option, // the state to set 17 | pub print_shell_function: Option, // shell function to print on stdout 18 | } 19 | impl InstallLaunchArgs { 20 | pub fn from(args: &Args) -> Result { 21 | let mut install = None; 22 | if let Ok(s) = env::var("BR_INSTALL") { 23 | if s == "yes" { 24 | install = Some(true); 25 | } else if s == "no" { 26 | install = Some(false); 27 | } else { 28 | warn!("Unexpected value of BR_INSTALL: {:?}", s); 29 | } 30 | } 31 | // the cli arguments may override the env var value 32 | if args.install { 33 | install = Some(true); 34 | } 35 | let print_shell_function = args.print_shell_function.clone(); 36 | let set_install_state = args.set_install_state; 37 | Ok(Self { 38 | install, 39 | set_install_state, 40 | print_shell_function, 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/command/mod.rs: -------------------------------------------------------------------------------- 1 | mod command; 2 | mod completion; 3 | mod panel_input; 4 | mod parts; 5 | mod sequence; 6 | mod sel; 7 | mod scroll; 8 | mod trigger_type; 9 | 10 | pub use { 11 | command::Command, 12 | completion::Completions, 13 | panel_input::PanelInput, 14 | parts::CommandParts, 15 | sequence::Sequence, 16 | sel::move_sel, 17 | scroll::ScrollCommand, 18 | trigger_type::TriggerType, 19 | }; 20 | -------------------------------------------------------------------------------- /src/command/scroll.rs: -------------------------------------------------------------------------------- 1 | 2 | #[derive(Debug, Clone, Copy)] 3 | pub enum ScrollCommand { 4 | Lines(i32), 5 | Pages(i32), 6 | } 7 | 8 | impl ScrollCommand { 9 | pub fn to_lines(self, page_height: usize) -> i32 { 10 | match self { 11 | Self::Lines(n) => n, 12 | Self::Pages(n) => n * page_height as i32, 13 | } 14 | } 15 | pub fn is_up(self) -> bool { 16 | match self { 17 | Self::Lines(n) => n < 0, 18 | Self::Pages(n) => n < 0, 19 | } 20 | } 21 | /// compute the new scroll value 22 | pub fn apply( 23 | self, 24 | scroll: usize, 25 | content_height: usize, 26 | page_height: usize, 27 | ) -> usize { 28 | (scroll as i32 + self.to_lines(page_height)) 29 | .min(content_height as i32 - page_height as i32 + 1) 30 | .max(0) as usize 31 | } 32 | pub fn is_thumb(y: u16, scrollbar: Option<(u16, u16)>) -> bool { 33 | if let Some((sctop, scbottom)) = scrollbar { 34 | if sctop <= y && y <= scbottom { 35 | return true; 36 | } 37 | } 38 | false 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/command/sel.rs: -------------------------------------------------------------------------------- 1 | 2 | /// compute a new selection index for the given list len, 3 | /// taking into account whether we should cycle or not 4 | pub fn move_sel( 5 | selection: usize, 6 | len: usize, 7 | d: i32, // displacement 8 | cycle: bool, 9 | ) -> usize { 10 | if len == 0 { 11 | return 0; 12 | } 13 | let ns = (selection as i32) + d; 14 | if ns < 0 { 15 | if cycle { 16 | len - 1 17 | } else { 18 | 0 19 | } 20 | } else if ns >= len as i32 { 21 | if cycle { 22 | 0 23 | } else { 24 | len - 1 25 | } 26 | } else { 27 | ns as usize 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/command/trigger_type.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::verb::Verb, 3 | }; 4 | 5 | /// This rather vague enum might be precised or removed. It 6 | /// serves today to characterize whether a verb execution 7 | /// comes from the input or not (in this case the input is 8 | /// consumed and cleared when the verb is executed). 9 | #[derive(Debug, Clone, Copy, PartialEq)] 10 | pub enum TriggerType<'v> { 11 | /// the verb was typed in the input and user has hit enter. 12 | Input(&'v Verb), 13 | /// probably a key shortcut 14 | Other, 15 | } 16 | -------------------------------------------------------------------------------- /src/conf/default.rs: -------------------------------------------------------------------------------- 1 | use { 2 | include_dir::{Dir, DirEntry, include_dir}, 3 | std::{ 4 | fs, 5 | io, 6 | path::Path, 7 | }, 8 | }; 9 | 10 | static DEFAULT_CONF_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/resources/default-conf"); 11 | 12 | /// Write the default configuration files in the destination directory, not 13 | /// overwriting existing ones 14 | pub fn write_default_conf_in(dir: &Path) -> Result<(), io::Error> { 15 | info!("writing default conf in {:?}", dir); 16 | if dir.exists() && !dir.is_dir() { 17 | return Err(io::Error::other( format!("{dir:?} isn't a directory"))); 18 | } 19 | let mut files = Vec::new(); 20 | find_files(&DEFAULT_CONF_DIR, &mut files); 21 | for file in files { 22 | let dest_path = dir.join(file.path()); 23 | if dest_path.exists() { 24 | warn!("not overwriting {:?}", dest_path); 25 | } else { 26 | if let Some(dir) = dest_path.parent() { 27 | if !dir.exists() { 28 | fs::create_dir_all(dir)?; 29 | } 30 | }; 31 | info!("writing file {:?}", file.path()); 32 | fs::write(dest_path, file.contents())?; 33 | } 34 | } 35 | Ok(()) 36 | } 37 | 38 | fn find_files<'d>(dir: &'d Dir<'d>, files: &mut Vec<&'d include_dir::File<'d>>) { 39 | for entry in dir.entries() { 40 | match entry { 41 | DirEntry::Dir(sub_dir) => { 42 | find_files(sub_dir, files); 43 | } 44 | DirEntry::File(file) => { 45 | files.push(file); 46 | } 47 | } 48 | } 49 | } 50 | 51 | /// Check that all the files in the default_conf directory are valid 52 | /// configuration files 53 | #[test] 54 | fn check_default_conf_files() { 55 | use crate::conf::*; 56 | let mut files = Vec::new(); 57 | find_files(&DEFAULT_CONF_DIR, &mut files); 58 | for file in files { 59 | println!("Checking {}", file.path().display()); 60 | let file_content = std::str::from_utf8(file.contents()).unwrap(); 61 | SerdeFormat::read_string::(file.path(), file_content).unwrap(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/conf/default_flags.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | cli::Args, 4 | errors::ConfError, 5 | }, 6 | clap::Parser, 7 | lazy_regex::*, 8 | }; 9 | 10 | /// parse the 'default_flags' parameter of a conf. 11 | pub fn parse_default_flags(s: &str) -> Result { 12 | let prefixed; 13 | let mut tokens: Vec<&str> = if regex_is_match!("^[a-zA-Z]+$", s) { 14 | // this covers the old syntax like `default_flags: gh` 15 | prefixed = format!("-{s}"); 16 | vec![&prefixed] 17 | } else { 18 | splitty::split_unquoted_whitespace(s).collect() 19 | }; 20 | tokens.insert(0, "broot"); 21 | Args::try_parse_from(&tokens) 22 | .map_err(|_| ConfError::InvalidDefaultFlags { 23 | flags: s.to_string() 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /src/conf/file_size.rs: -------------------------------------------------------------------------------- 1 | use::serde::{ 2 | de, 3 | Deserialize, 4 | }; 5 | 6 | pub fn parse_file_size(input: &str) -> Result { 7 | let s = input.to_lowercase(); 8 | let s = s.trim_end_matches('b'); 9 | let (s, binary) = match s.strip_suffix('i') { 10 | Some(s) => (s, true), 11 | None => (s, false), 12 | }; 13 | let cut = s.find(|c: char| !(c.is_ascii_digit() || c=='.')); 14 | let (digits, factor): (&str, u64) = match cut { 15 | Some(idx) => ( 16 | &s[..idx], 17 | match (&s[idx..], binary) { 18 | ("k", false) => 1000, 19 | ("k", true) => 1024, 20 | ("m", false) => 1000*1000, 21 | ("m", true) => 1024*1024, 22 | ("g", false) => 1000*1000*1000, 23 | ("g", true) => 1024*1024*1024, 24 | ("t", false) => 1000*1000*1000*1000, 25 | ("t", true) => 1024*1024*1024*1024, 26 | _ => { 27 | // it's not a number 28 | return Err(format!("{input:?} can't be parsed as file size")); 29 | } 30 | } 31 | ), 32 | None => (s, 1), 33 | }; 34 | match digits.parse::() { 35 | Ok(n) => Ok((n * factor as f64).ceil() as u64), 36 | _ => Err(format!("{input:?} can't be parsed as file size")) 37 | } 38 | } 39 | 40 | #[test] 41 | fn test_parse_file_size(){ 42 | assert_eq!(parse_file_size("33"), Ok(33)); 43 | assert_eq!(parse_file_size("55G"), Ok(55_000_000_000)); 44 | assert_eq!(parse_file_size("2kb"), Ok(2_000)); 45 | assert_eq!(parse_file_size("1.23kiB"), Ok(1260)); 46 | } 47 | 48 | pub fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: de::Deserializer<'de> { 49 | as Deserialize>::deserialize(d)? 50 | .map(|s| parse_file_size(&s).map_err(de::Error::custom)) 51 | .transpose() 52 | } 53 | -------------------------------------------------------------------------------- /src/conf/format.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | errors::{ConfError, ProgramError}, 4 | }, 5 | serde::de::DeserializeOwned, 6 | std::{ 7 | fs, 8 | path::Path, 9 | }, 10 | deser_hjson, 11 | toml, 12 | }; 13 | 14 | 15 | /// Formats usable for reading configuration files 16 | #[derive(Default, PartialEq, Eq, Debug, Clone, Copy)] 17 | pub enum SerdeFormat { 18 | #[default] 19 | Hjson, 20 | Toml, 21 | } 22 | 23 | pub static FORMATS: &[SerdeFormat] = &[ 24 | SerdeFormat::Hjson, 25 | SerdeFormat::Toml, 26 | ]; 27 | 28 | impl SerdeFormat { 29 | pub fn key(self) -> &'static str { 30 | match self { 31 | Self::Hjson => "hjson", 32 | Self::Toml => "toml", 33 | } 34 | } 35 | pub fn from_key(key: &str) -> Option { 36 | match key { 37 | "hjson" => Some(SerdeFormat::Hjson), 38 | "toml" => Some(SerdeFormat::Toml), 39 | _ => None, 40 | } 41 | } 42 | pub fn from_path(path: &Path) -> Result { 43 | path.extension() 44 | .and_then(|os| os.to_str()) 45 | .map(|ext| ext.to_lowercase()) 46 | .and_then(|key| Self::from_key(&key)) 47 | .ok_or_else(|| ConfError::UnknownFileExtension { path: path.to_string_lossy().to_string() }) 48 | } 49 | pub fn read_string(path: &Path, s: &str) -> Result 50 | where T: DeserializeOwned 51 | { 52 | let format = Self::from_path(path)?; 53 | match format { 54 | Self::Hjson => { 55 | deser_hjson::from_str::(s) 56 | .map_err(|e| ProgramError::ConfFile { 57 | path: path.to_string_lossy().to_string(), 58 | details: e.into(), 59 | }) 60 | } 61 | Self::Toml => { 62 | toml::from_str::(s) 63 | .map_err(|e| ProgramError::ConfFile { 64 | path: path.to_string_lossy().to_string(), 65 | details: e.into(), 66 | }) 67 | } 68 | } 69 | } 70 | pub fn read_file(path: &Path) -> Result 71 | where T: DeserializeOwned 72 | { 73 | let file_content = fs::read_to_string(path)?; 74 | Self::read_string(path, &file_content) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/conf/import.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | display::LumaCondition, 4 | }, 5 | serde::Deserialize, 6 | }; 7 | 8 | 9 | /// A file to import, with optionally a condition 10 | #[derive(Clone, Debug, Deserialize)] 11 | #[serde(untagged)] 12 | pub enum Import { 13 | Simple(String), 14 | Detailed(DetailedImport), 15 | } 16 | 17 | 18 | #[derive(Clone, Debug, Deserialize)] 19 | pub struct DetailedImport { 20 | 21 | /// a condition on terminal light 22 | pub luma: Option, 23 | 24 | /// path, either absolute or relative to the current file 25 | /// or the conf directory 26 | pub file: String, 27 | } 28 | 29 | impl Import { 30 | pub fn applies(&self) -> bool { 31 | self.luma().map_or(true, |luma| luma.is_verified()) 32 | } 33 | pub fn luma(&self) -> Option<&LumaCondition> { 34 | match self { 35 | Self::Simple(_) => None, 36 | Self::Detailed(detailed) => detailed.luma.as_ref(), 37 | } 38 | } 39 | pub fn file(&self) -> &str { 40 | match self { 41 | Self::Simple(s) => s, 42 | Self::Detailed(detailed) => &detailed.file 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/conf/verb_conf.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | app::{ 4 | PanelStateType, 5 | }, 6 | verb::*, 7 | }, 8 | serde::{Deserialize, Serialize}, 9 | }; 10 | 11 | /// A deserializable verb entry in the configuration 12 | #[derive(Default, Debug, Clone, Deserialize, Serialize)] 13 | pub struct VerbConf { 14 | 15 | pub invocation: Option, 16 | 17 | pub internal: Option, 18 | 19 | pub external: Option, 20 | 21 | pub execution: Option, 22 | 23 | pub cmd: Option, 24 | 25 | pub cmd_separator: Option, 26 | 27 | pub key: Option, 28 | 29 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 30 | pub keys: Vec, 31 | 32 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 33 | pub extensions: Vec, 34 | 35 | pub shortcut: Option, 36 | 37 | pub leave_broot: Option, 38 | 39 | pub from_shell: Option, 40 | 41 | #[serde(default, skip_serializing_if = "FileTypeCondition::is_default")] 42 | pub apply_to: FileTypeCondition, 43 | 44 | pub set_working_dir: Option, 45 | 46 | pub working_dir: Option, 47 | 48 | pub description: Option, 49 | 50 | pub auto_exec: Option, 51 | 52 | pub switch_terminal: Option, 53 | 54 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 55 | pub panels: Vec, 56 | 57 | pub refresh_after: Option, 58 | } 59 | 60 | -------------------------------------------------------------------------------- /src/content_search/content_match.rs: -------------------------------------------------------------------------------- 1 | 2 | /// a displayable representation of where 3 | /// the needle was found, with some text around 4 | #[derive(Debug, Clone)] 5 | pub struct ContentMatch { 6 | pub extract: String, 7 | pub needle_start: usize, // position in the extract, in bytes 8 | pub needle_end: usize, // length in bytes 9 | } 10 | 11 | impl ContentMatch { 12 | pub fn build( 13 | hay: &[u8], 14 | pos: usize, // position in the hay 15 | needle: &str, 16 | desired_len: usize, // max length of the extract in bytes 17 | ) -> Self { 18 | if hay.is_empty() { 19 | // this happens if you search `cr/.*` and a file starts with an empty line 20 | return Self { 21 | extract: "".to_string(), 22 | needle_start: 0, 23 | needle_end: 0, 24 | }; 25 | } 26 | let mut extract_start = pos; 27 | let mut extract_end = pos + needle.len(); // not included 28 | loop { 29 | if extract_start == 0 || extract_end - extract_start >= desired_len / 2 { 30 | break; 31 | } 32 | let c = hay[extract_start - 1]; 33 | if c < 32 { 34 | break; 35 | } 36 | extract_start -= 1; 37 | } 38 | // left trimming 39 | while (hay[extract_start] == 32) && extract_start < pos { 40 | extract_start += 1; 41 | } 42 | loop { 43 | if extract_end == hay.len() || extract_end - extract_start >= desired_len { 44 | break; 45 | } 46 | let c = hay[extract_end]; 47 | if c < 32 { 48 | break; 49 | } 50 | extract_end += 1; 51 | } 52 | // at this point we're unsure whether we start at a correct char boundary, hence 53 | // the from_utf8_lossy 54 | let extract = String::from_utf8_lossy(&hay[extract_start..extract_end]).to_string(); 55 | let needle_start = extract.find(needle).unwrap_or(0); 56 | Self { 57 | extract, 58 | needle_start, 59 | needle_end: needle_start + needle.len(), 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/content_search/content_search_result.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | /// result of a full text search 4 | #[derive(Debug, Clone, Copy, PartialEq)] 5 | pub enum ContentSearchResult { 6 | 7 | /// the needle has been found at the given pos 8 | Found { 9 | pos: usize, 10 | }, 11 | 12 | /// the needle hasn't been found 13 | NotFound, // no match 14 | 15 | /// the file wasn't searched because it's binary or too big 16 | NotSuitable, 17 | } 18 | 19 | impl ContentSearchResult { 20 | pub fn is_found(self) -> bool { 21 | matches!(self, Self::Found {..}) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/content_search/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | mod content_match; 3 | mod content_search_result; 4 | mod needle; 5 | 6 | pub use { 7 | crate::content_type::{self, extensions, magic_numbers}, 8 | content_match::ContentMatch, 9 | content_search_result::ContentSearchResult, 10 | needle::Needle, 11 | std::io::{ BufRead, BufReader}, 12 | }; 13 | 14 | use { 15 | memmap2::Mmap, 16 | std::{ 17 | fs::File, 18 | io, 19 | path::Path, 20 | }, 21 | }; 22 | 23 | pub const DEFAULT_MAX_FILE_SIZE: usize = 10 * 1024 * 1024; 24 | 25 | pub fn get_mmap>(hay_path: P) -> io::Result { 26 | let file = File::open(hay_path.as_ref())?; 27 | let hay = unsafe { Mmap::map(&file)? }; 28 | Ok(hay) 29 | } 30 | 31 | /// return the memmap to the file except if it was determined 32 | /// that the file is binary (from its extension, size, or first bytes) 33 | /// or is too big 34 | pub fn get_mmap_if_suitable>(hay_path: P, max_size: usize) -> io::Result> { 35 | if let Some(ext) = hay_path.as_ref().extension().and_then(|s| s.to_str()) { 36 | if extensions::is_known_binary(ext) { 37 | return Ok(None); 38 | } 39 | } 40 | let hay = get_mmap(&hay_path)?; 41 | if hay.len() > max_size || magic_numbers::is_known_binary(&hay) { 42 | return Ok(None); 43 | } 44 | Ok(Some(hay)) 45 | } 46 | 47 | /// return true when the file looks suitable for searching as text. 48 | /// 49 | /// If a memmap will be needed afterwards, prefer to use `get_mmap_if_not_binary` 50 | /// which optimizes testing and getting the mmap. 51 | pub fn is_path_suitable>(path: P, max_size: usize) -> bool { 52 | let path = path.as_ref(); 53 | let Ok(metadata) = path.metadata() else { 54 | return false; 55 | }; 56 | if metadata.len() > max_size as u64 { 57 | return false; 58 | } 59 | content_type::is_file_text(path).unwrap_or(false) 60 | } 61 | 62 | /// Return the 1-indexed line number for the byte at position pos 63 | pub fn line_count_at_pos>(path: P, pos: usize) -> io::Result { 64 | let mut reader = BufReader::new(File::open(path)?); 65 | let mut line = String::new(); 66 | let mut line_count = 1; 67 | let mut bytes_count = 0; 68 | while reader.read_line(&mut line)? > 0 { 69 | bytes_count += line.len(); 70 | if bytes_count >= pos { 71 | return Ok(line_count); 72 | } 73 | line_count += 1; 74 | line.clear(); 75 | } 76 | Err(io::Error::new(io::ErrorKind::UnexpectedEof, "too short".to_string())) 77 | } 78 | -------------------------------------------------------------------------------- /src/content_type/extensions.rs: -------------------------------------------------------------------------------- 1 | use { 2 | phf::{phf_set, Set}, 3 | }; 4 | 5 | /// a short list of extensions that shouldn't be searched 6 | /// by content 7 | /// 8 | /// If you feel this list should maybe be changed, contact 9 | /// me on miaou or raise an issue. 10 | static BINARY_EXTENSIONS: Set<&'static str> = phf_set! { 11 | "a", 12 | "aif", "AIF", 13 | "ap_", 14 | "apk", 15 | "bin", "BIN", 16 | "bmp", "BMP", 17 | "bzip", "BZIP", 18 | "bzip2", "BZIP2", 19 | "cab", "CAB", 20 | "class", 21 | "com", "COM", 22 | "crx", 23 | "dat", "DAT", 24 | "db", "DB", 25 | "dbf", "DBF", 26 | "deb", 27 | "doc", "DOC", 28 | "docx", "DOCX", 29 | "eps", "EPS", 30 | "exe", "EXE", 31 | "dll", "DLL", 32 | "gif", "GIF", 33 | "gz", 34 | "gzip", 35 | "ico", "ICO", 36 | "iso", "ISO", 37 | "jar", "JAR", 38 | "jpg", "JPG", 39 | "jpeg", "JPEG", 40 | "lz4", "LZ4", 41 | "mdb", "MDB", 42 | "mp3", "MP3", 43 | "mp4", "MP4", 44 | "mpa", "MPa", 45 | "mpg", "MPG", 46 | "mpeg", "MPEG", 47 | "msi", "MSI", 48 | "o", 49 | "odf", "ODF", 50 | "odp", "ODP", 51 | "ods", "ODS", 52 | "odt", "ODT", 53 | "ogg", "OGG", 54 | "pdb", 55 | "pdf", "PDF", 56 | "pkg", "PKG", 57 | "png", "PNG", 58 | "ppt", "PPT", 59 | "pptx", "PPTX", 60 | "psd", "PSD", 61 | "ps", "PS", 62 | "rar", "RAR", 63 | "rpm", "RPM", 64 | "rsrc", 65 | "rtf", 66 | "so", 67 | "tar", 68 | "tar.gz", 69 | "ttf", "TTF", 70 | "tgz", "TGZ", 71 | "xls", "XLS", 72 | "xlsx", "XLSX", 73 | "vob", "VOB", 74 | "vsd", "VSD", 75 | "vsdx", "VSDX", 76 | "war", "WAR", 77 | "wasm", 78 | "wav", "WAV", 79 | "woff", "WOFF", 80 | "woff2", "WOFF2", 81 | "zip", "ZIP", 82 | "z", "Z", 83 | }; 84 | 85 | /// tells whether the file extension is one of a file format 86 | /// which shouldn't be searched as text 87 | pub fn is_known_binary(ext: &str) -> bool { 88 | BINARY_EXTENSIONS.contains(ext) 89 | } 90 | 91 | -------------------------------------------------------------------------------- /src/content_type/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod magic_numbers; 2 | pub mod extensions; 3 | 4 | use { 5 | std::{ 6 | io, 7 | path::Path, 8 | }, 9 | }; 10 | 11 | /// Assuming the path is already checked to be to a file 12 | /// (not a link or directory), tell whether it looks like a text file 13 | pub fn is_file_text>(path: P) -> io::Result { 14 | // the current algorithm is rather crude. If needed I'll add 15 | // more, like checking the start of the file is UTF8 compatible 16 | Ok(!is_file_binary(path)?) 17 | } 18 | 19 | /// Assuming the path is already checked to be to a file 20 | /// (not a link or directory), tell whether it looks like a binary file 21 | pub fn is_file_binary>(path: P) -> io::Result { 22 | if let Some(ext) = path.as_ref().extension().and_then(|s| s.to_str()) { 23 | if extensions::is_known_binary(ext) { 24 | return Ok(true); 25 | } 26 | } 27 | magic_numbers::is_file_known_binary(path) 28 | } 29 | -------------------------------------------------------------------------------- /src/display/cell_size.rs: -------------------------------------------------------------------------------- 1 | 2 | /// find and return the size of a cell (a char location) in pixels 3 | /// as (width, height). 4 | /// Many terminals don't fill this information correctly, so an 5 | /// error is expected (it works on kitty, where I use the data 6 | /// to compute the rendering dimensions of images) 7 | #[cfg(unix)] 8 | pub fn cell_size_in_pixels() -> std::io::Result<(u32, u32)> { 9 | use { 10 | libc::{ 11 | c_ushort, 12 | ioctl, 13 | STDOUT_FILENO, 14 | TIOCGWINSZ, 15 | }, 16 | std::io, 17 | }; 18 | // see http://www.delorie.com/djgpp/doc/libc/libc_495.html 19 | #[repr(C)] 20 | struct winsize { 21 | ws_row: c_ushort, /* rows, in characters */ 22 | ws_col: c_ushort, /* columns, in characters */ 23 | ws_xpixel: c_ushort, /* horizontal size, pixels */ 24 | ws_ypixel: c_ushort, /* vertical size, pixels */ 25 | } 26 | let mut w = winsize { 27 | ws_row: 0, 28 | ws_col: 0, 29 | ws_xpixel: 0, 30 | ws_ypixel: 0, 31 | }; 32 | #[allow(clippy::useless_conversion)] 33 | let r = unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &mut w) }; 34 | if r == 0 && w.ws_xpixel > w.ws_col && w.ws_ypixel > w.ws_row { 35 | Ok(( 36 | (w.ws_xpixel / w.ws_col) as u32, 37 | (w.ws_ypixel / w.ws_row) as u32, 38 | )) 39 | } else { 40 | warn!("failed to fetch cell dimension with ioctl"); 41 | Err(io::Error::other("failed to fetch terminal dimension with ioctl")) 42 | } 43 | } 44 | 45 | #[cfg(not(unix))] 46 | pub fn cell_size_in_pixels() -> std::io::Result<(u32, u32)> { 47 | // there's probably a way but I don't know it 48 | Err(std::io::Error::new( 49 | std::io::ErrorKind::Other, 50 | "fetching cell size isn't supported on Windows", 51 | )) 52 | } 53 | -------------------------------------------------------------------------------- /src/display/flags_display.rs: -------------------------------------------------------------------------------- 1 | 2 | use { 3 | super::W, 4 | crate::{ 5 | errors::ProgramError, 6 | flag::Flag, 7 | skin::PanelSkin, 8 | }, 9 | }; 10 | 11 | /// compute the needed length for displaying the flags 12 | pub fn visible_width(flags: &[Flag]) -> u16 { 13 | let mut width = flags.len() * 2 + 1; 14 | for flag in flags { 15 | width += flag.name.len(); // we assume only ascii chars 16 | width += flag.value.len(); 17 | } 18 | width as u16 19 | } 20 | 21 | /// draw the flags 22 | pub fn write( 23 | w: &mut W, 24 | flags: &[Flag], 25 | panel_skin: &PanelSkin, 26 | ) -> Result<(), ProgramError> { 27 | for flag in flags { 28 | panel_skin.styles.flag_label.queue_str(w, format!( " {}:", flag.name))?; 29 | panel_skin.styles.flag_value.queue(w, flag.value)?; 30 | panel_skin.styles.flag_label.queue(w, ' ')?; 31 | } 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /src/display/luma.rs: -------------------------------------------------------------------------------- 1 | pub use { 2 | crokey::crossterm::tty::IsTty, 3 | once_cell::sync::Lazy, 4 | serde::Deserialize, 5 | }; 6 | 7 | #[derive(Debug, Clone, Copy, Deserialize, PartialEq)] 8 | #[serde(rename_all = "lowercase")] 9 | pub enum Luma { 10 | Light, 11 | Unknown, 12 | Dark, 13 | } 14 | 15 | /// Return the light of the terminal background, which is a value 16 | /// between 0 (black) and 1 (white). 17 | pub fn luma() -> &'static Result { 18 | static LUMA: Lazy> = Lazy::new(|| { 19 | let luma = time!(Debug, terminal_light::luma()); 20 | info!("terminal's luma: {:?}", &luma); 21 | luma 22 | }); 23 | &LUMA 24 | } 25 | 26 | impl Luma { 27 | pub fn read() -> Self { 28 | match luma() { 29 | Ok(luma) if *luma > 0.6 => Self::Light, 30 | Ok(_) => Self::Dark, 31 | _ => Self::Unknown, 32 | } 33 | } 34 | } 35 | 36 | #[derive(Clone, Debug, Deserialize)] 37 | #[serde(untagged)] 38 | pub enum LumaCondition { 39 | Simple(Luma), 40 | Array(Vec), 41 | } 42 | 43 | impl LumaCondition { 44 | pub fn is_verified(&self) -> bool { 45 | let luma = if std::io::stdout().is_tty() { 46 | Luma::read() 47 | } else { 48 | Luma::Unknown 49 | }; 50 | self.includes(luma) 51 | } 52 | pub fn includes(&self, other: Luma) -> bool { 53 | match self { 54 | Self::Simple(luma) => other == *luma, 55 | Self::Array(arr) => arr.contains(&other), 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/display/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module is where is defined whether broot 2 | //! writes on stdout, on stderr or elsewhere. It also provides helper 3 | //! structs for io. 4 | 5 | /// declare a style named `$dst` which is usually a reference to the `$src` 6 | /// skin but, in case `selected` is true, is a clone with background changed 7 | /// to the one of selected lines. 8 | #[macro_export] 9 | macro_rules! cond_bg { 10 | ($dst:ident, $self:ident, $selected:expr, $src:expr) => { 11 | let mut cloned_style; 12 | let $dst = if $selected { 13 | cloned_style = $src.clone(); 14 | if let Some(c) = $self.skin.selected_line.get_bg() { 15 | cloned_style.set_bg(c); 16 | } 17 | &cloned_style 18 | } else { 19 | &$src 20 | }; 21 | }; 22 | } 23 | 24 | mod areas; 25 | mod cell_size; 26 | mod col; 27 | mod displayable_tree; 28 | pub mod flags_display; 29 | mod git_status_display; 30 | mod layout_instructions; 31 | mod luma; 32 | mod matched_string; 33 | mod num_format; 34 | mod screen; 35 | pub mod status_line; 36 | 37 | #[cfg(not(any(target_family="windows",target_os="android")))] 38 | mod permissions; 39 | 40 | pub use { 41 | areas::Areas, 42 | col::*, 43 | cond_bg, 44 | displayable_tree::DisplayableTree, 45 | git_status_display::GitStatusDisplay, 46 | layout_instructions::*, 47 | luma::LumaCondition, 48 | matched_string::MatchedString, 49 | screen::Screen, 50 | cell_size::*, 51 | }; 52 | use { 53 | once_cell::sync::Lazy, 54 | termimad::*, 55 | }; 56 | 57 | #[cfg(not(any(target_family="windows",target_os="android")))] 58 | pub use { 59 | permissions::PermWriter, 60 | }; 61 | 62 | pub static BRANCH_FILLING: Lazy = Lazy::new(|| { Filling::from_char('─') }); 63 | 64 | /// if true then the status of a panel covers the whole width 65 | /// of the terminal (over the other panels) 66 | pub const WIDE_STATUS: bool = true; 67 | 68 | /// the type used by all GUI writing functions 69 | pub type W = std::io::BufWriter; 70 | 71 | /// return the writer used by the application 72 | pub fn writer() -> W { 73 | std::io::BufWriter::new(std::io::stderr()) 74 | } 75 | -------------------------------------------------------------------------------- /src/display/num_format.rs: -------------------------------------------------------------------------------- 1 | 2 | /// Format a number with commas as thousands separators 3 | pub fn format_count(count: usize) -> String { 4 | let mut s = count.to_string(); 5 | let l = s.len(); 6 | for i in 1..l { 7 | if i % 3 == 0 { 8 | s.insert(l-i, ','); 9 | } 10 | } 11 | s 12 | } 13 | 14 | #[test] 15 | fn test_format_count() { 16 | assert_eq!(&format_count(1), "1"); 17 | assert_eq!(&format_count(12), "12"); 18 | assert_eq!(&format_count(123), "123"); 19 | assert_eq!(&format_count(1234), "1,234"); 20 | assert_eq!(&format_count(12345), "12,345"); 21 | assert_eq!(&format_count(123456), "123,456"); 22 | assert_eq!(&format_count(1234567), "1,234,567"); 23 | assert_eq!(&format_count(12345678), "12,345,678"); 24 | assert_eq!(&format_count(1234567890), "1,234,567,890"); 25 | } 26 | -------------------------------------------------------------------------------- /src/display/screen.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::W, 3 | crate::{ 4 | app::AppContext, 5 | errors::ProgramError, 6 | skin::PanelSkin, 7 | }, 8 | crokey::crossterm::{ 9 | cursor, 10 | terminal::{Clear, ClearType}, 11 | QueueableCommand, 12 | }, 13 | termimad::Area, 14 | }; 15 | 16 | /// The dimensions of the screen 17 | #[derive(Clone, Copy)] 18 | pub struct Screen { 19 | pub width: u16, 20 | pub height: u16, 21 | } 22 | 23 | impl Screen { 24 | pub fn new(con: &AppContext) -> Result { 25 | let mut screen = Screen { 26 | width: 0, 27 | height: 0, 28 | }; 29 | screen.read_size(con)?; 30 | Ok(screen) 31 | } 32 | pub fn set_terminal_size(&mut self, w: u16, h: u16, con: &AppContext) { 33 | self.width = w; 34 | self.height = h; 35 | if let Some(h) = con.launch_args.height { 36 | self.height = h; 37 | } 38 | } 39 | pub fn read_size(&mut self, con: &AppContext) -> Result<(), ProgramError> { 40 | let (w, h) = termimad::terminal_size(); 41 | self.set_terminal_size(w, h, con); 42 | Ok(()) 43 | } 44 | /// move the cursor to x,y 45 | pub fn goto(self, w: &mut W, x: u16, y: u16) -> Result<(), ProgramError> { 46 | w.queue(cursor::MoveTo(x, y))?; 47 | Ok(()) 48 | } 49 | /// clear from the cursor to the end of line 50 | pub fn clear_line(self, w: &mut W) -> Result<(), ProgramError> { 51 | w.queue(Clear(ClearType::UntilNewLine))?; 52 | Ok(()) 53 | } 54 | /// clear the area and everything to the right. 55 | /// Should be used with parcimony as it could lead to flickering. 56 | pub fn clear_area_to_right(self, w: &mut W, area: &Area) -> Result<(), ProgramError> { 57 | for y in area.top..area.top + area.height { 58 | self.goto(w, area.left, y)?; 59 | self.clear_line(w)?; 60 | } 61 | Ok(()) 62 | } 63 | /// just clears the char at the bottom right. 64 | /// (any redraw of this position makes the whole terminal flicker on some 65 | /// terminals like win/conemu, so we draw it only once at start of the 66 | /// app) 67 | pub fn clear_bottom_right_char( 68 | &self, 69 | w: &mut W, 70 | panel_skin: &PanelSkin, 71 | ) -> Result<(), ProgramError> { 72 | self.goto(w, self.width, self.height)?; 73 | panel_skin.styles.default.queue(w, ' ')?; 74 | Ok(()) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/display/status_line.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::{Screen, W}, 3 | crate::{ 4 | app::Status, 5 | errors::ProgramError, 6 | skin::PanelSkin, 7 | }, 8 | termimad::{ 9 | minimad::{Alignment, Composite}, 10 | Area, StyledChar, 11 | }, 12 | }; 13 | 14 | /// write the whole status line (task + status) 15 | pub fn write( 16 | w: &mut W, 17 | task: Option<&str>, 18 | status: &Status, 19 | area: &Area, 20 | panel_skin: &PanelSkin, 21 | screen: Screen, 22 | ) -> Result<(), ProgramError> { 23 | let y = area.top; 24 | screen.goto(w, area.left, y)?; 25 | let mut x = area.left; 26 | if let Some(pending_task) = task { 27 | let pending_task = format!(" {pending_task}… "); 28 | x += pending_task.chars().count() as u16; 29 | panel_skin.styles.status_job.queue(w, pending_task)?; 30 | } 31 | screen.goto(w, x, y)?; 32 | let style = if status.error { 33 | &panel_skin.status_skin.error 34 | } else { 35 | &panel_skin.status_skin.normal 36 | }; 37 | style.write_inline_on(w, " ")?; 38 | let remaining_width = (area.width - (x - area.left) - 1) as usize; 39 | style.write_composite_fill( 40 | w, 41 | Composite::from_inline(&status.message), 42 | remaining_width, 43 | Alignment::Unspecified, 44 | )?; 45 | Ok(()) 46 | } 47 | 48 | /// erase the whole status line 49 | pub fn erase( 50 | w: &mut W, 51 | area: &Area, 52 | panel_skin: &PanelSkin, 53 | screen: Screen, 54 | ) -> Result<(), ProgramError> { 55 | screen.goto(w, area.left, area.top)?; 56 | let sc = StyledChar::new( 57 | panel_skin.status_skin.normal.paragraph.compound_style.clone(), 58 | ' ', 59 | ); 60 | sc.queue_repeat(w, area.width as usize)?; 61 | Ok(()) 62 | } 63 | -------------------------------------------------------------------------------- /src/filesystems/mod.rs: -------------------------------------------------------------------------------- 1 | //! The whole module is only available on unix now 2 | 3 | mod filesystems_state; 4 | mod mount_list; 5 | mod mount_space_display; 6 | 7 | pub use { 8 | filesystems_state::FilesystemState, 9 | mount_list::MountList, 10 | mount_space_display::MountSpaceDisplay, 11 | }; 12 | 13 | use { 14 | once_cell::sync::Lazy, 15 | std::sync::Mutex, 16 | }; 17 | 18 | pub static MOUNTS: Lazy> = Lazy::new(|| Mutex::new(MountList::default())); 19 | 20 | pub fn clear_cache() { 21 | let mut mount_list = MOUNTS.lock().unwrap(); 22 | mount_list.clear_cache(); 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/filesystems/mount_list.rs: -------------------------------------------------------------------------------- 1 | 2 | use { 3 | crate::{ 4 | errors::ProgramError, 5 | }, 6 | lfs_core::{ 7 | DeviceId, 8 | Mount, 9 | read_mounts, 10 | ReadOptions, 11 | }, 12 | }; 13 | 14 | #[derive(Default)] 15 | pub struct MountList { 16 | mounts: Option>, 17 | } 18 | 19 | impl MountList { 20 | pub fn clear_cache(&mut self) { 21 | self.mounts = None; 22 | } 23 | /// try to load the mounts if they aren't loaded. 24 | pub fn load(&mut self) -> Result<&Vec, ProgramError> { 25 | if self.mounts.is_none() { 26 | let mut options = ReadOptions::default(); 27 | options.remote_stats(false); 28 | match read_mounts(&options) { 29 | Ok(mut vec) => { 30 | debug!("{} mounts loaded", vec.len()); 31 | vec.sort_by_key(|m| { 32 | let size = m.stats().map_or(0, |s| s.size()); 33 | u64::MAX - size 34 | }); 35 | self.mounts = Some(vec); 36 | } 37 | Err(e) => { 38 | warn!("Failed to load mounts: {:?}", e); 39 | return Err(ProgramError::Lfs { 40 | details: e.to_string(), 41 | }); 42 | } 43 | } 44 | } 45 | Ok( 46 | // this unwrap will be fixed as soon as there's option.insert in stable 47 | self.mounts.as_ref().unwrap() 48 | ) 49 | } 50 | pub fn get_by_device_id(&self, dev: DeviceId) -> Option<&Mount> { 51 | self.mounts.as_ref() 52 | .and_then(|mounts| mounts.iter().find(|m| m.info.dev == dev)) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/flag/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | /// Right now the flag is just a vessel for display. 4 | #[derive(Clone, Copy)] 5 | pub struct Flag { 6 | pub name: &'static str, 7 | pub value: &'static str, 8 | } 9 | 10 | -------------------------------------------------------------------------------- /src/git/mod.rs: -------------------------------------------------------------------------------- 1 | mod ignore; 2 | mod status; 3 | mod status_computer; 4 | 5 | pub use { 6 | ignore::{IgnoreChain, Ignorer}, 7 | status::{LineGitStatus, LineStatusComputer, TreeGitStatus}, 8 | status_computer::{clear_status_computer_cache, get_tree_status}, 9 | }; 10 | 11 | use std::path::{Path, PathBuf}; 12 | 13 | /// return the closest parent (or self) containing a .git file 14 | pub fn closest_repo_dir(mut path: &Path) -> Option { 15 | loop { 16 | let c = path.join(".git"); 17 | if c.exists() { 18 | return Some(path.to_path_buf()); 19 | } 20 | path = match path.parent() { 21 | Some(path) => path, 22 | None => { 23 | return None; 24 | } 25 | }; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/help/help_features.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | /// find the list of optional features which are enabled 4 | pub fn list() -> Vec<(&'static str, &'static str)> { 5 | #[allow(unused_mut)] 6 | let mut features: Vec<(&'static str, &'static str)> = Vec::new(); 7 | 8 | #[cfg(not(any(target_family = "windows", target_os = "android")))] 9 | features.push(("permissions", "allow showing file mode, owner and group")); 10 | 11 | #[cfg(feature = "clipboard")] 12 | features.push(( 13 | "clipboard", 14 | ":copy_path (copying the current path), and :input_paste (pasting into the input)", 15 | )); 16 | 17 | #[cfg(feature = "trash")] 18 | features.push(( 19 | "trash", 20 | ":trash, :open_trash, :restore_trashed_file, :purge_trash, :delete_trashed_file", 21 | )); 22 | 23 | features 24 | } 25 | -------------------------------------------------------------------------------- /src/help/help_search_modes.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | app::AppContext, 4 | pattern::*, 5 | }, 6 | }; 7 | 8 | /// what should be shown for a search_mode in the help screen, after 9 | /// filtering 10 | pub struct SearchModeHelp { 11 | pub prefix: String, 12 | pub description: String, 13 | pub example: String, 14 | } 15 | 16 | /// return the rows of the "Search Modes" table in help. 17 | pub fn search_mode_help(mode: SearchMode, con: &AppContext) -> SearchModeHelp { 18 | let prefix = mode.prefix(con); 19 | let description = format!( 20 | "{} search on {}", 21 | match mode.kind() { 22 | SearchKind::Exact => "exact string", 23 | SearchKind::Fuzzy => "fuzzy", 24 | SearchKind::Regex => "regex", 25 | SearchKind::Tokens => "tokens", 26 | }, 27 | match mode.object() { 28 | SearchObject::Name => "file name", 29 | SearchObject::Path => "sub path", 30 | SearchObject::Content => "file content", 31 | }, 32 | ); 33 | let example = match mode { 34 | SearchMode::NameExact => format!("`{prefix}feat` matches *help_features.rs*"), 35 | SearchMode::NameFuzzy => format!("`{prefix}conh` matches *DefaultConf.hjson*"), 36 | SearchMode::NameRegex => format!("`{prefix}rs$` matches *build.rs*"), 37 | SearchMode::NameTokens => format!("`{prefix}fea,he` matches *HelpFeature.java*"), 38 | SearchMode::PathExact => format!("`{prefix}te\\/do` matches *website/docs*"), 39 | SearchMode::PathFuzzy => format!("`{prefix}flam` matches *src/flag/mod.rs*"), 40 | SearchMode::PathRegex => format!(r#"`{prefix}\d{{3}}.*txt` matches *dir/a123/b.txt*"#), 41 | SearchMode::PathTokens => format!("`{prefix}help,doc` matches *website/docs/help.md*"), 42 | SearchMode::ContentExact => format!("`{prefix}find(` matches a file containing *a.find(b);*"), 43 | SearchMode::ContentRegex => format!("`{prefix}find/i` matches a file containing *A::Find(b)*"), 44 | }; 45 | SearchModeHelp { 46 | prefix, 47 | description, 48 | example, 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /src/help/mod.rs: -------------------------------------------------------------------------------- 1 | mod help_content; 2 | mod help_features; 3 | mod help_search_modes; 4 | mod help_state; 5 | mod help_verbs; 6 | 7 | pub use { 8 | help_state::HelpState, 9 | help_search_modes::*, 10 | }; 11 | -------------------------------------------------------------------------------- /src/hex/byte.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | skin::StyleMap, 4 | }, 5 | termimad::CompoundStyle, 6 | }; 7 | 8 | pub enum ByteCategory { 9 | Null, 10 | AsciiGraphic, 11 | AsciiWhitespace, 12 | AsciiOther, 13 | NonAscii, 14 | } 15 | 16 | #[derive(Clone, Copy)] 17 | pub struct Byte(u8); 18 | 19 | impl From for Byte { 20 | fn from(u: u8) -> Self { 21 | Self(u) 22 | } 23 | } 24 | 25 | impl Byte { 26 | pub fn category(self) -> ByteCategory { 27 | if self.0 == 0x00 { 28 | ByteCategory::Null 29 | } else if self.0.is_ascii_graphic() { 30 | ByteCategory::AsciiGraphic 31 | } else if self.0.is_ascii_whitespace() { 32 | ByteCategory::AsciiWhitespace 33 | } else if self.0.is_ascii() { 34 | ByteCategory::AsciiOther 35 | } else { 36 | ByteCategory::NonAscii 37 | } 38 | } 39 | 40 | pub fn style(self, styles: &StyleMap) -> &CompoundStyle { 41 | match self.category() { 42 | ByteCategory::Null => &styles.hex_null, 43 | ByteCategory::AsciiGraphic => &styles.hex_ascii_graphic, 44 | ByteCategory::AsciiWhitespace => &styles.hex_ascii_whitespace, 45 | ByteCategory::AsciiOther => &styles.hex_ascii_other, 46 | ByteCategory::NonAscii => &styles.hex_non_ascii, 47 | } 48 | } 49 | 50 | pub fn as_char(self) -> char { 51 | match self.category() { 52 | ByteCategory::Null => '0', 53 | ByteCategory::AsciiGraphic => self.0 as char, 54 | ByteCategory::AsciiWhitespace if self.0 == 0x20 => ' ', 55 | ByteCategory::AsciiWhitespace => '_', 56 | ByteCategory::AsciiOther => '•', 57 | ByteCategory::NonAscii => '×', 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/hex/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | mod byte; 4 | mod hex_view; 5 | 6 | pub use hex_view::HexView; 7 | -------------------------------------------------------------------------------- /src/icon/icon_plugin.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::tree::TreeLineType, 3 | }; 4 | 5 | pub trait IconPlugin { 6 | fn get_icon( 7 | &self, 8 | tree_line_type: &TreeLineType, 9 | name: &str, 10 | double_ext: Option<&str>, 11 | ext: Option<&str>, 12 | ) -> char; 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/icon/mod.rs: -------------------------------------------------------------------------------- 1 | mod font; 2 | mod icon_plugin; 3 | 4 | use font::FontPlugin; 5 | 6 | pub use icon_plugin::IconPlugin; 7 | 8 | pub fn icon_plugin(icon_set: &str) -> Option> { 9 | match icon_set { 10 | "vscode" => Some(Box::new(FontPlugin::new( 11 | &include!("../../resources/icons/vscode/data/icon_name_to_icon_code_point_map.rs"), 12 | &include!("../../resources/icons/vscode/data/double_extension_to_icon_name_map.rs"), 13 | &include!("../../resources/icons/vscode/data/extension_to_icon_name_map.rs"), 14 | &include!("../../resources/icons/vscode/data/file_name_to_icon_name_map.rs"), 15 | ))), 16 | "nerdfont" => Some(Box::new(FontPlugin::new( 17 | &include!("../../resources/icons/nerdfont/data/icon_name_to_icon_code_point_map.rs"), 18 | &include!("../../resources/icons/nerdfont/data/double_extension_to_icon_name_map.rs"), 19 | &include!("../../resources/icons/nerdfont/data/extension_to_icon_name_map.rs"), 20 | &include!("../../resources/icons/nerdfont/data/file_name_to_icon_name_map.rs"), 21 | ))), 22 | _ => None, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/image/mod.rs: -------------------------------------------------------------------------------- 1 | mod double_line; 2 | mod image_view; 3 | mod source_image; 4 | mod svg; 5 | 6 | pub use { 7 | image_view::ImageView, 8 | source_image::SourceImage, 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /src/keys.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crokey::*, 3 | crossterm::event::{ 4 | KeyCode, 5 | KeyModifiers, 6 | }, 7 | once_cell::sync::Lazy, 8 | }; 9 | 10 | pub static KEY_FORMAT: Lazy = Lazy::new(|| { 11 | KeyCombinationFormat::default().with_lowercase_modifiers() 12 | }); 13 | 14 | pub fn is_reserved(key: KeyCombination) -> bool { 15 | key == key!(backspace) || key == key!(delete) || key == key!(esc) 16 | } 17 | 18 | /// Tell whether the key can only be used as a shortcut key if the 19 | /// modal mode is active. 20 | pub fn is_key_only_modal( 21 | key: KeyCombination, 22 | ) -> bool { 23 | matches!(key, KeyCombination { 24 | codes: OneToThree::One(KeyCode::Char(_)), 25 | modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT, 26 | }) 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate cli_log; 2 | 3 | pub mod app; 4 | pub mod browser; 5 | pub mod cli; 6 | pub mod command; 7 | pub mod conf; 8 | pub mod content_search; 9 | pub mod content_type; 10 | pub mod display; 11 | pub mod errors; 12 | pub mod file_sum; 13 | pub mod flag; 14 | pub mod git; 15 | pub mod hex; 16 | pub mod help; 17 | pub mod icon; 18 | pub mod image; 19 | pub mod keys; 20 | pub mod kitty; 21 | pub mod launchable; 22 | pub mod path; 23 | pub mod pattern; 24 | pub mod permissions; 25 | pub mod preview; 26 | pub mod print; 27 | pub mod stage; 28 | pub mod shell_install; 29 | pub mod skin; 30 | pub mod syntactic; 31 | pub mod task_sync; 32 | pub mod terminal; 33 | pub mod tree; 34 | pub mod tree_build; 35 | pub mod verb; 36 | 37 | #[cfg(unix)] 38 | pub mod filesystems; 39 | 40 | #[cfg(unix)] 41 | pub mod net; 42 | 43 | #[cfg(feature = "trash")] 44 | pub mod trash; 45 | 46 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use cli_log::*; 2 | 3 | fn main() { 4 | init_cli_log!(); 5 | debug!("env::args(): {:#?}", std::env::args().collect::>()); 6 | match broot::cli::run() { 7 | Ok(Some(launchable)) => { 8 | debug!("launching {:#?}", launchable); 9 | if let Err(e) = launchable.execute(None) { 10 | warn!("Failed to launch {:?}", &launchable); 11 | warn!("Error: {:?}", e); 12 | eprintln!("{e}"); 13 | } 14 | } 15 | Ok(None) => {} 16 | Err(e) => { 17 | // this usually happens when the passed path isn't of a directory 18 | warn!("Error: {}", e); 19 | eprintln!("{e}"); 20 | } 21 | }; 22 | log_mem(Level::Info); 23 | info!("bye"); 24 | } 25 | -------------------------------------------------------------------------------- /src/net/client.rs: -------------------------------------------------------------------------------- 1 | 2 | use { 3 | super::{ 4 | Message, 5 | }, 6 | crate::{ 7 | errors::NetError, 8 | }, 9 | std::{ 10 | io::BufReader, 11 | os::unix::net::{ 12 | UnixStream, 13 | }, 14 | }, 15 | }; 16 | 17 | pub struct Client { 18 | path: String, 19 | } 20 | 21 | impl Client { 22 | pub fn new(socket_name: &str) -> Self { 23 | Self { 24 | path: super::socket_file_path(socket_name), 25 | } 26 | } 27 | pub fn send(&self, message: &Message) -> Result<(), NetError> { 28 | debug!("try connecting {:?}", &self.path); 29 | let mut stream = UnixStream::connect(&self.path)?; 30 | message.write(&mut stream)?; 31 | if let Message::GetRoot = message { 32 | // we wait for the answer 33 | let mut br = BufReader::new(&stream); 34 | match Message::read(&mut br) { 35 | Ok(answer) => { 36 | debug!("got an answer: {:?}", &answer); 37 | if let Message::Root(root) = answer { 38 | println!("{root}"); 39 | } 40 | } 41 | Err(e) => { 42 | warn!("got no answer but error {:?}", e); 43 | } 44 | } 45 | } 46 | Ok(()) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/net/message.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | errors::NetError, 4 | command::Sequence, 5 | }, 6 | std::{ 7 | io::{ 8 | self, 9 | BufRead, 10 | Write, 11 | }, 12 | }, 13 | }; 14 | 15 | /// A message which may be sent by a client or server to the other part 16 | #[derive(Debug)] 17 | pub enum Message { 18 | Command(String), 19 | Hi, 20 | GetRoot, 21 | Root(String), 22 | Sequence(Sequence), 23 | } 24 | 25 | fn read_line(r: &mut BR) -> Result { 26 | let mut line = String::new(); 27 | r.read_line(&mut line)?; 28 | debug!("read line => {:?}", &line); 29 | while line.ends_with('\n') || line.ends_with('\r') { 30 | line.pop(); 31 | } 32 | Ok(line) 33 | } 34 | 35 | impl Message { 36 | pub fn read(r: &mut BR) -> Result { 37 | // the first line gives the type of message 38 | match read_line(r)?.as_ref() { 39 | "CMD" => Ok(Self::Command(read_line(r)?)), 40 | "GET_ROOT" => Ok(Self::GetRoot), 41 | "ROOT" => Ok(Self::Root(read_line(r)?)), 42 | "SEQ" => Ok(Self::Sequence(Sequence::new( 43 | read_line(r)?, 44 | Some(read_line(r)?), 45 | ))), 46 | _ => Err(NetError::InvalidMessage), 47 | } 48 | } 49 | pub fn write(&self, w: &mut W) -> io::Result<()> { 50 | match self { 51 | Self::Command(c) => { 52 | writeln!(w, "CMD")?; 53 | writeln!(w, "{c}") 54 | } 55 | Self::GetRoot => { 56 | writeln!(w, "GET_ROOT") 57 | } 58 | Self::Hi => { 59 | writeln!(w, "HI") 60 | } 61 | Self::Root(path) => { 62 | writeln!(w, "ROOT")?; 63 | writeln!(w, "{path}") 64 | } 65 | Self::Sequence(Sequence { separator, raw }) => { 66 | writeln!(w, "SEQ")?; 67 | writeln!(w, "{raw}")?; 68 | writeln!(w, "{separator}") 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/net/mod.rs: -------------------------------------------------------------------------------- 1 | mod client; 2 | mod message; 3 | mod server; 4 | 5 | pub use { 6 | client::Client, 7 | message::Message, 8 | server::Server, 9 | }; 10 | 11 | pub fn socket_file_path(server_name: &str) -> String { 12 | format!("/tmp/broot-server-{server_name}.sock") 13 | } 14 | -------------------------------------------------------------------------------- /src/path/anchor.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Copy)] 2 | pub enum PathAnchor { 3 | Unspecified, 4 | Parent, 5 | Directory, 6 | } 7 | 8 | -------------------------------------------------------------------------------- /src/path/closest.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | /// return the closest enclosing directory 4 | pub fn closest_dir(mut path: &Path) -> PathBuf { 5 | loop { 6 | if path.exists() && path.is_dir() { 7 | return path.to_path_buf(); 8 | } 9 | match path.parent() { 10 | Some(parent) => path = parent, 11 | None => { 12 | debug!("no existing parent"); // unexpected 13 | return path.to_path_buf(); 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/path/common.rs: -------------------------------------------------------------------------------- 1 | use { 2 | std::path::{Components, PathBuf}, 3 | }; 4 | 5 | pub fn longest_common_ancestor(paths: &[PathBuf]) -> PathBuf { 6 | match paths.len() { 7 | 0 => PathBuf::new(), // empty 8 | 1 => paths[0].clone(), 9 | _ => { 10 | let cs0 = paths[0].components(); 11 | let mut csi: Vec = paths 12 | .iter() 13 | .skip(1) 14 | .map(|p| p.components()) 15 | .collect(); 16 | let mut lca = PathBuf::new(); 17 | for component in cs0 { 18 | for cs in &mut csi { 19 | if cs.next() != Some(component) { 20 | return lca; 21 | } 22 | } 23 | lca.push(component); 24 | } 25 | lca 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/path/mod.rs: -------------------------------------------------------------------------------- 1 | mod anchor; 2 | mod common; 3 | mod closest; 4 | mod from; 5 | mod normalize; 6 | mod special_path; 7 | 8 | pub use { 9 | anchor::*, 10 | closest::*, 11 | common::*, 12 | from::*, 13 | normalize::*, 14 | special_path::*, 15 | }; 16 | -------------------------------------------------------------------------------- /src/pattern/candidate.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | tree::TreeLine, 4 | }, 5 | std::{ 6 | path::Path, 7 | }, 8 | }; 9 | 10 | /// something which can be evaluated by a pattern to produce 11 | /// either a score or a more precise match 12 | #[derive(Debug, Clone, Copy)] 13 | pub struct Candidate<'c> { 14 | 15 | /// path to the file to open if the pattern searches into files 16 | pub path: &'c Path, 17 | 18 | /// path from the current root 19 | pub subpath: &'c str, 20 | 21 | /// filename 22 | pub name: &'c str, 23 | 24 | /// whether the file is regular (ie has a searchable content) 25 | pub regular_file: bool, 26 | } 27 | 28 | impl<'c> Candidate<'c> { 29 | pub fn from(line: &'c TreeLine) -> Self { 30 | Self { 31 | path: &line.path, 32 | subpath: &line.subpath, 33 | name: &line.name, 34 | regular_file: line.is_file(), 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/pattern/content_pattern.rs: -------------------------------------------------------------------------------- 1 | 2 | use { 3 | super::*, 4 | crate::{ 5 | content_search::*, 6 | }, 7 | std::{ 8 | fmt, 9 | path::Path, 10 | }, 11 | }; 12 | 13 | /// A pattern for searching in file content 14 | #[derive(Debug, Clone)] 15 | pub struct ContentExactPattern { 16 | needle: Needle, 17 | } 18 | 19 | impl fmt::Display for ContentExactPattern { 20 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 21 | f.write_str(self.as_str()) 22 | } 23 | } 24 | 25 | impl ContentExactPattern { 26 | 27 | pub fn new(pat: &str, max_file_size: usize) -> Self { 28 | Self { needle: Needle::new(pat, max_file_size) } 29 | } 30 | 31 | pub fn as_str(&self) -> &str { 32 | self.needle.as_str() 33 | } 34 | 35 | pub fn is_empty(&self) -> bool { 36 | self.needle.is_empty() 37 | } 38 | 39 | pub fn to_regex_parts(&self) -> (String, String) { 40 | (regex::escape(self.as_str()), "".to_string()) 41 | } 42 | 43 | pub fn score_of(&self, candidate: Candidate) -> Option { 44 | if !candidate.regular_file { 45 | return None; 46 | } 47 | match self.needle.search(candidate.path) { 48 | Ok(ContentSearchResult::Found { .. }) => Some(1), 49 | Ok(ContentSearchResult::NotFound) => None, 50 | Ok(ContentSearchResult::NotSuitable) => { 51 | None 52 | } 53 | Err(e) => { 54 | debug!("error while scanning {:?} : {:?}", &candidate.path, e); 55 | None 56 | } 57 | } 58 | } 59 | 60 | /// get the line of the first match, if any 61 | pub fn get_match_line_count( 62 | &self, 63 | path: &Path, 64 | ) -> Option { 65 | if let Ok(ContentSearchResult::Found { pos }) = self.needle.search(path) { 66 | line_count_at_pos(path, pos).ok() 67 | } else { 68 | None 69 | } 70 | } 71 | 72 | pub fn get_content_match( 73 | &self, 74 | path: &Path, 75 | desired_len: usize, 76 | ) -> Option { 77 | self.needle.get_match(path, desired_len) 78 | } 79 | } 80 | 81 | -------------------------------------------------------------------------------- /src/pattern/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | mod candidate; 3 | mod composite_pattern; 4 | mod content_pattern; 5 | mod content_regex_pattern; 6 | mod exact_pattern; 7 | mod fuzzy_pattern; 8 | mod input_pattern; 9 | mod name_match; 10 | mod operator; 11 | mod pattern; 12 | mod pattern_object; 13 | mod pattern_parts; 14 | mod pos; 15 | mod regex_pattern; 16 | mod search_mode; 17 | mod tok_pattern; 18 | 19 | pub use { 20 | candidate::Candidate, 21 | composite_pattern::CompositePattern, 22 | content_pattern::ContentExactPattern, 23 | content_regex_pattern::ContentRegexPattern, 24 | exact_pattern::ExactPattern, 25 | fuzzy_pattern::FuzzyPattern, 26 | input_pattern::InputPattern, 27 | name_match::NameMatch, 28 | pattern::Pattern, 29 | pattern_object::PatternObject, 30 | pattern_parts::PatternParts, 31 | pos::*, 32 | operator::PatternOperator, 33 | regex_pattern::RegexPattern, 34 | search_mode::*, 35 | tok_pattern::*, 36 | }; 37 | 38 | use { 39 | crate::errors::PatternError, 40 | lazy_regex::regex, 41 | }; 42 | 43 | pub fn build_regex(pat: &str, flags: &str) -> Result { 44 | let mut builder = regex::RegexBuilder::new(pat); 45 | for c in flags.chars() { 46 | match c { 47 | 'i' => { 48 | builder.case_insensitive(true); 49 | } 50 | 'U' => { 51 | builder.swap_greed(true); 52 | } 53 | _ => { 54 | return Err(PatternError::UnknownRegexFlag { bad: c }); 55 | } 56 | } 57 | } 58 | Ok(builder.build()?) 59 | } 60 | -------------------------------------------------------------------------------- /src/pattern/name_match.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::Pos, 3 | smallvec::SmallVec, 4 | }; 5 | 6 | /// A NameMatch is a positive result of pattern matching inside 7 | /// a filename or subpath 8 | #[derive(Debug, Clone)] 9 | pub struct NameMatch { 10 | pub score: i32, // score of the match, guaranteed strictly positive, bigger is better 11 | pub pos: Pos, // positions of the matching chars 12 | } 13 | 14 | impl NameMatch { 15 | /// wraps any group of matching characters with match_start and match_end 16 | pub fn wrap(&self, name: &str, match_start: &str, match_end: &str) -> String { 17 | let mut result = String::new(); 18 | let mut index_in_pos = 0; 19 | let mut wrapped = false; 20 | for (idx, c) in name.chars().enumerate() { 21 | if index_in_pos < self.pos.len() && self.pos[index_in_pos] == idx { 22 | index_in_pos += 1; 23 | if !wrapped { 24 | result.push_str(match_start); 25 | wrapped = true; 26 | } 27 | } else if wrapped { 28 | result.push_str(match_end); 29 | wrapped = false; 30 | } 31 | result.push(c); 32 | } 33 | if wrapped { 34 | result.push_str(match_end); 35 | } 36 | result 37 | } 38 | // cut the name match in two parts by recomputing the pos 39 | // arrays 40 | pub fn cut_after(&mut self, chars_count: usize) -> Self { 41 | let mut tail = Self { 42 | score: self.score, 43 | pos: SmallVec::new(), 44 | }; 45 | let idx = self.pos.iter().position(|&p| p >= chars_count); 46 | if let Some(idx) = idx { 47 | for p in self.pos.drain(idx..) { 48 | tail.pos.push(p - chars_count); 49 | } 50 | } 51 | tail 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/pattern/operator.rs: -------------------------------------------------------------------------------- 1 | /// operators combining patterns 2 | #[derive(Debug, Clone, Copy, PartialEq)] 3 | pub enum PatternOperator { 4 | And, 5 | Or, 6 | Not, 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/pattern/pattern_object.rs: -------------------------------------------------------------------------------- 1 | use { 2 | std::ops, 3 | }; 4 | 5 | /// on what the search applies 6 | /// (a composite pattern may apply to several topic 7 | /// hence the bools) 8 | #[derive(Debug, Clone, Copy, PartialEq, Default)] 9 | pub struct PatternObject { 10 | pub name: bool, 11 | pub subpath: bool, 12 | pub content: bool, 13 | } 14 | 15 | impl ops::BitOr for PatternObject { 16 | type Output = Self; 17 | fn bitor(self, o: Self) -> Self::Output { 18 | Self { 19 | name: self.name | o.name, 20 | subpath: self.subpath | o.subpath, 21 | content: self.content | o.content, 22 | } 23 | } 24 | } 25 | 26 | impl ops::BitOrAssign for PatternObject { 27 | fn bitor_assign(&mut self, rhs: Self) { 28 | self.name |= rhs.name; 29 | self.subpath |= rhs.subpath; 30 | self.content |= rhs.content; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/pattern/pattern_parts.rs: -------------------------------------------------------------------------------- 1 | use { 2 | std::fmt, 3 | }; 4 | 5 | /// An intermediate parsed representation of the raw string making 6 | /// a pattern, with up to 3 parts (search mode, core pattern, modifiers) 7 | #[derive(Debug, Clone, PartialEq)] 8 | pub struct PatternParts { 9 | /// can't be empty by construct 10 | parts: Vec, 11 | } 12 | 13 | impl fmt::Display for PatternParts { 14 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 15 | match self.parts.len() { 16 | 1 => write!(f, "{}", &self.parts[0]), 17 | 2 => write!(f, "{}/{}", &self.parts[0], &self.parts[1]), 18 | _ => write!(f, "{}/{}/{}", &self.parts[0], &self.parts[1], &self.parts[2]), 19 | } 20 | } 21 | } 22 | 23 | impl Default for PatternParts { 24 | fn default() -> Self { 25 | Self { 26 | parts: vec![String::new()], 27 | } 28 | } 29 | } 30 | 31 | #[cfg(test)] 32 | impl TryFrom<&[&str]> for PatternParts { 33 | type Error = &'static str; 34 | fn try_from(a: &[&str]) -> Result { 35 | if a.is_empty() { 36 | return Err("invalid empty parts array"); 37 | } 38 | let parts = a.iter().map(|s| (*s).into()).collect(); 39 | Ok(Self { parts }) 40 | } 41 | } 42 | 43 | impl PatternParts { 44 | pub fn push(&mut self, c: char) { 45 | // self.parts can't be empty, by construct 46 | self.parts.last_mut().unwrap().push(c); 47 | } 48 | pub fn is_between_slashes(&self) -> bool { 49 | self.parts.len() == 2 50 | } 51 | pub fn add_part(&mut self) { 52 | self.parts.push(String::new()); 53 | } 54 | pub fn is_empty(&self) -> bool { 55 | self.core().is_empty() 56 | } 57 | pub fn core(&self) -> &str { 58 | if self.parts.len() > 1 { 59 | &self.parts[1] 60 | } else { 61 | &self.parts[0] 62 | } 63 | } 64 | pub fn mode(&self) -> Option<&String> { 65 | if self.parts.len() > 1 { 66 | self.parts.first() 67 | } else { 68 | None 69 | } 70 | } 71 | pub fn flags(&self) -> Option<&str> { 72 | if self.parts.len() > 2 { 73 | self.parts.get(2).map(|s| s.as_str()) 74 | } else { 75 | None 76 | } 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /src/pattern/pos.rs: -------------------------------------------------------------------------------- 1 | use { 2 | smallvec::SmallVec, 3 | }; 4 | 5 | /// a vector of indexes of the matching characters (not bytes) 6 | pub type Pos = SmallVec<[usize; 8]>; 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/pattern/regex_pattern.rs: -------------------------------------------------------------------------------- 1 | //! a filtering pattern using a regular expression 2 | 3 | use { 4 | super::NameMatch, 5 | crate::errors::PatternError, 6 | lazy_regex::regex, 7 | smallvec::SmallVec, 8 | std::fmt, 9 | }; 10 | 11 | #[derive(Debug, Clone)] 12 | pub struct RegexPattern { 13 | rex: regex::Regex, 14 | flags: String, 15 | } 16 | 17 | impl fmt::Display for RegexPattern { 18 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 19 | if self.flags.is_empty() { 20 | write!(f, "/{}", self.rex) 21 | } else { 22 | write!(f, "/{}/{}", self.rex, self.flags) 23 | } 24 | } 25 | } 26 | 27 | impl RegexPattern { 28 | pub fn from(pat: &str, flags: &str) -> Result { 29 | Ok(RegexPattern { 30 | rex: super::build_regex(pat, flags)?, 31 | flags: flags.to_string(), 32 | }) 33 | } 34 | /// return a match if the pattern can be found in the candidate string 35 | pub fn find(&self, candidate: &str) -> Option { 36 | // note that there's no significative cost related to using 37 | // find over is_match 38 | self.rex.find(candidate).map(|rm| { 39 | let chars_before = candidate[..rm.start()].chars().count(); 40 | let rm_chars = rm.as_str().chars().count(); 41 | let mut pos = SmallVec::with_capacity(rm_chars); 42 | for i in 0..rm_chars { 43 | pos.push(chars_before + i); 44 | } 45 | super::NameMatch { score: 1, pos } 46 | }) 47 | } 48 | pub fn is_empty(&self) -> bool { 49 | self.rex.as_str().is_empty() 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/permissions/mod.rs: -------------------------------------------------------------------------------- 1 | //////////////////// UNIX 2 | 3 | #[cfg(not(any(target_family = "windows", target_os = "android")))] 4 | pub mod permissions_unix; 5 | 6 | #[cfg(not(any(target_family = "windows", target_os = "android")))] 7 | pub use permissions_unix::*; 8 | 9 | //////////////////// WINDOWS 10 | 11 | #[cfg(windows)] 12 | pub fn supported() -> bool { 13 | false 14 | } 15 | -------------------------------------------------------------------------------- /src/permissions/permissions_unix.rs: -------------------------------------------------------------------------------- 1 | use { 2 | rustc_hash::FxHashMap, 3 | once_cell::sync::Lazy, 4 | std::sync::Mutex, 5 | }; 6 | 7 | pub fn supported() -> bool { 8 | true 9 | } 10 | 11 | pub fn user_name(uid: u32) -> String { 12 | static USERS_CACHE_MUTEX: Lazy>> = Lazy::new(|| { 13 | Mutex::new(FxHashMap::default()) 14 | }); 15 | let mut users_cache = USERS_CACHE_MUTEX.lock().unwrap(); 16 | let name = users_cache 17 | .entry(uid) 18 | .or_insert_with(|| { 19 | uzers::get_user_by_uid(uid).map_or_else( 20 | || "????".to_string(), 21 | |u| u.name().to_string_lossy().to_string(), 22 | ) 23 | }); 24 | (*name).to_string() 25 | } 26 | 27 | pub fn group_name(gid: u32) -> String { 28 | static GROUPS_CACHE_MUTEX: Lazy>> = Lazy::new(|| { 29 | Mutex::new(FxHashMap::default()) 30 | }); 31 | let mut groups_cache = GROUPS_CACHE_MUTEX.lock().unwrap(); 32 | let name = groups_cache 33 | .entry(gid) 34 | .or_insert_with(|| { 35 | uzers::get_group_by_gid(gid).map_or_else( 36 | || "????".to_string(), 37 | |u| u.name().to_string_lossy().to_string(), 38 | ) 39 | }); 40 | (*name).to_string() 41 | } 42 | -------------------------------------------------------------------------------- /src/preview/mod.rs: -------------------------------------------------------------------------------- 1 | mod dir_view; 2 | mod preview; 3 | mod preview_transformer; 4 | mod preview_state; 5 | mod zero_len_file_view; 6 | 7 | pub use { 8 | dir_view::DirView, 9 | preview::Preview, 10 | preview_transformer::*, 11 | preview_state::PreviewState, 12 | zero_len_file_view::ZeroLenFileView, 13 | }; 14 | 15 | #[derive(Debug, Clone, Copy, PartialEq, serde::Deserialize)] 16 | #[serde(rename_all = "snake_case")] 17 | pub enum PreviewMode { 18 | 19 | /// image 20 | Image, 21 | 22 | /// show the content as text, with syntax coloring if 23 | /// it makes sense. Fails if the file isn't in UTF8 24 | Text, 25 | 26 | /// show the content of the file as hex 27 | Hex, 28 | } 29 | -------------------------------------------------------------------------------- /src/preview/zero_len_file_view.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | display::{Screen, W}, 4 | errors::ProgramError, 5 | skin::PanelSkin, 6 | }, 7 | char_reader::CharReader, 8 | crokey::crossterm::{ 9 | cursor, 10 | QueueableCommand, 11 | }, 12 | std::{ 13 | fs::File, 14 | path::PathBuf, 15 | }, 16 | termimad::{Area, CropWriter, SPACE_FILLING}, 17 | }; 18 | 19 | /// a (light) display for a file declaring a size 0, 20 | /// as happens for many system "files", for example in /proc 21 | pub struct ZeroLenFileView { 22 | path: PathBuf, 23 | } 24 | 25 | impl ZeroLenFileView { 26 | pub fn new(path: PathBuf) -> Self { 27 | Self { path } 28 | } 29 | pub fn display( 30 | &mut self, 31 | w: &mut W, 32 | _screen: Screen, 33 | panel_skin: &PanelSkin, 34 | area: &Area, 35 | ) -> Result<(), ProgramError> { 36 | let styles = &panel_skin.styles; 37 | let line_count = area.height as usize; 38 | let file = File::open(&self.path)?; 39 | let mut reader = CharReader::new(file); 40 | // line_len here is in chars, and we crop in cols, but it's OK because both 41 | // are usually identical for system files and we crop later anyway 42 | let line_len = area.width as usize; 43 | for y in 0..line_count { 44 | w.queue(cursor::MoveTo(area.left, y as u16 + area.top))?; 45 | let mut cw = CropWriter::new(w, area.width as usize); 46 | let cw = &mut cw; 47 | if let Some(line) = reader.next_line(line_len, 15_000)? { 48 | cw.queue_str(&styles.default, &line)?; 49 | } 50 | cw.fill(&styles.default, &SPACE_FILLING)?; 51 | } 52 | Ok(()) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/shell_install/util.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::errors::*, 3 | std::{ 4 | fs::{self, OpenOptions}, 5 | io::{BufRead, BufReader, Write}, 6 | path::Path, 7 | }, 8 | }; 9 | pub fn file_contains_line(path: &Path, searched_line: &str) -> Result { 10 | let file = fs::File::open(path) 11 | .context(&|| format!("opening {path:?}"))?; 12 | for line in BufReader::new(file).lines() { 13 | let line = line.context(&|| format!("reading line in {path:?}"))?; 14 | if line == searched_line { 15 | return Ok(true); 16 | } 17 | } 18 | Ok(false) 19 | } 20 | 21 | pub fn append_to_file>(path: &Path, content: S) -> Result<(), ShellInstallError> { 22 | let mut shellrc = OpenOptions::new() 23 | .append(true) 24 | .open(path) 25 | .context(&|| format!("opening {path:?} for append"))?; 26 | shellrc.write_all(content.as_ref().as_bytes()) 27 | .context(&|| format!("writing in {path:?}"))?; 28 | Ok(()) 29 | } 30 | -------------------------------------------------------------------------------- /src/skin/app_skin.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::*, 3 | crate::{ 4 | conf::Conf, 5 | }, 6 | rustc_hash::FxHashMap, 7 | }; 8 | 9 | 10 | /// all the skin things used by the broot application 11 | /// during running 12 | pub struct AppSkin { 13 | 14 | /// the skin used in the focused panel 15 | pub focused: PanelSkin, 16 | 17 | /// the skin used in unfocused panels 18 | pub unfocused: PanelSkin, 19 | } 20 | 21 | impl AppSkin { 22 | pub fn new(conf: &Conf, no_style: bool) -> Self { 23 | if no_style { 24 | Self { 25 | focused: PanelSkin::new(StyleMap::no_term()), 26 | unfocused: PanelSkin::new(StyleMap::no_term()), 27 | } 28 | } else { 29 | let def_skin; 30 | let skin = if let Some(skin) = &conf.skin { 31 | skin 32 | } else { 33 | def_skin = FxHashMap::default(); 34 | &def_skin 35 | }; 36 | let StyleMaps { focused, unfocused } = StyleMaps::create(skin); 37 | Self { 38 | focused: PanelSkin::new(focused), 39 | unfocused: PanelSkin::new(unfocused), 40 | } 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/skin/cli_mad_skin.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crokey::crossterm::style::Color, 3 | termimad::{gray, MadSkin}, 4 | }; 5 | 6 | /// build a termimad skin for cli output (mostly 7 | /// for the install process) 8 | pub fn make_cli_mad_skin() -> MadSkin { 9 | let mut skin = MadSkin::default(); 10 | skin.set_headers_fg(Color::AnsiValue(178)); 11 | skin.inline_code.set_bg(gray(2)); 12 | skin.inline_code.set_fg(gray(18)); 13 | skin.code_block.set_bg(gray(2)); 14 | skin.code_block.set_fg(gray(18)); 15 | skin.italic.set_fg(Color::Magenta); 16 | skin 17 | } 18 | -------------------------------------------------------------------------------- /src/skin/ext_colors.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | errors::InvalidSkinError, 4 | }, 5 | rustc_hash::FxHashMap, 6 | crokey::crossterm::style::Color, 7 | lazy_regex::*, 8 | std::convert::TryFrom, 9 | termimad::parse_color, 10 | }; 11 | 12 | 13 | /// a map from file extension to the foreground 14 | /// color to use when drawing the tree 15 | #[derive(Debug, Clone, Default)] 16 | pub struct ExtColorMap { 17 | map: FxHashMap, 18 | } 19 | 20 | impl ExtColorMap { 21 | /// return the color to use, or None when the default color 22 | /// of files should apply 23 | pub fn get(&self, ext: &str) -> Option { 24 | self.map.get(ext).copied() 25 | } 26 | pub fn set(&mut self, ext: String, raw_color: &str) -> Result<(), InvalidSkinError> { 27 | if !regex_is_match!("^none$"i, raw_color) { 28 | let color = parse_color(raw_color)?; 29 | self.map.insert(ext, color); 30 | } 31 | Ok(()) 32 | } 33 | } 34 | 35 | impl TryFrom<&FxHashMap> for ExtColorMap { 36 | type Error = InvalidSkinError; 37 | fn try_from(raw_map: &FxHashMap) -> Result { 38 | let mut map = ExtColorMap::default(); 39 | for (k, v) in raw_map { 40 | map.set(k.to_string(), v)?; 41 | } 42 | Ok(map) 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/skin/help_mad_skin.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::StyleMap, 3 | termimad::{Alignment, LineStyle, MadSkin}, 4 | }; 5 | 6 | 7 | /// build a MadSkin, which will be used for markdown formatting 8 | /// for the help screen by applying the `help_*` entries 9 | /// of the skin. 10 | pub fn make_help_mad_skin(skin: &StyleMap) -> MadSkin { 11 | let mut ms = MadSkin::default(); 12 | ms.paragraph.compound_style = skin.help_paragraph.clone(); 13 | ms.inline_code = skin.help_code.clone(); 14 | ms.code_block.compound_style = ms.inline_code.clone(); 15 | ms.bold = skin.help_bold.clone(); 16 | ms.italic = skin.help_italic.clone(); 17 | ms.table = LineStyle::new( 18 | skin.help_table_border.clone(), 19 | Alignment::Center, 20 | ); 21 | if let Some(c) = skin.help_headers.get_fg() { 22 | ms.set_headers_fg(c); 23 | } 24 | if let Some(c) = skin.help_headers.get_bg() { 25 | ms.set_headers_bg(c); 26 | } 27 | ms.bullet 28 | .set_compound_style(ms.paragraph.compound_style.clone()); 29 | ms.scrollbar 30 | .track 31 | .set_compound_style(skin.scrollbar_track.clone()); 32 | ms.scrollbar 33 | .thumb 34 | .set_compound_style(skin.scrollbar_thumb.clone()); 35 | ms 36 | } 37 | 38 | -------------------------------------------------------------------------------- /src/skin/mod.rs: -------------------------------------------------------------------------------- 1 | mod app_skin; 2 | mod cli_mad_skin; 3 | mod ext_colors; 4 | mod help_mad_skin; 5 | mod panel_skin; 6 | mod purpose_mad_skin; 7 | mod skin_entry; 8 | mod style_map; 9 | mod status_mad_skin; 10 | 11 | pub use { 12 | app_skin::AppSkin, 13 | cli_mad_skin::*, 14 | ext_colors::ExtColorMap, 15 | help_mad_skin::*, 16 | panel_skin::PanelSkin, 17 | purpose_mad_skin::*, 18 | skin_entry::SkinEntry, 19 | style_map::{StyleMap, StyleMaps}, 20 | status_mad_skin::StatusMadSkinSet, 21 | }; 22 | 23 | use crokey::crossterm::style::Color::{self, *}; 24 | 25 | pub fn gray(mut level: u8) -> Option { 26 | if level > 23 { 27 | // this only happens when I mess the literals in style_map.rs 28 | warn!("fixed invalid gray level: {}", level); 29 | level = 23 30 | } 31 | Some(AnsiValue(0xE8 + level)) 32 | } 33 | 34 | pub fn rgb(r: u8, g: u8, b: u8) -> Option { 35 | Some(Rgb { r, g, b }) 36 | } 37 | 38 | pub fn ansi(v: u8) -> Option { 39 | Some(AnsiValue(v)) 40 | } 41 | -------------------------------------------------------------------------------- /src/skin/panel_skin.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::*, 3 | termimad::MadSkin, 4 | }; 5 | 6 | /// the various skin things used in a panel. 7 | /// 8 | /// There are normally two instances of this struct in 9 | /// a broot application: one is used for the focused panel 10 | /// and one is used for the other panels. 11 | pub struct PanelSkin { 12 | pub styles: StyleMap, 13 | pub purpose_skin: MadSkin, 14 | pub status_skin: StatusMadSkinSet, 15 | pub help_skin: MadSkin, 16 | } 17 | 18 | 19 | impl PanelSkin { 20 | pub fn new(styles: StyleMap) -> Self { 21 | let purpose_skin = make_purpose_mad_skin(&styles); 22 | let status_skin = StatusMadSkinSet::from_skin(&styles); 23 | let help_skin = make_help_mad_skin(&styles); 24 | Self { 25 | styles, 26 | purpose_skin, 27 | status_skin, 28 | help_skin, 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/skin/purpose_mad_skin.rs: -------------------------------------------------------------------------------- 1 | 2 | use { 3 | super::StyleMap, 4 | termimad::{Alignment, LineStyle, MadSkin}, 5 | }; 6 | 7 | /// build a MadSkin which will be used to display the status 8 | /// when there's no error 9 | pub fn make_purpose_mad_skin(skin: &StyleMap) -> MadSkin { 10 | MadSkin { 11 | paragraph: LineStyle::new( 12 | skin.purpose_normal.clone(), 13 | Alignment::Left, 14 | ), 15 | italic: skin.purpose_italic.clone(), 16 | bold: skin.purpose_bold.clone(), 17 | ellipsis: skin.purpose_ellipsis.clone(), 18 | ..Default::default() 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/skin/skin_entry.rs: -------------------------------------------------------------------------------- 1 | //! Manage conversion of a user provided string 2 | //! defining foreground and background colors into 3 | //! a string with TTY colors 4 | 5 | use { 6 | crate::errors::InvalidSkinError, 7 | serde::{de::Error, Deserialize, Deserializer}, 8 | termimad::{ 9 | CompoundStyle, 10 | parse_compound_style, 11 | }, 12 | }; 13 | 14 | /// Parsed content of a [skin] line of the conf.toml file 15 | #[derive(Clone, Debug)] 16 | pub struct SkinEntry { 17 | focused: CompoundStyle, 18 | unfocused: Option, 19 | } 20 | 21 | impl SkinEntry { 22 | pub fn new(focused: CompoundStyle, unfocused: Option) -> Self { 23 | Self { focused, unfocused } 24 | } 25 | pub fn get_focused(&self) -> &CompoundStyle { 26 | &self.focused 27 | } 28 | pub fn get_unfocused(&self) -> &CompoundStyle { 29 | self.unfocused.as_ref().unwrap_or(&self.focused) 30 | } 31 | /// Parse a string representation of a skin entry. 32 | /// 33 | /// The general form is either "" or " / ": 34 | /// It may be just the focused compound_style, or both 35 | /// the focused and the unfocused ones, in which case there's 36 | /// a '/' as separator. 37 | /// 38 | /// Each part is " " 39 | /// where the attributes list may be empty. 40 | pub fn parse(s: &str) -> Result { 41 | let mut parts = s.split('/'); 42 | let focused = parse_compound_style(parts.next().unwrap())?; 43 | let unfocused = parts.next() 44 | .map(parse_compound_style) 45 | .transpose()?; 46 | Ok(Self { focused, unfocused }) 47 | } 48 | } 49 | 50 | impl<'de> Deserialize<'de> for SkinEntry { 51 | fn deserialize(deserializer: D) -> Result 52 | where D: Deserializer<'de> 53 | { 54 | let s = String::deserialize(deserializer)?; 55 | SkinEntry::parse(&s) 56 | .map_err(|e| D::Error::custom(e.to_string())) 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /src/skin/status_mad_skin.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::StyleMap, 3 | termimad::{Alignment, LineStyle, MadSkin}, 4 | }; 5 | 6 | /// the mad skin applying to the status depending whether it's an 7 | /// error or not 8 | pub struct StatusMadSkinSet { 9 | pub normal: MadSkin, 10 | pub error: MadSkin, 11 | } 12 | 13 | /// build a MadSkin which will be used to display the status 14 | /// when there's no error 15 | fn make_normal_status_mad_skin(skin: &StyleMap) -> MadSkin { 16 | MadSkin { 17 | paragraph: LineStyle::new( 18 | skin.status_normal.clone(), 19 | Alignment::Left, 20 | ), 21 | italic: skin.status_italic.clone(), 22 | bold: skin.status_bold.clone(), 23 | inline_code: skin.status_code.clone(), 24 | ellipsis: skin.status_ellipsis.clone(), 25 | ..Default::default() 26 | } 27 | } 28 | 29 | /// build a MadSkin which will be used to display the status 30 | /// when there's a error 31 | fn make_error_status_mad_skin(skin: &StyleMap) -> MadSkin { 32 | MadSkin { 33 | paragraph: LineStyle::new( 34 | skin.status_error.clone(), 35 | Alignment::Left, 36 | ), 37 | ellipsis: skin.status_ellipsis.clone(), 38 | ..Default::default() 39 | } 40 | } 41 | 42 | impl StatusMadSkinSet { 43 | pub fn from_skin(skin: &StyleMap) -> Self { 44 | Self { 45 | normal: make_normal_status_mad_skin(skin), 46 | error: make_error_status_mad_skin(skin), 47 | } 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/stage/mod.rs: -------------------------------------------------------------------------------- 1 | mod filtered_stage; 2 | mod stage; 3 | mod stage_state; 4 | mod stage_sum; 5 | 6 | pub use { 7 | filtered_stage::*, 8 | stage::*, 9 | stage_state::*, 10 | stage_sum::*, 11 | }; 12 | -------------------------------------------------------------------------------- /src/stage/stage_sum.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::*, 3 | crate::{ 4 | app::AppContext, 5 | file_sum::FileSum, 6 | task_sync::Dam, 7 | }, 8 | }; 9 | 10 | #[derive(Clone, Copy, Default)] 11 | pub struct StageSum { 12 | stage_version: usize, 13 | sum: Option, 14 | } 15 | 16 | impl StageSum { 17 | /// invalidates the computed sum if the version at compilation 18 | /// time is older than the current one 19 | pub fn see_stage(&mut self, stage: &Stage) { 20 | if stage.version() != self.stage_version { 21 | self.sum = None; 22 | } 23 | } 24 | pub fn is_up_to_date(&self) -> bool { 25 | self.sum.is_some() 26 | } 27 | pub fn clear(&mut self) { 28 | self.sum = None; 29 | } 30 | pub fn compute(&mut self, stage: &Stage, dam: &Dam, con: &AppContext) -> Option { 31 | if self.stage_version != stage.version() { 32 | self.sum = None; 33 | } 34 | self.stage_version = stage.version(); 35 | if self.sum.is_none() { 36 | // produces None in case of interruption 37 | self.sum = stage.compute_sum(dam, con); 38 | } 39 | self.sum 40 | } 41 | pub fn computed(&self) -> Option { 42 | self.sum 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/syntactic/mod.rs: -------------------------------------------------------------------------------- 1 | mod syntactic_view; 2 | mod syntax_theme; 3 | mod syntaxer; 4 | 5 | pub use { 6 | syntactic_view::SyntacticView, 7 | syntaxer::{SYNTAXER, Syntaxer}, 8 | syntax_theme::*, 9 | }; 10 | -------------------------------------------------------------------------------- /src/syntactic/syntaxer.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | app::AppContext, 4 | }, 5 | once_cell::sync::Lazy, 6 | std::path::Path, 7 | syntect::{ 8 | easy::{ 9 | HighlightLines, 10 | HighlightOptions, 11 | }, 12 | parsing::SyntaxSet, 13 | highlighting::{Theme, ThemeSet}, 14 | }, 15 | }; 16 | 17 | static SYNTAXES: &[u8] = include_bytes!("../../resources/syntect/syntaxes.bin"); 18 | 19 | pub static SYNTAXER: Lazy = Lazy::new(Syntaxer::default); 20 | 21 | /// wrap heavy to initialize syntect things 22 | pub struct Syntaxer { 23 | pub syntax_set: SyntaxSet, 24 | pub theme_set: ThemeSet, 25 | } 26 | impl Default for Syntaxer { 27 | fn default() -> Self { 28 | Self { 29 | syntax_set: time!( 30 | Debug, 31 | syntect::dumps::from_uncompressed_data(SYNTAXES).unwrap() 32 | ), 33 | theme_set: ThemeSet::load_defaults(), 34 | } 35 | } 36 | } 37 | 38 | impl Syntaxer { 39 | pub fn available_themes( 40 | &self 41 | ) -> std::collections::btree_map::Keys { 42 | self.theme_set.themes.keys() 43 | } 44 | 45 | pub fn highlighter_for( 46 | &self, 47 | path: &Path, 48 | con: &AppContext, 49 | ) -> Option> { 50 | path.extension() 51 | .and_then(|e| e.to_str()) 52 | .and_then(|ext| self.syntax_set.find_syntax_by_extension(ext)) 53 | .map(|syntax| { 54 | let theme = con.syntax_theme.unwrap_or_default(); 55 | let theme = self.theme_set.themes.get(theme.syntect_name()) 56 | .unwrap_or_else(|| self.theme_set.themes.iter().next().unwrap().1); 57 | let options = HighlightOptions { 58 | ignore_errors: true, 59 | }; 60 | HighlightLines::new(syntax, theme, options) 61 | }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/terminal.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | app::*, 4 | display::W, 5 | verb::*, 6 | }, 7 | std::io::Write, 8 | }; 9 | 10 | /// Change the terminal's title if broot was configured with 11 | /// a `terminal_title` entry 12 | #[inline] 13 | pub fn update_title( 14 | w: &mut W, 15 | app_state: &AppState, 16 | con: &AppContext, 17 | ) { 18 | if let Some(pattern) = &con.terminal_title_pattern { 19 | set_title(w, pattern, app_state, con); 20 | } 21 | } 22 | 23 | /// Reset the terminal's title to its default value (which may be the one 24 | /// just before broot was launched, but may also be different) 25 | pub fn reset_title( 26 | w: &mut W, 27 | con: &AppContext, 28 | ) { 29 | if con.terminal_title_pattern.is_some() && con.reset_terminal_title_on_exit { 30 | let _ = write!(w, "\u{1b}]2;\u{07}"); 31 | let _ = w.flush(); 32 | } 33 | } 34 | 35 | fn set_title( 36 | w: &mut W, 37 | pattern: &ExecPattern, 38 | app_state: &AppState, 39 | con: &AppContext, 40 | ) { 41 | let builder = ExecutionStringBuilder::without_invocation( 42 | SelInfo::from_path(&app_state.root), 43 | app_state, 44 | ); 45 | let title = builder.shell_exec_string(pattern, con); 46 | set_title_str(w, &title) 47 | } 48 | 49 | #[inline] 50 | fn set_title_str( 51 | w: &mut W, 52 | title: &str, 53 | ) { 54 | let _ = write!(w, "\u{1b}]0;{title}\u{07}"); 55 | let _ = w.flush(); 56 | } 57 | 58 | -------------------------------------------------------------------------------- /src/trash/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | mod trash_sort; 3 | mod trash_state; 4 | mod trash_state_cols; 5 | 6 | pub use trash_state::*; 7 | 8 | use { 9 | trash::{ 10 | self as trash_crate, 11 | TrashItem, 12 | TrashItemSize, 13 | }, 14 | }; 15 | 16 | /// Determine whether an item in the trash is a directory. 17 | /// 18 | /// There's probably a simpler solution in the trash crate, but I didn't found it. 19 | fn item_is_dir(item: &TrashItem) -> bool { 20 | match trash_crate::os_limited::metadata(item) { 21 | Ok(metadata) => { 22 | match metadata.size { 23 | TrashItemSize::Bytes(_) => false, 24 | TrashItemSize::Entries(_) => true, 25 | } 26 | } 27 | Err(_) => false, 28 | } 29 | } 30 | 31 | /// Return either the byte size or the number of entries of a trash item. 32 | /// 33 | /// Return None when it couldn't be determined. 34 | fn item_unified_size(item: &TrashItem) -> Option { 35 | match trash_crate::os_limited::metadata(item).ok()?.size { 36 | TrashItemSize::Bytes(v) => Some(v), 37 | TrashItemSize::Entries(v) => v.try_into().ok(), 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/trash/trash_sort.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::*, 3 | crate::{ 4 | tree::*, 5 | }, 6 | trash::{ 7 | TrashItem, 8 | }, 9 | }; 10 | 11 | /// Sort trash items according to the current tree options. 12 | pub fn sort( 13 | items: &mut [TrashItem], 14 | tree_options: &TreeOptions, 15 | ) { 16 | info!("sorting itemsi by {:?}", tree_options.sort); 17 | match tree_options.sort { 18 | Sort::Date => items.sort_by_key(|item| std::cmp::Reverse(item.time_deleted)), 19 | Sort::Size => items.sort_by_key(|item| std::cmp::Reverse( 20 | item_unified_size(item).unwrap_or(0) 21 | )), 22 | _ => items.sort_by_key(|item| (item.name.clone(), item.original_parent.clone())), 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/tree/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | mod sort; 3 | mod tree; 4 | mod tree_line; 5 | mod tree_line_type; 6 | mod tree_options; 7 | 8 | pub use { 9 | sort::Sort, 10 | tree::Tree, 11 | tree_line::*, 12 | tree_line_type::TreeLineType, 13 | tree_options::TreeOptions, 14 | }; 15 | -------------------------------------------------------------------------------- /src/tree/sort.rs: -------------------------------------------------------------------------------- 1 | 2 | /// A sort key. 3 | /// A non None sort mode implies only one level of the tree 4 | /// is displayed. 5 | /// When in None mode, paths are alpha sorted 6 | #[derive(Debug, Clone, Copy, PartialEq)] 7 | pub enum Sort { 8 | None, 9 | Count, 10 | Date, 11 | Size, 12 | TypeDirsFirst, 13 | TypeDirsLast, 14 | } 15 | 16 | impl Sort { 17 | pub fn prevent_deep_display(self) -> bool { 18 | match self { 19 | Self::None => false, 20 | Self::Count => true, 21 | Self::Date => true, 22 | Self::Size => true, 23 | Self::TypeDirsFirst => false, 24 | Self::TypeDirsLast => false, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/tree_build/bid.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::bline::BLine, 3 | id_arena::Id, 4 | std::cmp::Ordering, 5 | }; 6 | 7 | pub type BId = Id; 8 | 9 | /// a structure making it possible to keep bline references 10 | /// sorted in a binary heap with the line with the smallest 11 | /// score at the top 12 | pub struct SortableBId { 13 | pub id: BId, 14 | pub score: i32, 15 | } 16 | impl Eq for SortableBId {} 17 | impl PartialEq for SortableBId { 18 | fn eq(&self, other: &SortableBId) -> bool { 19 | self.score == other.score // unused but required by spec of Ord 20 | } 21 | } 22 | impl Ord for SortableBId { 23 | fn cmp(&self, other: &SortableBId) -> Ordering { 24 | other.score.cmp(&self.score) 25 | } 26 | } 27 | impl PartialOrd for SortableBId { 28 | fn partial_cmp(&self, other: &SortableBId) -> Option { 29 | Some(self.cmp(other)) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/tree_build/build_report.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | /// Information from the builder about the 4 | /// tree operation 5 | /// 6 | /// A file is counted at most once here 7 | #[derive(Debug, Clone, Copy, Default)] 8 | pub struct BuildReport { 9 | 10 | /// number of times a gitignore pattern excluded a file 11 | pub gitignored_count: usize, 12 | 13 | /// number of times a file was excluded because hidden 14 | /// (this count stays at zero if hidden files are displayed) 15 | pub hidden_count: usize, 16 | 17 | /// number of errors excluding a file 18 | pub error_count: usize, 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/tree_build/mod.rs: -------------------------------------------------------------------------------- 1 | mod bid; 2 | mod bline; 3 | mod build_report; 4 | mod builder; 5 | 6 | pub use { 7 | bid::BId, 8 | builder::TreeBuilder, 9 | build_report::BuildReport, 10 | }; 11 | -------------------------------------------------------------------------------- /src/verb/arg_def.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | app::SelectionType, 4 | path::PathAnchor, 5 | }, 6 | }; 7 | 8 | /// The definition of an argument given to a verb 9 | /// as understood from the invocation pattern 10 | #[derive(Debug, Clone, Copy)] 11 | pub enum ArgDef { 12 | Path { 13 | anchor: PathAnchor, 14 | selection_type: SelectionType, 15 | }, 16 | Theme, 17 | Unspecified, 18 | } 19 | -------------------------------------------------------------------------------- /src/verb/external_execution_mode.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Copy, PartialEq)] 2 | pub enum ExternalExecutionMode { 3 | /// executed in the parent shell, on broot leaving, using the `br` function 4 | FromParentShell, 5 | 6 | /// executed on broot leaving, not necessarily in the parent shell 7 | LeaveBroot, 8 | 9 | /// executed in a sub process without quitting broot 10 | StayInBroot, 11 | } 12 | 13 | impl ExternalExecutionMode { 14 | pub fn is_from_shell(self) -> bool { 15 | matches!(self, Self::FromParentShell) 16 | } 17 | pub fn is_leave_broot(self) -> bool { 18 | !matches!(self, Self::StayInBroot) 19 | } 20 | 21 | pub fn from_conf( 22 | from_shell: Option, // default is false 23 | leave_broot: Option, // default is true 24 | ) -> Self { 25 | if from_shell.unwrap_or(false) { 26 | Self::FromParentShell 27 | } else if leave_broot.unwrap_or(true) { 28 | Self::LeaveBroot 29 | } else { 30 | Self::StayInBroot 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/verb/file_type_condition.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | app::SelectionType, 4 | content_type, 5 | tree::{TreeLine, TreeLineType}, 6 | }, 7 | serde::{Deserialize, Serialize}, 8 | std::path::Path, 9 | }; 10 | 11 | #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)] 12 | #[serde(rename_all = "snake_case")] 13 | pub enum FileTypeCondition { 14 | #[default] 15 | Any, 16 | // directory or link to a directory 17 | Directory, 18 | File, 19 | TextFile, 20 | BinaryFile, 21 | } 22 | 23 | impl FileTypeCondition { 24 | pub fn is_default(&self) -> bool { 25 | self == &Self::default() 26 | } 27 | pub fn accepts_path(self, path: &Path) -> bool { 28 | match self { 29 | Self::Any => true, 30 | Self::Directory => path.is_dir(), 31 | Self::File => path.is_file(), 32 | Self::TextFile => { 33 | path.is_file() && matches!(content_type::is_file_text(path), Ok(true)) 34 | } 35 | Self::BinaryFile => { 36 | path.is_file() && matches!(content_type::is_file_binary(path), Ok(true)) 37 | } 38 | } 39 | } 40 | pub fn accepts_line(self, line: &TreeLine) -> bool { 41 | match self { 42 | Self::Any => true, 43 | Self::Directory => line.is_dir(), 44 | Self::File => matches!(line.line_type, TreeLineType::File), 45 | Self::TextFile => { 46 | line.is_file() && matches!(content_type::is_file_text(&line.path), Ok(true)) 47 | } 48 | Self::BinaryFile => { 49 | line.is_file() && matches!(content_type::is_file_binary(&line.path), Ok(true)) 50 | } 51 | } 52 | } 53 | /// a little clunky, should be used only on well defined cases, like documenting 54 | /// internals 55 | pub fn accepts_selection_type( 56 | self, 57 | stype: SelectionType, 58 | ) -> bool { 59 | match (self, stype) { 60 | (Self::Any, _) => true, 61 | (Self::Directory, SelectionType::Directory) => true, 62 | (Self::File, SelectionType::File) => true, 63 | _ => false, 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/verb/internal_execution.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::*, 3 | crate::errors::ConfError, 4 | std::fmt, 5 | }; 6 | 7 | /// A verb execution definition based on an internal 8 | #[derive(Debug, Clone)] 9 | pub struct InternalExecution { 10 | 11 | /// the internal to use 12 | pub internal: Internal, 13 | 14 | /// whether to open the resulting state in a new panel 15 | /// instead of the current ones 16 | pub bang: bool, 17 | 18 | /// arguments 19 | /// (for example `"~"` when a verb execution is `:!focus ~`) 20 | pub arg: Option, 21 | } 22 | 23 | impl InternalExecution { 24 | pub fn from_internal(internal: Internal) -> Self { 25 | Self { 26 | internal, 27 | bang: false, 28 | arg: None, 29 | } 30 | } 31 | pub fn from_internal_bang(internal: Internal, bang: bool) -> Self { 32 | Self { 33 | internal, 34 | bang, 35 | arg: None, 36 | } 37 | } 38 | pub fn try_from(invocation_str: &str) -> Result { 39 | let invocation = VerbInvocation::from(invocation_str); 40 | let internal = Internal::try_from(&invocation.name)?; 41 | Ok(Self { 42 | internal, 43 | bang: invocation.bang, 44 | arg: invocation.args, 45 | }) 46 | } 47 | pub fn needs_selection(&self) -> bool { 48 | self.internal.needs_selection(&self.arg) 49 | } 50 | } 51 | impl fmt::Display for InternalExecution { 52 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 53 | write!(f, ":{}", self.internal.name())?; 54 | if self.bang { 55 | write!(f, "!")?; 56 | } 57 | if let Some(arg) = &self.arg { 58 | write!(f, " {arg}")?; 59 | } 60 | Ok(()) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/verb/internal_select.rs: -------------------------------------------------------------------------------- 1 | //! utility functions to help handle the `:select` internal 2 | 3 | use { 4 | super::*, 5 | crate::{ 6 | app::*, 7 | browser::BrowserState, 8 | command::TriggerType, 9 | display::Screen, 10 | tree::Tree, 11 | }, 12 | std::path::PathBuf, 13 | }; 14 | 15 | 16 | /// general implementation for verbs based on the :select internal with optionally 17 | /// a bang or an argument. 18 | pub fn on_internal( 19 | internal_exec: &InternalExecution, 20 | input_invocation: Option<&VerbInvocation>, 21 | trigger_type: TriggerType, 22 | tree: &mut Tree, 23 | app_state: & AppState, 24 | cc: &CmdContext, 25 | ) -> CmdResult { 26 | let Some(path) = internal_path::determine_path( 27 | internal_exec, 28 | input_invocation, 29 | trigger_type, 30 | tree, 31 | app_state, 32 | cc, 33 | ) else { 34 | return CmdResult::Keep; 35 | }; 36 | let screen = cc.app.screen; 37 | let bang = input_invocation 38 | .map(|inv| inv.bang) 39 | .unwrap_or(internal_exec.bang); 40 | on_path(path, tree, screen, bang) 41 | } 42 | 43 | pub fn on_path( 44 | path: PathBuf, 45 | tree: &mut Tree, 46 | screen: Screen, 47 | in_new_panel: bool, 48 | ) -> CmdResult { 49 | debug!("executing :select on path {:?}", &path); 50 | if in_new_panel { 51 | warn!("bang in :select isn't supported yet"); 52 | } 53 | if tree.try_select_path(&path) { 54 | tree.make_selection_visible(BrowserState::page_height(screen)); 55 | } 56 | CmdResult::Keep 57 | } 58 | -------------------------------------------------------------------------------- /src/verb/mod.rs: -------------------------------------------------------------------------------- 1 | mod arg_def; 2 | mod exec_pattern; 3 | mod execution_builder; 4 | mod external_execution; 5 | mod external_execution_mode; 6 | mod file_type_condition; 7 | mod internal; 8 | mod internal_execution; 9 | pub mod internal_focus; 10 | pub mod internal_select; 11 | pub mod internal_path; 12 | mod invocation_parser; 13 | mod sequence_execution; 14 | mod verb; 15 | mod verb_description; 16 | mod verb_execution; 17 | mod verb_invocation; 18 | mod verb_store; 19 | mod write; 20 | 21 | pub use { 22 | arg_def::*, 23 | exec_pattern::*, 24 | execution_builder::ExecutionStringBuilder, 25 | external_execution::ExternalExecution, 26 | external_execution_mode::ExternalExecutionMode, 27 | file_type_condition::*, 28 | internal::Internal, 29 | internal_execution::InternalExecution, 30 | invocation_parser::InvocationParser, 31 | once_cell::sync::Lazy, 32 | sequence_execution::SequenceExecution, 33 | verb::Verb, 34 | verb_description::VerbDescription, 35 | verb_execution::VerbExecution, 36 | verb_invocation::*, 37 | verb_store::{PrefixSearchResult, VerbStore}, 38 | write::*, 39 | }; 40 | use { 41 | lazy_regex::*, 42 | }; 43 | 44 | /// the group you find in invocation patterns and execution patterns 45 | pub static GROUP: Lazy = lazy_regex!(r"\{([^{}:]+)(?::([^{}:]+))?\}"); 46 | 47 | pub type VerbId = usize; 48 | 49 | pub fn str_has_selection_group(s: &str) -> bool { 50 | GROUP.find_iter(s) 51 | .any(|group| matches!( 52 | group.as_str(), 53 | "{file}" | "{file-name}" | "{parent}" | "{directory}", 54 | )) 55 | } 56 | pub fn str_has_other_panel_group(s: &str) -> bool { 57 | for group in GROUP.find_iter(s) { 58 | if group.as_str().starts_with("{other-panel-") { 59 | return true; 60 | } 61 | } 62 | false 63 | } 64 | 65 | -------------------------------------------------------------------------------- /src/verb/sequence_execution.rs: -------------------------------------------------------------------------------- 1 | 2 | use { 3 | crate::{ 4 | command::Sequence, 5 | }, 6 | }; 7 | 8 | /// A verb execution definition based on a sequence 9 | /// of commands 10 | #[derive(Debug, Clone)] 11 | pub struct SequenceExecution { 12 | 13 | pub sequence: Sequence, 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/verb/verb_description.rs: -------------------------------------------------------------------------------- 1 | /// how a verb is described in the help screen 2 | #[derive(Debug, Clone)] 3 | pub struct VerbDescription { 4 | pub code: bool, 5 | pub content: String, 6 | } 7 | 8 | impl VerbDescription { 9 | pub fn from_code(content: String) -> Self { 10 | Self { 11 | code: true, 12 | content, 13 | } 14 | } 15 | pub fn from_text(content: String) -> Self { 16 | Self { 17 | code: false, 18 | content, 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/verb/verb_execution.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::*, 3 | std::fmt, 4 | }; 5 | 6 | /// how a verb must be executed 7 | #[derive(Debug, Clone)] 8 | pub enum VerbExecution { 9 | /// the verb execution is based on a behavior defined in code in Broot. 10 | /// Executions in conf starting with ":" are of this type. 11 | Internal(InternalExecution), 12 | 13 | /// the verb execution refers to a command that will be executed by the system, 14 | /// outside of broot. 15 | External(ExternalExecution), 16 | 17 | /// the execution is a sequence similar to what can be given 18 | /// to broot with --cmd 19 | Sequence(SequenceExecution), 20 | } 21 | 22 | impl fmt::Display for VerbExecution { 23 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 24 | match self { 25 | Self::Internal(ie) => ie.fmt(f), 26 | Self::External(ee) => ee.exec_pattern.fmt(f), 27 | Self::Sequence(se) => se.sequence.raw.fmt(f), 28 | } 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/verb/write.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | app::*, 4 | errors::ProgramError, 5 | }, 6 | std::{ 7 | fs::{File, OpenOptions}, 8 | io::Write, 9 | }, 10 | }; 11 | 12 | /// Intended to verbs, this function writes the passed string to the file 13 | /// provided to broot with `--verb-output`, creating a new line if the 14 | /// file is not empty. 15 | pub fn verb_write( 16 | con: &AppContext, 17 | line: &str, 18 | ) -> Result { 19 | let Some(path) = &con.launch_args.verb_output else { 20 | return Ok(CmdResult::error("No --verb-output provided".to_string())); 21 | }; 22 | let mut file = OpenOptions::new() 23 | .create(true) 24 | .append(true) 25 | .open(path)?; 26 | if file.metadata().map(|m| m.len() > 0).unwrap_or(false) { 27 | writeln!(file)?; 28 | } 29 | write!(file, "{}", line)?; 30 | Ok(CmdResult::Keep) 31 | } 32 | 33 | /// Remove the content of the file provided to broot with `--verb-output`. 34 | pub fn verb_clear_output( 35 | con: &AppContext, 36 | ) -> Result { 37 | let Some(path) = &con.launch_args.verb_output else { 38 | return Ok(CmdResult::error("No --verb-output provided".to_string())); 39 | }; 40 | File::create(path)?; 41 | Ok(CmdResult::Keep) 42 | } 43 | -------------------------------------------------------------------------------- /version.sh: -------------------------------------------------------------------------------- 1 | # extract the version from the Cargo.toml file 2 | version=$(sed 's/^version = "\([^\"]*\)"/\1/;t;d' Cargo.toml | head -1) 3 | 4 | echo "$version" 5 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | 2 | Broot's website is live at https://dystroy.org/broot 3 | 4 | It's built using [mkdocs](https://www.mkdocs.org/) 5 | 6 | The current version of the site uses mkdocs version 1.0.4 and doesn't properly render on versions 1.1.x (this should be changed) 7 | 8 | To test it locally, cd to the website directory then 9 | 10 | mkdocs serve 11 | 12 | To build it, do 13 | 14 | mkdocs build 15 | 16 | The broot_theme theme is taken from mkdocs standard theme "mkdocs" from mkdocs version 1.0.4 then adapted. 17 | 18 | The reason I'm not using anymore the theme by name is that the theme changes with mkdocs version changes. 19 | 20 | So in order to keep a constant theme between versions of mkdocs, I had to extract it. 21 | -------------------------------------------------------------------------------- /website/broot_theme/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | 5 |
6 |
7 |

404

8 |

Page not found

9 |
10 |
11 | 12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /website/broot_theme/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/broot_theme/__init__.py -------------------------------------------------------------------------------- /website/broot_theme/broot_theme.yml: -------------------------------------------------------------------------------- 1 | # Config options for 'mkdocs' theme 2 | 3 | static_templates: 4 | - 404.html 5 | 6 | include_search_page: false 7 | search_index_only: false 8 | 9 | highlightjs: true 10 | hljs_languages: 11 | - yaml 12 | - ini 13 | - css 14 | - rust 15 | hljs_style: 'github' 16 | shortcuts: 17 | help: 191 # ? 18 | next: 78 # n 19 | previous: 80 # p 20 | search: 83 # s 21 | -------------------------------------------------------------------------------- /website/broot_theme/content.html: -------------------------------------------------------------------------------- 1 | {% if page.meta.source %} 2 | 7 | {% endif %} 8 | 9 | {{ page.content }} 10 | -------------------------------------------------------------------------------- /website/broot_theme/css/github.min.css: -------------------------------------------------------------------------------- 1 | .hljs{display:block;overflow-x:auto;padding:0.5em;color:#333;background:#f8f8f8}.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:bold}.hljs-number,.hljs-literal,.hljs-variable,.hljs-template-variable,.hljs-tag .hljs-attr{color:#008080}.hljs-string,.hljs-doctag{color:#d14}.hljs-title,.hljs-section,.hljs-selector-id{color:#900;font-weight:bold}.hljs-subst{font-weight:normal}.hljs-type,.hljs-class .hljs-title{color:#458;font-weight:bold}.hljs-tag,.hljs-name,.hljs-attribute{color:#000080;font-weight:normal}.hljs-regexp,.hljs-link{color:#009926}.hljs-symbol,.hljs-bullet{color:#990073}.hljs-built_in,.hljs-builtin-name{color:#0086b3}.hljs-meta{color:#999;font-weight:bold}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold} -------------------------------------------------------------------------------- /website/broot_theme/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/broot_theme/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /website/broot_theme/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/broot_theme/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /website/broot_theme/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/broot_theme/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /website/broot_theme/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/broot_theme/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /website/broot_theme/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/broot_theme/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /website/broot_theme/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/broot_theme/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /website/broot_theme/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/broot_theme/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /website/broot_theme/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/broot_theme/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /website/broot_theme/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/broot_theme/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /website/broot_theme/img/cows.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/broot_theme/img/cows.jpg -------------------------------------------------------------------------------- /website/broot_theme/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/broot_theme/img/favicon.ico -------------------------------------------------------------------------------- /website/broot_theme/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/broot_theme/img/favicon.png -------------------------------------------------------------------------------- /website/broot_theme/keyboard-modal.html: -------------------------------------------------------------------------------- 1 | 41 | -------------------------------------------------------------------------------- /website/broot_theme/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block footer %} 4 | {% endblock %} 5 | -------------------------------------------------------------------------------- /website/broot_theme/nav-sub.html: -------------------------------------------------------------------------------- 1 | {%- if not nav_item.children %} 2 |
  • 3 | {{ nav_item.title }} 4 |
  • 5 | {%- else %} 6 | 14 | {%- endif %} 15 | -------------------------------------------------------------------------------- /website/broot_theme/search-modal.html: -------------------------------------------------------------------------------- 1 | 25 | -------------------------------------------------------------------------------- /website/broot_theme/toc.html: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /website/deploy-master.sh: -------------------------------------------------------------------------------- 1 | mkdocs build 2 | cp -r site/* ~/dev/www/dystroy/broot-master/ 3 | -------------------------------------------------------------------------------- /website/docs/README.md: -------------------------------------------------------------------------------- 1 | 2 | This directory is the source of the web site which is at https://dystroy.org/broot 3 | -------------------------------------------------------------------------------- /website/docs/css/details.css: -------------------------------------------------------------------------------- 1 | .admonition.closed { 2 | padding: 15px 15px 5px 15px; 3 | } 4 | .note.details { 5 | background: #ffffff80; 6 | color: #555; 7 | } 8 | .note.details .admonition-title { 9 | cursor: pointer; 10 | color: #777; 11 | } 12 | .note.details .admonition-title:hover { 13 | color: #555; 14 | } 15 | .note.details.closed .admonition-title::before { 16 | content: "▼"; 17 | } 18 | .note.details .admonition-title::before { 19 | content: "▲"; 20 | } 21 | .note.details.closed > *:not(.admonition-title) { 22 | display: none; 23 | } 24 | -------------------------------------------------------------------------------- /website/docs/css/link-to-dystroy.css: -------------------------------------------------------------------------------- 1 | 2 | .link-to-dystroy { 3 | position: absolute; 4 | left: 4px; 5 | top: 0; 6 | height: 100%; 7 | width: 50px; 8 | background: url(../img/dystroy-rust-white.svg) no-repeat; 9 | background-position: left center; 10 | background-size: 40px 40px; 11 | opacity: .3; 12 | transition: opacity .5s; 13 | } 14 | .link-to-dystroy:hover { 15 | opacity: 1; 16 | transition: opacity .5s; 17 | } 18 | -------------------------------------------------------------------------------- /website/docs/css/tab-langs.css: -------------------------------------------------------------------------------- 1 | 2 | .lang-tab { 3 | color: #555; 4 | padding: 2px 5px; 5 | cursor: pointer; 6 | font-size: 90%; 7 | } 8 | 9 | .lang-tab.active { 10 | color: #333; 11 | border-top: solid 1px #e1e4e5; 12 | border-right: solid 1px #e1e4e5; 13 | border-bottom: solid 3px #f8f8f8; 14 | border-left: solid 1px #e1e4e5; 15 | background: #f8f8f8; 16 | cursor: auto; 17 | } 18 | 19 | pre.tabbed { 20 | } 21 | 22 | /* Hljs support for unknown languages like Hjson has 23 | * regressed, I have to hack */ 24 | pre code.language-Hjson, pre code.language-hjson { 25 | display: block; 26 | overflow-x: auto; 27 | padding: 0.5em; 28 | color: #333; 29 | background: #f8f8f8; 30 | } 31 | -------------------------------------------------------------------------------- /website/docs/export.md: -------------------------------------------------------------------------------- 1 | 2 | If you want to use the pruned tree out of broot (for example for a documentation), you may use the `:print_tree` verb. 3 | 4 | It can be used in several ways. 5 | 6 | The easiest is to execute it from inside the application (the verb is also accessible with the `:pt` shortcut). This quits broot and you find the tree on your console, without the status line and the input, but with the same filtering state as when you were browsing. 7 | 8 | 9 | Example with a filter: 10 | 11 | ![exported styled tree](img/20190321-cmd-pt-styled.png) 12 | 13 | Example without style or color, thanks to `--color no`: 14 | 15 | ![exported unstyled tree](img/20210222-cmd-pt-unstyled.png) 16 | 17 | This is also how would look the tree directly exported into a file. 18 | 19 | With the `--out` command, the tree is written in a given file. For example `br --out test.txt`. 20 | 21 | You can also redirect the output of broot in a standard unix way. 22 | 23 | You don't have to enter broot, you may also directly get the tree by using the [`--cmd` argument](../launch/#the-cmd-launch-argument). An additional parameter may come handy: `--height` which specifies the size of the virtual screen, which may be smaller or bigger than the real one (no problem if you want 10000 lines). 24 | 25 | For example 26 | 27 | br --cmd ":pt" > my_file.txt 28 | 29 | will export the local tree to the `my_file.txt` file. 30 | 31 | Or 32 | 33 | br > tree.txt 34 | 35 | in which case you'll manually do `:pt` when in broot but after having had the opportunity to navigate, filter and change toggles as desired. 36 | 37 | -------------------------------------------------------------------------------- /website/docs/help.md: -------------------------------------------------------------------------------- 1 | 2 | Broot's help screen is designed to be as short as possible while giving the essential references and directions. 3 | 4 | It starts with the version (useful to get help), links to the documentation (you may ctrl-click the link on most terminals) then explains how to edit the configuration, lists the commands and shortcuts, the search modes, and ends with the list of special feature the executable you're using was compiled with (once again, this is handy when you need help). 5 | 6 | 7 | # Open the help 8 | 9 | As for everything, this can be configured, but you'll normally get to the help either 10 | 11 | * by hitting the ? key 12 | * by typing `:help` then enter 13 | 14 | And you'll leave this screen with a hit on the esc key. 15 | 16 | # edit the configuration 17 | 18 | When in this screen, the current selection is the configuration file. 19 | 20 | What it means is that any verb taking as argument a file can be executed. 21 | 22 | For example, hitting enter calls the standard opening of this file. 23 | 24 | And if you configured a text editor, let's say bound on `:e`, it would work too. 25 | 26 | This is the fastest way to change the configuration. 27 | 28 | # Verbs 29 | 30 | *Verbs* are what is combined with the selection and optional arguments to make the commands you execute. 31 | They're behind every action in Broot, so their list, made from both the built-in verbs and the ones you configured, is essential. 32 | 33 | 34 | ![unfiltered help](img/help-unfiltered.png) 35 | 36 | But this list is a little to long for scanning, so you'll most often search it. 37 | 38 | For example, let's imagine you want to see what's the shortcut for showing *hidden* files. 39 | You search for the first letter of your topic, here "hi". The list is filtered while you type to reveal the interesting verb(s): 40 | 41 | ![filtered help](img/help-filtered.png) 42 | 43 | In this example you see that you can toggle showing hidden files by hitting alth or by typing `:h` then enter. 44 | 45 | # Check search modes and their prefixes 46 | 47 | There are [several kinds of searches](../input). 48 | 49 | You might want to check those modes and their prefixes (which can be [configured](../conf_file/#search-modes)): 50 | 51 | ![help search modes](img/help-search-modes.png) 52 | 53 | 54 | -------------------------------------------------------------------------------- /website/docs/img/20181215-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20181215-edit.png -------------------------------------------------------------------------------- /website/docs/img/20181215-only-folders-with-size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20181215-only-folders-with-size.png -------------------------------------------------------------------------------- /website/docs/img/20181215-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20181215-overview.png -------------------------------------------------------------------------------- /website/docs/img/20181215-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20181215-search.png -------------------------------------------------------------------------------- /website/docs/img/20181218-cd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20181218-cd.png -------------------------------------------------------------------------------- /website/docs/img/20190101-flags.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190101-flags.png -------------------------------------------------------------------------------- /website/docs/img/20190110-flags.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190110-flags.png -------------------------------------------------------------------------------- /website/docs/img/20190110-pattern_verb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190110-pattern_verb.png -------------------------------------------------------------------------------- /website/docs/img/20190122-dcd_rulset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190122-dcd_rulset.png -------------------------------------------------------------------------------- /website/docs/img/20190128-cd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190128-cd.png -------------------------------------------------------------------------------- /website/docs/img/20190128-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190128-edit.png -------------------------------------------------------------------------------- /website/docs/img/20190128-only-folders-with-size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190128-only-folders-with-size.png -------------------------------------------------------------------------------- /website/docs/img/20190128-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190128-overview.png -------------------------------------------------------------------------------- /website/docs/img/20190128-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190128-search.png -------------------------------------------------------------------------------- /website/docs/img/20190205-mycnf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190205-mycnf.png -------------------------------------------------------------------------------- /website/docs/img/20190212-mycnf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190212-mycnf.png -------------------------------------------------------------------------------- /website/docs/img/20190217-custom-colors-help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190217-custom-colors-help.png -------------------------------------------------------------------------------- /website/docs/img/20190217-custom-colors-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190217-custom-colors-tree.png -------------------------------------------------------------------------------- /website/docs/img/20190305-dev-sizes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190305-dev-sizes.png -------------------------------------------------------------------------------- /website/docs/img/20190305-rm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190305-rm.png -------------------------------------------------------------------------------- /website/docs/img/20190305-search-hel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190305-search-hel.png -------------------------------------------------------------------------------- /website/docs/img/20190306-blop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190306-blop.png -------------------------------------------------------------------------------- /website/docs/img/20190306-md-c-client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190306-md-c-client.png -------------------------------------------------------------------------------- /website/docs/img/20190306-md.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190306-md.png -------------------------------------------------------------------------------- /website/docs/img/20190306-mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190306-mv.png -------------------------------------------------------------------------------- /website/docs/img/20190321-cmd-pt-styled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190321-cmd-pt-styled.png -------------------------------------------------------------------------------- /website/docs/img/20190321-cmd-pt-unstyled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190321-cmd-pt-unstyled.png -------------------------------------------------------------------------------- /website/docs/img/20190607-custom-colors-sizes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190607-custom-colors-sizes.png -------------------------------------------------------------------------------- /website/docs/img/20190607-custom-colors-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190607-custom-colors-tree.png -------------------------------------------------------------------------------- /website/docs/img/20190802-sizes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20190802-sizes.png -------------------------------------------------------------------------------- /website/docs/img/20191030-dev-sizes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20191030-dev-sizes.png -------------------------------------------------------------------------------- /website/docs/img/20191112-cd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20191112-cd.png -------------------------------------------------------------------------------- /website/docs/img/20191112-custom-colors-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20191112-custom-colors-tree.png -------------------------------------------------------------------------------- /website/docs/img/20191112-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20191112-edit.png -------------------------------------------------------------------------------- /website/docs/img/20191112-md-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20191112-md-list.png -------------------------------------------------------------------------------- /website/docs/img/20191112-md-missing-subpath.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20191112-md-missing-subpath.png -------------------------------------------------------------------------------- /website/docs/img/20191112-mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20191112-mv.png -------------------------------------------------------------------------------- /website/docs/img/20191112-mycnf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20191112-mycnf.png -------------------------------------------------------------------------------- /website/docs/img/20191112-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20191112-overview.png -------------------------------------------------------------------------------- /website/docs/img/20191112-sizes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20191112-sizes.png -------------------------------------------------------------------------------- /website/docs/img/20191114-light-skin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20191114-light-skin.png -------------------------------------------------------------------------------- /website/docs/img/20191214-replace-ls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20191214-replace-ls.png -------------------------------------------------------------------------------- /website/docs/img/20200203-git.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200203-git.png -------------------------------------------------------------------------------- /website/docs/img/20200520-ctrlp-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200520-ctrlp-1.png -------------------------------------------------------------------------------- /website/docs/img/20200520-ctrlp-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200520-ctrlp-2.png -------------------------------------------------------------------------------- /website/docs/img/20200520-ctrlp-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200520-ctrlp-3.png -------------------------------------------------------------------------------- /website/docs/img/20200520-ctrlp-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200520-ctrlp-4.png -------------------------------------------------------------------------------- /website/docs/img/20200525-colored-panels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200525-colored-panels.png -------------------------------------------------------------------------------- /website/docs/img/20200525-cpp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200525-cpp.png -------------------------------------------------------------------------------- /website/docs/img/20200525-custom-colors-panels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200525-custom-colors-panels.png -------------------------------------------------------------------------------- /website/docs/img/20200526-3-panels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200526-3-panels.png -------------------------------------------------------------------------------- /website/docs/img/20200526-input-fuzzy-mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200526-input-fuzzy-mv.png -------------------------------------------------------------------------------- /website/docs/img/20200526-input-fuzzy-rm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200526-input-fuzzy-rm.png -------------------------------------------------------------------------------- /website/docs/img/20200526-input-fuzzy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200526-input-fuzzy.png -------------------------------------------------------------------------------- /website/docs/img/20200526-input-regex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200526-input-regex.png -------------------------------------------------------------------------------- /website/docs/img/20200526-light-skin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200526-light-skin.png -------------------------------------------------------------------------------- /website/docs/img/20200529-transparent-broot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200529-transparent-broot.png -------------------------------------------------------------------------------- /website/docs/img/20200604-fuzzy-path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200604-fuzzy-path.png -------------------------------------------------------------------------------- /website/docs/img/20200604-input-regex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200604-input-regex.png -------------------------------------------------------------------------------- /website/docs/img/20200608-content-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200608-content-search.png -------------------------------------------------------------------------------- /website/docs/img/20200620-complex-composite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200620-complex-composite.png -------------------------------------------------------------------------------- /website/docs/img/20200620-composite-notrs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200620-composite-notrs.png -------------------------------------------------------------------------------- /website/docs/img/20200620-content-memm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200620-content-memm.png -------------------------------------------------------------------------------- /website/docs/img/20200620-content-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200620-content-search.png -------------------------------------------------------------------------------- /website/docs/img/20200628-sdp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200628-sdp.png -------------------------------------------------------------------------------- /website/docs/img/20200628-whale-spotting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200628-whale-spotting.png -------------------------------------------------------------------------------- /website/docs/img/20200629-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200629-overview.png -------------------------------------------------------------------------------- /website/docs/img/20200704-sdp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200704-sdp.png -------------------------------------------------------------------------------- /website/docs/img/20200704-whale-spotting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200704-whale-spotting.png -------------------------------------------------------------------------------- /website/docs/img/20200709-combneg-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200709-combneg-1.png -------------------------------------------------------------------------------- /website/docs/img/20200709-combneg-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200709-combneg-2.png -------------------------------------------------------------------------------- /website/docs/img/20200709-combneg-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200709-combneg-3.png -------------------------------------------------------------------------------- /website/docs/img/20200710-alias-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200710-alias-tree.png -------------------------------------------------------------------------------- /website/docs/img/20200716-binary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200716-binary.png -------------------------------------------------------------------------------- /website/docs/img/20200716-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200716-preview.png -------------------------------------------------------------------------------- /website/docs/img/20200716-search-log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200716-search-log.png -------------------------------------------------------------------------------- /website/docs/img/20200727-search-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20200727-search-preview.png -------------------------------------------------------------------------------- /website/docs/img/2020081609-preview-binary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/2020081609-preview-binary.png -------------------------------------------------------------------------------- /website/docs/img/2020081609-preview-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/2020081609-preview-image.png -------------------------------------------------------------------------------- /website/docs/img/20201002-cr-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20201002-cr-search.png -------------------------------------------------------------------------------- /website/docs/img/20201020-chmod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20201020-chmod.png -------------------------------------------------------------------------------- /website/docs/img/20201020-fs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20201020-fs.png -------------------------------------------------------------------------------- /website/docs/img/20201020-whale-spotting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20201020-whale-spotting.png -------------------------------------------------------------------------------- /website/docs/img/20201127-kitty-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20201127-kitty-preview.png -------------------------------------------------------------------------------- /website/docs/img/20201130-sdp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20201130-sdp.png -------------------------------------------------------------------------------- /website/docs/img/20201219-tree-with-args.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20201219-tree-with-args.png -------------------------------------------------------------------------------- /website/docs/img/20210204-gruvbox-sdp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210204-gruvbox-sdp.png -------------------------------------------------------------------------------- /website/docs/img/20210204-mycnf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210204-mycnf.png -------------------------------------------------------------------------------- /website/docs/img/20210222-cmd-pt-unstyled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210222-cmd-pt-unstyled.png -------------------------------------------------------------------------------- /website/docs/img/20210424-gruvbox-sdp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210424-gruvbox-sdp.png -------------------------------------------------------------------------------- /website/docs/img/20210424-mycnf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210424-mycnf.png -------------------------------------------------------------------------------- /website/docs/img/20210424-staging-mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210424-staging-mv.png -------------------------------------------------------------------------------- /website/docs/img/20210425-alias-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210425-alias-tree.png -------------------------------------------------------------------------------- /website/docs/img/20210425-help-filter-stage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210425-help-filter-stage.png -------------------------------------------------------------------------------- /website/docs/img/20210425-sdp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210425-sdp.png -------------------------------------------------------------------------------- /website/docs/img/20210425-staging-filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210425-staging-filter.png -------------------------------------------------------------------------------- /website/docs/img/20210511-fuzzy-re.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210511-fuzzy-re.png -------------------------------------------------------------------------------- /website/docs/img/20210511-regex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210511-regex.png -------------------------------------------------------------------------------- /website/docs/img/20210511-twop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210511-twop.png -------------------------------------------------------------------------------- /website/docs/img/20210603-br-w-stage-rm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210603-br-w-stage-rm.png -------------------------------------------------------------------------------- /website/docs/img/20210603-br-w-stage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210603-br-w-stage.png -------------------------------------------------------------------------------- /website/docs/img/20210603-br-w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210603-br-w.png -------------------------------------------------------------------------------- /website/docs/img/20210603-chmod-perm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210603-chmod-perm.png -------------------------------------------------------------------------------- /website/docs/img/20210603-chmod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210603-chmod.png -------------------------------------------------------------------------------- /website/docs/img/20210603-cp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210603-cp.png -------------------------------------------------------------------------------- /website/docs/img/20210603-cpp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210603-cpp.png -------------------------------------------------------------------------------- /website/docs/img/20210603-cr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210603-cr.png -------------------------------------------------------------------------------- /website/docs/img/20210603-e-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210603-e-line.png -------------------------------------------------------------------------------- /website/docs/img/20210603-e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210603-e.png -------------------------------------------------------------------------------- /website/docs/img/20210603-md.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210603-md.png -------------------------------------------------------------------------------- /website/docs/img/20210603-mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210603-mv.png -------------------------------------------------------------------------------- /website/docs/img/20210603-mvp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210603-mvp.png -------------------------------------------------------------------------------- /website/docs/img/20210603-rename.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210603-rename.png -------------------------------------------------------------------------------- /website/docs/img/20210603-rm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20210603-rm.png -------------------------------------------------------------------------------- /website/docs/img/20230129-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20230129-overview.png -------------------------------------------------------------------------------- /website/docs/img/20230210-extension-content.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20230210-extension-content.png -------------------------------------------------------------------------------- /website/docs/img/20230517-fs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20230517-fs.png -------------------------------------------------------------------------------- /website/docs/img/20230526-chmod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20230526-chmod.png -------------------------------------------------------------------------------- /website/docs/img/20230930-cd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20230930-cd.png -------------------------------------------------------------------------------- /website/docs/img/20230930-chmod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20230930-chmod.png -------------------------------------------------------------------------------- /website/docs/img/20230930-colored-panels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20230930-colored-panels.png -------------------------------------------------------------------------------- /website/docs/img/20230930-content-memm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20230930-content-memm.png -------------------------------------------------------------------------------- /website/docs/img/20230930-cpp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20230930-cpp.png -------------------------------------------------------------------------------- /website/docs/img/20230930-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20230930-edit.png -------------------------------------------------------------------------------- /website/docs/img/20230930-fs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20230930-fs.png -------------------------------------------------------------------------------- /website/docs/img/20230930-gccran.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20230930-gccran.png -------------------------------------------------------------------------------- /website/docs/img/20230930-gg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20230930-gg.png -------------------------------------------------------------------------------- /website/docs/img/20230930-git.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20230930-git.png -------------------------------------------------------------------------------- /website/docs/img/20230930-mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20230930-mv.png -------------------------------------------------------------------------------- /website/docs/img/20230930-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20230930-overview.png -------------------------------------------------------------------------------- /website/docs/img/20230930-preview-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20230930-preview-image.png -------------------------------------------------------------------------------- /website/docs/img/20230930-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20230930-preview.png -------------------------------------------------------------------------------- /website/docs/img/20230930-psql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20230930-psql.png -------------------------------------------------------------------------------- /website/docs/img/20230930-sdp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20230930-sdp.png -------------------------------------------------------------------------------- /website/docs/img/20230930-staging-mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20230930-staging-mv.png -------------------------------------------------------------------------------- /website/docs/img/20230930-whale-spotting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20230930-whale-spotting.png -------------------------------------------------------------------------------- /website/docs/img/20240225-icon-comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20240225-icon-comparison.png -------------------------------------------------------------------------------- /website/docs/img/20240225-nerdfont-cheatsheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20240225-nerdfont-cheatsheet.png -------------------------------------------------------------------------------- /website/docs/img/20240501-sdp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20240501-sdp.png -------------------------------------------------------------------------------- /website/docs/img/20240706-match-surrounding-0-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20240706-match-surrounding-0-0.png -------------------------------------------------------------------------------- /website/docs/img/20240706-match-surrounding-1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20240706-match-surrounding-1-1.png -------------------------------------------------------------------------------- /website/docs/img/20240706-preview-json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20240706-preview-json.png -------------------------------------------------------------------------------- /website/docs/img/20240706-preview-pdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20240706-preview-pdf.png -------------------------------------------------------------------------------- /website/docs/img/20240715-preview-odt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20240715-preview-odt.png -------------------------------------------------------------------------------- /website/docs/img/20241011-alias-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20241011-alias-tree.png -------------------------------------------------------------------------------- /website/docs/img/20241011-tree-with-args.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20241011-tree-with-args.png -------------------------------------------------------------------------------- /website/docs/img/20241027-cows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20241027-cows.png -------------------------------------------------------------------------------- /website/docs/img/20241027-preview-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/20241027-preview-favicon.png -------------------------------------------------------------------------------- /website/docs/img/br-w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/br-w.png -------------------------------------------------------------------------------- /website/docs/img/gf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/gf.png -------------------------------------------------------------------------------- /website/docs/img/help-filtered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/help-filtered.png -------------------------------------------------------------------------------- /website/docs/img/help-search-modes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/help-search-modes.png -------------------------------------------------------------------------------- /website/docs/img/help-unfiltered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/help-unfiltered.png -------------------------------------------------------------------------------- /website/docs/img/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/icons.png -------------------------------------------------------------------------------- /website/docs/img/regex-antislash-d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/regex-antislash-d.png -------------------------------------------------------------------------------- /website/docs/img/skins/solarized_dark/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/skins/solarized_dark/default.png -------------------------------------------------------------------------------- /website/docs/img/skins/solarized_dark/panels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/skins/solarized_dark/panels.png -------------------------------------------------------------------------------- /website/docs/img/skins/solarized_dark/perms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/skins/solarized_dark/perms.png -------------------------------------------------------------------------------- /website/docs/img/skins/solarized_dark/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/skins/solarized_dark/search.png -------------------------------------------------------------------------------- /website/docs/img/skins/solarized_dark/sizes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/skins/solarized_dark/sizes.png -------------------------------------------------------------------------------- /website/docs/img/skins/solarized_light/fs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/skins/solarized_light/fs.png -------------------------------------------------------------------------------- /website/docs/img/skins/solarized_light/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/skins/solarized_light/search.png -------------------------------------------------------------------------------- /website/docs/img/sort_by_date.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/sort_by_date.png -------------------------------------------------------------------------------- /website/docs/img/sorts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/sorts.png -------------------------------------------------------------------------------- /website/docs/img/subpath-match-not-shown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/subpath-match-not-shown.png -------------------------------------------------------------------------------- /website/docs/img/subpath-match-shown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/subpath-match-shown.png -------------------------------------------------------------------------------- /website/docs/img/tree-dates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/tree-dates.png -------------------------------------------------------------------------------- /website/docs/img/tree-perm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/tree-perm.png -------------------------------------------------------------------------------- /website/docs/img/tree-sizes-and-counts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/tree-sizes-and-counts.png -------------------------------------------------------------------------------- /website/docs/img/tree-sizes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/tree-sizes.png -------------------------------------------------------------------------------- /website/docs/img/tree_view-basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/tree_view-basic.png -------------------------------------------------------------------------------- /website/docs/img/vache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Canop/broot/f19ab14e3f151c337a0f35d61bbca253fce2c707/website/docs/img/vache.png -------------------------------------------------------------------------------- /website/docs/install-br.md: -------------------------------------------------------------------------------- 1 | 2 | broot is convenient to find a directory then `cd` to it, which is done using altenter or `:cd`. 3 | 4 | But broot needs a companion function in the shell in order to be able to change directory. 5 | 6 | # Automatic shell function installation 7 | 8 | This is normally the easiest solution and it's safe. 9 | 10 | When you start broot, it checks whether the `br` shell function seems to have been installed (or to have been refused). If needed, and if the used shell seems compatible, then broot asks the permission to register this shell function. 11 | 12 | When it's done, you can do `br` to launch broot, and typing altenter will cd for you. 13 | 14 | Supported shells today are bash, zsh, fish, nushell, and powershell. 15 | 16 | !!! Note 17 | **Windows users:** broot may need additional rights at first use in order to write its configuration file. You may also have to allow script execution (`set-executionpolicy unrestricted`) 18 | **Nushell users:** use either `alias` or `def --env` when aliasing the `br` shell function. `def` alone won't enable the shell function to perform `cd`. 19 | 20 | # Retry the automatic installation 21 | 22 | If you have messed with the configuration files, you might want to have the shell function reinstalled. 23 | 24 | In order to do this, either remove all broot config files, or launch `broot --install`. 25 | 26 | You can also use the `--install` argument when you first refused and then decided you want it installed. 27 | 28 | # Manual shell function installation 29 | 30 | If you prefer to manage the function sourcing yourself, or to automate the installation your way, or if you use an unsupported configuration, you still can get some help of broot: 31 | 32 | `broot --print-shell-function bash` (you can replace `bash` with `zsh`, `fish`, or `nushell`) outputs a recommended shell function. 33 | 34 | `broot --set-install-state installed` tells broot the `br` function is installed (other possible values are `undefined` and `refused`). 35 | 36 | # `br` alias for Xonsh shell 37 | 38 | The shortcut for [xonsh](https://xon.sh/) shell can be installed with using [xontrib-broot](https://github.com/jnoortheen/xontrib-broot) 39 | 40 | -------------------------------------------------------------------------------- /website/docs/js/details.js: -------------------------------------------------------------------------------- 1 | /// Make sections with class "note details" collapsible 2 | // (initially closed) 3 | // 4 | // Such a section is created by the following markdown: 5 | // 6 | // ```markdown 7 | // !!! Note Details 8 | // 9 | // Some **markdown** content 10 | // ``` 11 | (function main(){ 12 | $(".note.details").each(function(){ 13 | let section = this; 14 | section.classList.add("closed"); 15 | $(section).children(".admonition-title") 16 | .text("Details") 17 | .click(function(){ 18 | section.classList.toggle("closed"); 19 | }); 20 | }); 21 | })(); 22 | 23 | -------------------------------------------------------------------------------- /website/docs/js/link-to-dystroy.js: -------------------------------------------------------------------------------- 1 | 2 | (function ltd_main(){ 3 | function updateLink() { 4 | $(".link-to-dystroy").remove(); 5 | let $brand = $(".navbar-brand"); 6 | let available = $brand.offset().left; 7 | if (available < 30) return; 8 | $("") 9 | .attr("href", "https://dystroy.org") 10 | .addClass("link-to-dystroy") 11 | .prependTo(".navbar"); 12 | } 13 | window.addEventListener("resize", updateLink); 14 | updateLink(); 15 | })(); 16 | 17 | 18 | -------------------------------------------------------------------------------- /website/docs/js/tab-langs.js: -------------------------------------------------------------------------------- 1 | 2 | (function main(){ 3 | let groups = find_groups(["Hjson", "JSON", "TOML"]); 4 | console.log("groups:", groups); 5 | for (let group of groups) { 6 | add_tabs(group); 7 | } 8 | })(); 9 | 10 | 11 | // find and group the pre/code elements with the matching languages 12 | // return [[e]] 13 | function find_groups(langs) { 14 | let groups = []; 15 | $("code").each(function(){ 16 | let lang = langs.find( 17 | lang => this.className.toLowerCase().split(/[ -]/).includes(lang.toLowerCase()) 18 | ); 19 | if (!lang) return; 20 | let pre = this.parentElement; 21 | pre.classList.add("tabbed"); 22 | let last_group = groups[groups.length-1]; 23 | let item = { lang, pre }; 24 | if (last_group) { 25 | if (last_group[last_group.length-1].pre == pre.previousElementSibling) { 26 | last_group.push(item); 27 | return; 28 | } 29 | } 30 | groups.push([item]); 31 | }); 32 | return groups; 33 | } 34 | 35 | function add_tabs(group) { 36 | let $tabs = $("
    "); 37 | group.forEach((item, idx) => { 38 | let $tab = $("").text(item.lang).appendTo($tabs); 39 | if (idx) $(item.pre).hide(); 40 | else $tab.addClass("active"); 41 | $tab.click(function(){ 42 | $tabs.find(".lang-tab").removeClass("active"); 43 | group.forEach((_item, _idx) => { 44 | if (_idx==idx) $(_item.pre).show(); 45 | else $(_item.pre).hide(); 46 | }); 47 | this.classList.add("active"); 48 | }); 49 | }); 50 | $tabs.insertBefore(group[0].pre); 51 | } 52 | -------------------------------------------------------------------------------- /website/docs/modal.md: -------------------------------------------------------------------------------- 1 | 2 | # Why 3 | 4 | The "modal mode", which may be familiar to vim users, changes a little the way you interact with broot: 5 | 6 | - The input at the bottom isn't immediately focused, you must type a space, a `:`, or a `/` to focus it. And you unfocus it with the escape key. 7 | - The upside is you can use keyboard shortcuts without ctrl, for example you may move the selection with j and k. 8 | - The downside is you have one letter more to type to start searching, which isn't to dismiss as searching is usually the first thing you do in broot. 9 | 10 | I recommend you **don't** activate this mode until you really tried broot. Broot isn't a text editor and can't be confused with one. This mode may be more comfortable when you constantly jump from vim to broot but only after you understood how broot works. 11 | 12 | You may be an avid vim user, as I am, and still prefer not to use modality in broot. Starting in *command* mode means you have one more letter to type before searching, because search is done in *input* mode. And broot is search oriented and often used in very short sessions (less than 5 seconds from intent to launch to being back in the shell in the right directory or editing the right file in your favorite editor). 13 | 14 | # Configuration 15 | 16 | You need first to enable the "modal mode" and to decide whether you want to start in `command` or `input` mode: 17 | 18 | ```hjson 19 | modal: true 20 | initial_mode: command 21 | ``` 22 | ```TOML 23 | # note that this must be near the start of the configuration file 24 | modal = true 25 | initial_mode = "command" 26 | ``` 27 | 28 | If `modal` isn't set to `true`, the single letter shortcuts you define in configuration will be ignored (so you don't have to remove them if you don't want modality anymore). 29 | 30 | # Usage 31 | 32 | Broot may be in one of two modes: 33 | 34 | 1. **input** mode, with the input field at the bottom focused and received standard keys 35 | 1. **command** mode, with input not focused, and single key shortcuts enabled 36 | 37 | In *command* mode, you'll find those keys already configured: 38 | 39 | * `j` and `k` to go down and up 40 | * `h` and `l` to go to parent or to enter a directory 41 | 42 | You enter *input* mode by typing one of those letters: ` ` (space), `:`, or `/`. You leave it with the `escape` key. You may add other bindings to the `:mode_input` and `:mode_command` verbs. 43 | 44 | -------------------------------------------------------------------------------- /website/docs/remote.md: -------------------------------------------------------------------------------- 1 | 2 | # Presentation 3 | 4 | Broot can also act as client or server, which lets you 5 | 6 | * control broot from another process 7 | * query the state of broot from another process 8 | 9 | Example use cases: 10 | 11 | * synchronize broot with another program (shell, editor, etc.), both ways 12 | * have a viewer automatically display the file selected in broot 13 | * have broot automatically show the content of a directory focused in another program 14 | 15 | !!! Note 16 | This feature is only available on unix like systems today because the current implementation is based on unix sockets. 17 | 18 | # Usage 19 | 20 | 3 launch arguments are involved: 21 | 22 | * `--listen ` : listen on a specific socket 23 | * `--send `: send the command(s) to the given server and quit 24 | * `--get-root`: ask the server for its current root (in the active panel) 25 | 26 | For example if you start broot with 27 | 28 | br --listen my_broot 29 | 30 | broot will run normally but will *also* listen to commands sent from elsewhere (using linux sockets). 31 | 32 | 33 | Now that the "server" is running, try launching a command from another terminal: 34 | 35 | br --send my_broot -c "img;:parent;:focus" 36 | 37 | this will make the running "server" search for something like "img" and focus its parent. 38 | 39 | If you run 40 | 41 | br --send my_broot --get-root 42 | 43 | then the server's current root is printed on stdout. 44 | 45 | If you pass neither the `--get-root` nor the `--cmd` (shortened in `-c`) argument, then the server is told to focus the current directory or the path given as argument. 46 | 47 | # Hooks 48 | 49 | ## zsh 50 | 51 | `chpwd(){ ( broot --send global_file_viewer "$PWD" & ) > /dev/null 2>&1 }` 52 | 53 | 54 | -------------------------------------------------------------------------------- /website/docs/staging-area.md: -------------------------------------------------------------------------------- 1 | 2 | The staging area is broot's solution to let you execute commands on several files in one go. 3 | 4 | When the staging area is focused, commands apply to all the files it contains. 5 | 6 | # Stage and unstage files 7 | 8 | You can change the bindings to the `:stage`, `:unstage` and `:toggle_stage` verbs. 9 | The standard bindings are below 10 | 11 | verb | default binding | comment 12 | -|-|- 13 | `:toggle_stage` | ctrlg | the easiest solution when not using broot in [modal](../modal) 14 | `:stage` | + | only in [command mode](../modal#usage) 15 | `:unstage` | - | only in [command mode](../modal#usage) 16 | `:clear_stage` | | shortcut: `:cls` 17 | 18 | When staging a file, the staging area opens (but doesn't get focused) if it wasn't and there's not already the max number of panels open. 19 | 20 | # Execute a command 21 | 22 | Focus the staging area (usually with ctrl) then type the verb in the input. 23 | 24 | The verb will be executed, in order, to all files of the staging area. 25 | 26 | ![staging mv](img/20210424-staging-mv.png) 27 | 28 | Computed groups which would have the same value for all files are shown in the status bar. For example here, when you type ` mv ../app-panels`, broot can tell you that it will run `mv {file} /home/dys/dev/broot/src/app-panels/` for each file of the staging area. 29 | 30 | Some verbs aren't compatible with execution on the staging area: 31 | 32 | * Verbs which don't come back to broot after execution (for example `:cd` or any verb quitting broot) 33 | * [Sequences](../conf_verbs#cmd-execution) 34 | 35 | # Read the staging area 36 | 37 | The staging area can be opened or closed with the `:open_staging_area`, `:close_staging_area`, and `:toggle_staging_area` verbs, which have shortcuts `:osa`, `:csa`, and `:tsa`. 38 | But you rarely need those verbs as the staging area opens when you add to it and closes when it goes empty. 39 | 40 | You can filter, select, scroll, as in other panels. This may be convenient either to unstage a precise file from the staging area, or to check some files are present (or not) when there are many of them. 41 | 42 | ![staging filter](img/20210425-staging-filter.png) 43 | 44 | # Tips 45 | 46 | All files from left panel can be added at once to the staging panel with `ctrl-a`. 47 | 48 | # Evolutions 49 | 50 | The staging area is a new feature of broot. Some features (staging with globs for example) may be added, some may be changed, depending on your feedback. 51 | 52 | 53 | -------------------------------------------------------------------------------- /website/docs/trash.md: -------------------------------------------------------------------------------- 1 | 2 | ## Commands 3 | 4 | When broot is compiled with the `trash` feature enabled (which is done for Linux and Windows in standard distributions), some additional commands are available: 5 | 6 | * `:trash` : move the selected file to the trash 7 | * `:open_trash` : display the content of the trash 8 | * `:restore_trashed_file` : restore the file to its original location 9 | * `:delete_trashed_file` : irreversibly delete a file which is in the trash 10 | * `:purge_trash` : irreversibly delete the whole content of the trash 11 | 12 | `:restore_trashed_file` and `:delete_trashed_file` are only available when the trash content is displayed. 13 | 14 | -------------------------------------------------------------------------------- /website/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Broot 2 | site_description: 'Broot, a tree oriented file manager' 3 | site_url: https://dystroy.org/broot 4 | repo_url: https://github.com/Canop/broot 5 | edit_uri: '' 6 | 7 | nav: 8 | - Why: index.md 9 | - Install: 10 | - Install broot: install.md 11 | - Install br: install-br.md 12 | - Common Problems: common-problems.md 13 | - Usage: 14 | - Launch: launch.md 15 | - Search and Navigate: navigation.md 16 | - Tree View: tree_view.md 17 | - Help screen: help.md 18 | - Verbs & Commands: verbs.md 19 | - Panels: panels.md 20 | - Staging Area: staging-area.md 21 | - Trash: trash.md 22 | - Tree export: export.md 23 | - Tips & tricks: tricks.md 24 | - Common file operations: file-operations.md 25 | - Client - Server: remote.md 26 | - Reference - The input: input.md 27 | - Conf: 28 | - Config files: conf_file.md 29 | - Verbs and shortcuts: conf_verbs.md 30 | - Skins: skins.md 31 | - Icons: icons.md 32 | - Modal Mode: modal.md 33 | - Community: community.md 34 | 35 | extra_css: 36 | - css/extra.css 37 | - css/tab-langs.css 38 | - css/link-to-dystroy.css 39 | - css/details.css 40 | 41 | extra_javascript: 42 | - js/tab-langs.js 43 | - js/link-to-dystroy.js 44 | - js/details.js 45 | 46 | theme: 47 | name: null 48 | custom_dir: broot_theme/ 49 | highlightjs: true 50 | 51 | markdown_extensions: 52 | - admonition 53 | - def_list 54 | --------------------------------------------------------------------------------